| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | /*************************************************************************/ | 
					
						
							|  |  |  | /*  portal.cpp                                                           */ | 
					
						
							|  |  |  | /*************************************************************************/ | 
					
						
							|  |  |  | /*                       This file is part of:                           */ | 
					
						
							|  |  |  | /*                           GODOT ENGINE                                */ | 
					
						
							|  |  |  | /*                      https://godotengine.org                          */ | 
					
						
							|  |  |  | /*************************************************************************/ | 
					
						
							|  |  |  | /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */ | 
					
						
							|  |  |  | /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */ | 
					
						
							|  |  |  | /*                                                                       */ | 
					
						
							|  |  |  | /* Permission is hereby granted, free of charge, to any person obtaining */ | 
					
						
							|  |  |  | /* a copy of this software and associated documentation files (the       */ | 
					
						
							|  |  |  | /* "Software"), to deal in the Software without restriction, including   */ | 
					
						
							|  |  |  | /* without limitation the rights to use, copy, modify, merge, publish,   */ | 
					
						
							|  |  |  | /* distribute, sublicense, and/or sell copies of the Software, and to    */ | 
					
						
							|  |  |  | /* permit persons to whom the Software is furnished to do so, subject to */ | 
					
						
							|  |  |  | /* the following conditions:                                             */ | 
					
						
							|  |  |  | /*                                                                       */ | 
					
						
							|  |  |  | /* The above copyright notice and this permission notice shall be        */ | 
					
						
							|  |  |  | /* included in all copies or substantial portions of the Software.       */ | 
					
						
							|  |  |  | /*                                                                       */ | 
					
						
							|  |  |  | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | 
					
						
							|  |  |  | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | 
					
						
							|  |  |  | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | 
					
						
							|  |  |  | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | 
					
						
							|  |  |  | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | 
					
						
							|  |  |  | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | 
					
						
							|  |  |  | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | 
					
						
							|  |  |  | /*************************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "portal.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "core/engine.h"
 | 
					
						
							|  |  |  | #include "mesh_instance.h"
 | 
					
						
							|  |  |  | #include "room.h"
 | 
					
						
							| 
									
										
										
										
											2021-07-14 14:34:09 +01:00
										 |  |  | #include "room_group.h"
 | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | #include "room_manager.h"
 | 
					
						
							|  |  |  | #include "scene/main/viewport.h"
 | 
					
						
							|  |  |  | #include "servers/visual_server.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Portal::_portal_plane_convention = false; | 
					
						
							|  |  |  | bool Portal::_settings_gizmo_show_margins = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Portal::Portal() { | 
					
						
							|  |  |  | 	clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 	_settings_active = true; | 
					
						
							|  |  |  | 	_settings_two_way = true; | 
					
						
							|  |  |  | 	_internal = false; | 
					
						
							|  |  |  | 	_linkedroom_ID[0] = -1; | 
					
						
							|  |  |  | 	_linkedroom_ID[1] = -1; | 
					
						
							|  |  |  | 	_pts_world.clear(); | 
					
						
							|  |  |  | 	_pts_local.clear(); | 
					
						
							|  |  |  | 	_pts_local_raw.resize(0); | 
					
						
							|  |  |  | 	_pt_center_world = Vector3(); | 
					
						
							|  |  |  | 	_plane = Plane(); | 
					
						
							| 
									
										
										
										
											2021-07-27 11:48:34 +01:00
										 |  |  | 	_margin = 1.0; | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 	_use_default_margin = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 	// the visual server portal lifetime is linked to the lifetime of this object
 | 
					
						
							|  |  |  | 	_portal_rid = VisualServer::get_singleton()->portal_create(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef TOOLS_ENABLED
 | 
					
						
							|  |  |  | 	_room_manager_godot_ID = 0; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// portals are defined COUNTER clockwise,
 | 
					
						
							|  |  |  | 	// because they point OUTWARD from the room in the direction
 | 
					
						
							|  |  |  | 	// of the normal
 | 
					
						
							|  |  |  | 	PoolVector<Vector2> points; | 
					
						
							|  |  |  | 	points.resize(4); | 
					
						
							|  |  |  | 	points.set(0, Vector2(1, -1)); | 
					
						
							|  |  |  | 	points.set(1, Vector2(1, 1)); | 
					
						
							|  |  |  | 	points.set(2, Vector2(-1, 1)); | 
					
						
							|  |  |  | 	points.set(3, Vector2(-1, -1)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_points(points); // default shape
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Portal::~Portal() { | 
					
						
							|  |  |  | 	if (_portal_rid != RID()) { | 
					
						
							|  |  |  | 		VisualServer::get_singleton()->free(_portal_rid); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-14 14:34:09 +01:00
										 |  |  | String Portal::get_configuration_warning() const { | 
					
						
							|  |  |  | 	String warning = Spatial::get_configuration_warning(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	auto lambda = [](const Node *p_node) { | 
					
						
							|  |  |  | 		return static_cast<bool>((Object::cast_to<RoomManager>(p_node) || Object::cast_to<Room>(p_node) || Object::cast_to<RoomGroup>(p_node))); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (Room::detect_nodes_using_lambda(this, lambda)) { | 
					
						
							|  |  |  | 		if (Room::detect_nodes_of_type<RoomManager>(this)) { | 
					
						
							|  |  |  | 			if (!warning.empty()) { | 
					
						
							|  |  |  | 				warning += "\n\n"; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			warning += TTR("The RoomManager should not be a child or grandchild of a Portal."); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (Room::detect_nodes_of_type<Room>(this)) { | 
					
						
							|  |  |  | 			if (!warning.empty()) { | 
					
						
							|  |  |  | 				warning += "\n\n"; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			warning += TTR("A Room should not be a child or grandchild of a Portal."); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (Room::detect_nodes_of_type<RoomGroup>(this)) { | 
					
						
							|  |  |  | 			if (!warning.empty()) { | 
					
						
							|  |  |  | 				warning += "\n\n"; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			warning += TTR("A RoomGroup should not be a child or grandchild of a Portal."); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return warning; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | void Portal::set_points(const PoolVector<Vector2> &p_points) { | 
					
						
							|  |  |  | 	_pts_local_raw = p_points; | 
					
						
							|  |  |  | 	_sanitize_points(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (is_inside_tree()) { | 
					
						
							|  |  |  | 		portal_update(); | 
					
						
							|  |  |  | 		update_gizmo(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PoolVector<Vector2> Portal::get_points() const { | 
					
						
							|  |  |  | 	return _pts_local_raw; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // extra editor links to the room manager to allow unloading
 | 
					
						
							|  |  |  | // on change, or re-converting
 | 
					
						
							|  |  |  | void Portal::_changed() { | 
					
						
							|  |  |  | #ifdef TOOLS_ENABLED
 | 
					
						
							|  |  |  | 	RoomManager *rm = RoomManager::active_room_manager; | 
					
						
							|  |  |  | 	if (!rm) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rm->_rooms_changed(); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::clear() { | 
					
						
							|  |  |  | 	_internal = false; | 
					
						
							|  |  |  | 	_linkedroom_ID[0] = -1; | 
					
						
							|  |  |  | 	_linkedroom_ID[1] = -1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::_notification(int p_what) { | 
					
						
							|  |  |  | 	switch (p_what) { | 
					
						
							|  |  |  | 		case NOTIFICATION_ENTER_WORLD: { | 
					
						
							|  |  |  | 			ERR_FAIL_COND(get_world().is_null()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// defer full creation of the visual server portal to when the editor portal is in the scene tree
 | 
					
						
							|  |  |  | 			VisualServer::get_singleton()->portal_set_scenario(_portal_rid, get_world()->get_scenario()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// we can't calculate world points until we have entered the tree
 | 
					
						
							|  |  |  | 			portal_update(); | 
					
						
							|  |  |  | 			update_gizmo(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} break; | 
					
						
							|  |  |  | 		case NOTIFICATION_EXIT_WORLD: { | 
					
						
							|  |  |  | 			// partially destroy  the visual server portal when the editor portal exits the scene tree
 | 
					
						
							|  |  |  | 			VisualServer::get_singleton()->portal_set_scenario(_portal_rid, RID()); | 
					
						
							|  |  |  | 		} break; | 
					
						
							|  |  |  | 		case NOTIFICATION_TRANSFORM_CHANGED: { | 
					
						
							|  |  |  | 			// keep the world points and the visual server up to date
 | 
					
						
							|  |  |  | 			portal_update(); | 
					
						
							| 
									
										
										
										
											2021-07-29 18:58:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// In theory we shouldn't need to update the gizmo when the transform
 | 
					
						
							|  |  |  | 			// changes .. HOWEVER, the portal margin is displayed in world space units,
 | 
					
						
							|  |  |  | 			// back transformed to model space.
 | 
					
						
							|  |  |  | 			// If the Z scale is changed by the user, the portal margin length can become incorrect
 | 
					
						
							|  |  |  | 			// and needs 'resyncing' to the global scale of the portal node.
 | 
					
						
							|  |  |  | 			// We really only need to do this when Z scale is changed, but it is easier codewise
 | 
					
						
							|  |  |  | 			// to always change it, unless we have evidence this is a performance problem.
 | 
					
						
							|  |  |  | 			update_gizmo(); | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 		} break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::set_portal_active(bool p_active) { | 
					
						
							|  |  |  | 	_settings_active = p_active; | 
					
						
							|  |  |  | 	VisualServer::get_singleton()->portal_set_active(_portal_rid, p_active); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Portal::get_portal_active() const { | 
					
						
							|  |  |  | 	return _settings_active; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::set_use_default_margin(bool p_use) { | 
					
						
							|  |  |  | 	_use_default_margin = p_use; | 
					
						
							|  |  |  | 	update_gizmo(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Portal::get_use_default_margin() const { | 
					
						
							|  |  |  | 	return _use_default_margin; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::set_portal_margin(real_t p_margin) { | 
					
						
							|  |  |  | 	_margin = p_margin; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!_use_default_margin) { | 
					
						
							|  |  |  | 		// give visual feedback in the editor for the portal margin zone
 | 
					
						
							|  |  |  | 		update_gizmo(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | real_t Portal::get_portal_margin() const { | 
					
						
							|  |  |  | 	return _margin; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | void Portal::resolve_links(const LocalVector<Room *, int32_t> &p_rooms, const RID &p_from_room_rid) { | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 	Room *linkedroom = nullptr; | 
					
						
							|  |  |  | 	if (has_node(_settings_path_linkedroom)) { | 
					
						
							|  |  |  | 		linkedroom = Object::cast_to<Room>(get_node(_settings_path_linkedroom)); | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// only allow linking to rooms that are part of the roomlist
 | 
					
						
							|  |  |  | 		// (already recognised).
 | 
					
						
							|  |  |  | 		// If we don't check this, it will start trying to link to Room nodes that are invalid,
 | 
					
						
							|  |  |  | 		// and crash.
 | 
					
						
							|  |  |  | 		if (linkedroom && (p_rooms.find(linkedroom) == -1)) { | 
					
						
							|  |  |  | 			// invalid room
 | 
					
						
							|  |  |  | 			WARN_PRINT("Portal attempting to link to Room outside the roomlist : " + linkedroom->get_name()); | 
					
						
							|  |  |  | 			linkedroom = nullptr; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// this should not happen, but just in case
 | 
					
						
							|  |  |  | 		if (linkedroom && (linkedroom->_room_ID >= p_rooms.size())) { | 
					
						
							|  |  |  | 			WARN_PRINT("Portal attempting to link to invalid Room : " + linkedroom->get_name()); | 
					
						
							|  |  |  | 			linkedroom = nullptr; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (linkedroom) { | 
					
						
							|  |  |  | 		_linkedroom_ID[1] = linkedroom->_room_ID; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// send to visual server
 | 
					
						
							|  |  |  | 		VisualServer::get_singleton()->portal_link(_portal_rid, p_from_room_rid, linkedroom->_room_rid, _settings_two_way); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		_linkedroom_ID[1] = -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::set_linked_room_internal(const NodePath &link_path) { | 
					
						
							|  |  |  | 	_settings_path_linkedroom = link_path; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Portal::try_set_unique_name(const String &p_name) { | 
					
						
							|  |  |  | 	SceneTree *scene_tree = get_tree(); | 
					
						
							|  |  |  | 	if (!scene_tree) { | 
					
						
							|  |  |  | 		// should not happen in the editor
 | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Viewport *root = scene_tree->get_root(); | 
					
						
							|  |  |  | 	if (!root) { | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Node *found = root->find_node(p_name, true, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if the name does not already exist in the scene tree, we can use it
 | 
					
						
							|  |  |  | 	if (!found) { | 
					
						
							|  |  |  | 		set_name(p_name); | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// we are trying to set the same name this node already has...
 | 
					
						
							|  |  |  | 	if (found == this) { | 
					
						
							|  |  |  | 		// noop
 | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::set_linked_room(const NodePath &link_path) { | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 	_settings_path_linkedroom = link_path; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 	// change the name of the portal as well, if the link looks legit
 | 
					
						
							|  |  |  | 	Room *linkedroom = nullptr; | 
					
						
							|  |  |  | 	if (has_node(link_path)) { | 
					
						
							|  |  |  | 		linkedroom = Object::cast_to<Room>(get_node(link_path)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (linkedroom) { | 
					
						
							|  |  |  | 			if (linkedroom != get_parent()) { | 
					
						
							|  |  |  | 				_settings_path_linkedroom = link_path; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// change the portal name
 | 
					
						
							|  |  |  | 				String string_link_room = RoomManager::_find_name_after(linkedroom, "Room"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// we need a unique name for the portal
 | 
					
						
							|  |  |  | 				String string_name_base = "Portal" + GODOT_PORTAL_DELINEATOR + string_link_room; | 
					
						
							|  |  |  | 				if (!try_set_unique_name(string_name_base)) { | 
					
						
							|  |  |  | 					bool success = false; | 
					
						
							|  |  |  | 					for (int n = 0; n < 128; n++) { | 
					
						
							|  |  |  | 						String string_name = string_name_base + GODOT_PORTAL_WILDCARD + itos(n); | 
					
						
							|  |  |  | 						if (try_set_unique_name(string_name)) { | 
					
						
							|  |  |  | 							success = true; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					if (!success) { | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 						WARN_PRINT("Could not set portal name, suggest setting name manually instead."); | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 				WARN_PRINT("Linked room cannot be the parent room of a portal."); | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 			WARN_PRINT("Linked room path is not a room."); | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-16 09:15:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	_changed(); | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | NodePath Portal::get_linked_room() const { | 
					
						
							|  |  |  | 	return _settings_path_linkedroom; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::flip() { | 
					
						
							|  |  |  | 	// flip portal
 | 
					
						
							|  |  |  | 	Transform tr = get_transform(); | 
					
						
							|  |  |  | 	Basis flip_basis = Basis(Vector3(0, Math_PI, 0)); | 
					
						
							|  |  |  | 	tr.basis *= flip_basis; | 
					
						
							|  |  |  | 	set_transform(tr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_pts_local.clear(); | 
					
						
							|  |  |  | 	_pts_world.clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// flip the raw verts
 | 
					
						
							|  |  |  | 	Vector<Vector2> raw; | 
					
						
							|  |  |  | 	raw.resize(_pts_local_raw.size()); | 
					
						
							|  |  |  | 	for (int n = 0; n < _pts_local_raw.size(); n++) { | 
					
						
							|  |  |  | 		const Vector2 &pt = _pts_local_raw[n]; | 
					
						
							|  |  |  | 		raw.set(n, Vector2(-pt.x, pt.y)); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// standardize raw verts winding
 | 
					
						
							|  |  |  | 	Geometry::sort_polygon_winding(raw, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int n = 0; n < raw.size(); n++) { | 
					
						
							|  |  |  | 		_pts_local_raw.set(n, raw[n]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_sanitize_points(); | 
					
						
							|  |  |  | 	portal_update(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	update_gizmo(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Portal::create_from_mesh_instance(const MeshInstance *p_mi) { | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(!p_mi, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_pts_local.clear(); | 
					
						
							|  |  |  | 	_pts_world.clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Ref<Mesh> rmesh = p_mi->get_mesh(); | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(!rmesh.is_valid(), false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (rmesh->get_surface_count() == 0) { | 
					
						
							|  |  |  | 		WARN_PRINT(vformat("Portal '%s' has no surfaces, ignoring", get_name())); | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Array arrays = rmesh->surface_get_arrays(0); | 
					
						
							|  |  |  | 	PoolVector<Vector3> vertices = arrays[VS::ARRAY_VERTEX]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get the model space verts and find center
 | 
					
						
							|  |  |  | 	int num_source_points = vertices.size(); | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(num_source_points < 3, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const Transform &tr_source = p_mi->get_global_transform(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Vector<Vector3> pts_world; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int n = 0; n < num_source_points; n++) { | 
					
						
							|  |  |  | 		Vector3 pt = tr_source.xform(vertices[n]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// test for duplicates.
 | 
					
						
							|  |  |  | 		// Some geometry may contain duplicate verts in portals
 | 
					
						
							|  |  |  | 		// which will muck up the winding etc...
 | 
					
						
							|  |  |  | 		bool duplicate = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (int m = 0; m < pts_world.size(); m++) { | 
					
						
							|  |  |  | 			Vector3 diff = pt - pts_world[m]; | 
					
						
							|  |  |  | 			// hopefully this epsilon will do in nearly all cases
 | 
					
						
							|  |  |  | 			if (diff.length() < 0.001) { | 
					
						
							|  |  |  | 				duplicate = true; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!duplicate) { | 
					
						
							|  |  |  | 			pts_world.push_back(pt); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get the verts sorted with winding, assume that the triangle initial winding
 | 
					
						
							|  |  |  | 	// tells us the normal and hence which way the world space portal should be facing
 | 
					
						
							|  |  |  | 	_sort_verts_clockwise(_portal_plane_convention, pts_world); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// back calculate the plane from *all* the portal points, this will give us a nice average plane
 | 
					
						
							|  |  |  | 	// (in case of wonky portals where artwork isn't bang on)
 | 
					
						
							|  |  |  | 	_plane = _plane_from_points_newell(pts_world); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// change the portal transform to match our plane and the center of the portal
 | 
					
						
							|  |  |  | 	Transform tr_global; | 
					
						
							|  |  |  | 	tr_global.set_look_at(Vector3(0, 0, 0), _plane.normal, Vector3(0, 1, 0)); | 
					
						
							|  |  |  | 	tr_global.origin = _pt_center_world; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// We can't directly set this global transform on the portal, because the parent node may already
 | 
					
						
							|  |  |  | 	// have a transform applied, so we need to account for this and give a corrected local transform
 | 
					
						
							|  |  |  | 	// for the portal, such that the end result global transform will be correct.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// find the difference between this new global transform and the transform of the parent
 | 
					
						
							|  |  |  | 	// then use this for the new local transform of the portal
 | 
					
						
							|  |  |  | 	Spatial *parent = Object::cast_to<Spatial>(get_parent()); | 
					
						
							|  |  |  | 	ERR_FAIL_COND_V(!parent, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Transform tr_inverse_parent = parent->get_global_transform().affine_inverse(); | 
					
						
							|  |  |  | 	Transform new_local_transform = tr_inverse_parent * tr_global; | 
					
						
							|  |  |  | 	set_transform(new_local_transform); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// now back calculate the local space coords of the portal from the world space coords.
 | 
					
						
							|  |  |  | 	// The local space will be used in future for editing and as a 'master' store of the verts.
 | 
					
						
							|  |  |  | 	_pts_local_raw.resize(pts_world.size()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// back transform from global space to local space
 | 
					
						
							|  |  |  | 	Transform tr = tr_global.affine_inverse(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int n = 0; n < pts_world.size(); n++) { | 
					
						
							|  |  |  | 		// pt3 is now in local space
 | 
					
						
							|  |  |  | 		Vector3 pt3 = tr.xform(pts_world[n]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// only the x and y required
 | 
					
						
							|  |  |  | 		_pts_local_raw.set(n, Vector2(pt3.x, pt3.y)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// The z coordinate should be approx zero
 | 
					
						
							|  |  |  | 		// DEV_ASSERT(Math::abs(pt3.z) < 0.1);
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_sanitize_points(); | 
					
						
							|  |  |  | 	portal_update(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::_update_aabb() { | 
					
						
							|  |  |  | 	_aabb_local = AABB(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (_pts_local.size()) { | 
					
						
							|  |  |  | 		Vector3 begin = _vec2to3(_pts_local[0]); | 
					
						
							|  |  |  | 		Vector3 end = begin; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (int n = 1; n < _pts_local.size(); n++) { | 
					
						
							|  |  |  | 			Vector3 pt = _vec2to3(_pts_local[n]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (pt.x < begin.x) { | 
					
						
							|  |  |  | 				begin.x = pt.x; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (pt.y < begin.y) { | 
					
						
							|  |  |  | 				begin.y = pt.y; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (pt.z < begin.z) { | 
					
						
							|  |  |  | 				begin.z = pt.z; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (pt.x > end.x) { | 
					
						
							|  |  |  | 				end.x = pt.x; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (pt.y > end.y) { | 
					
						
							|  |  |  | 				end.y = pt.y; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (pt.z > end.z) { | 
					
						
							|  |  |  | 				end.z = pt.z; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_aabb_local.position = begin; | 
					
						
							|  |  |  | 		_aabb_local.size = end - begin; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::portal_update() { | 
					
						
							|  |  |  | 	// first calculate the plane from the transform
 | 
					
						
							|  |  |  | 	// (portals are standardized outward from source room once sanitized,
 | 
					
						
							|  |  |  | 	// irrespective of the user portal plane convention)
 | 
					
						
							|  |  |  | 	const Transform &tr = get_global_transform(); | 
					
						
							|  |  |  | 	_plane = Plane(0.0, 0.0, -1.0, 0.0); | 
					
						
							|  |  |  | 	_plane = tr.xform(_plane); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// after becoming a portal, the centre world IS the transform origin
 | 
					
						
							|  |  |  | 	_pt_center_world = tr.origin; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// recalculates world points from the local space
 | 
					
						
							|  |  |  | 	int num_points = _pts_local.size(); | 
					
						
							|  |  |  | 	if (_pts_world.size() != num_points) { | 
					
						
							|  |  |  | 		_pts_world.resize(num_points); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int n = 0; n < num_points; n++) { | 
					
						
							|  |  |  | 		_pts_world.set(n, tr.xform(_vec2to3(_pts_local[n]))); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// no need to check winding order, the points are pre-sanitized only when they change
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// extension margin to prevent objects too easily sprawling
 | 
					
						
							|  |  |  | 	real_t margin = get_active_portal_margin(); | 
					
						
							|  |  |  | 	VisualServer::get_singleton()->portal_set_geometry(_portal_rid, _pts_world, margin); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | real_t Portal::get_active_portal_margin() const { | 
					
						
							|  |  |  | 	if (_use_default_margin) { | 
					
						
							| 
									
										
										
										
											2021-07-27 11:48:34 +01:00
										 |  |  | 		return RoomManager::_get_default_portal_margin(); | 
					
						
							| 
									
										
										
										
											2021-02-04 10:43:08 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return _margin; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::_sanitize_points() { | 
					
						
							|  |  |  | 	// remove duplicates? NYI maybe not necessary
 | 
					
						
							|  |  |  | 	Vector<Vector2> raw; | 
					
						
							|  |  |  | 	raw.resize(_pts_local_raw.size()); | 
					
						
							|  |  |  | 	for (int n = 0; n < _pts_local_raw.size(); n++) { | 
					
						
							|  |  |  | 		raw.set(n, _pts_local_raw[n]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// this function may get rid of some concave points due to user editing ..
 | 
					
						
							|  |  |  | 	// may not be necessary, no idea how fast it is
 | 
					
						
							|  |  |  | 	_pts_local = Geometry::convex_hull_2d(raw); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// some pecularity of convex_hull_2d function, it duplicates the last point for some reason
 | 
					
						
							|  |  |  | 	if (_pts_local.size() > 1) { | 
					
						
							|  |  |  | 		_pts_local.resize(_pts_local.size() - 1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// sort winding, the system expects counter clockwise polys
 | 
					
						
							|  |  |  | 	Geometry::sort_polygon_winding(_pts_local, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// a bit of a bodge, but a small epsilon pulling in the portal edges towards the center
 | 
					
						
							|  |  |  | 	// can hide walls in the opposite room that abutt the portal (due to floating point error)
 | 
					
						
							|  |  |  | 	// find 2d center
 | 
					
						
							|  |  |  | 	Vector2 center; | 
					
						
							|  |  |  | 	for (int n = 0; n < _pts_local.size(); n++) { | 
					
						
							|  |  |  | 		center += _pts_local[n]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	center /= _pts_local.size(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const real_t pull_in = 0.0001; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int n = 0; n < _pts_local.size(); n++) { | 
					
						
							|  |  |  | 		Vector2 offset = _pts_local[n] - center; | 
					
						
							|  |  |  | 		real_t l = offset.length(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// don't apply the pull in for tiny holes
 | 
					
						
							|  |  |  | 		if (l > (pull_in * 2.0)) { | 
					
						
							|  |  |  | 			real_t fract = (l - pull_in) / l; | 
					
						
							|  |  |  | 			offset *= fract; | 
					
						
							|  |  |  | 			_pts_local.set(n, center + offset); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_update_aabb(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::_sort_verts_clockwise(bool portal_plane_convention, Vector<Vector3> &r_verts) { | 
					
						
							|  |  |  | 	// cannot sort less than 3 verts
 | 
					
						
							|  |  |  | 	if (r_verts.size() < 3) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// assume first 3 points determine the desired normal, if these first 3 points are garbage,
 | 
					
						
							|  |  |  | 	// the routine will not work.
 | 
					
						
							|  |  |  | 	Plane portal_plane; | 
					
						
							|  |  |  | 	if (portal_plane_convention) { | 
					
						
							|  |  |  | 		portal_plane = Plane(r_verts[0], r_verts[2], r_verts[1]); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		portal_plane = Plane(r_verts[0], r_verts[1], r_verts[2]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const Vector3 &portal_normal = portal_plane.normal; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// find centroid
 | 
					
						
							|  |  |  | 	int num_points = r_verts.size(); | 
					
						
							|  |  |  | 	_pt_center_world = Vector3(0, 0, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int n = 0; n < num_points; n++) { | 
					
						
							|  |  |  | 		_pt_center_world += r_verts[n]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	_pt_center_world /= num_points; | 
					
						
							|  |  |  | 	/////////////////////////////////////////
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// now algorithm
 | 
					
						
							|  |  |  | 	for (int n = 0; n < num_points - 2; n++) { | 
					
						
							|  |  |  | 		Vector3 a = r_verts[n] - _pt_center_world; | 
					
						
							|  |  |  | 		a.normalize(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Plane p = Plane(r_verts[n], _pt_center_world, _pt_center_world + portal_normal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		double smallest_angle = -1; | 
					
						
							|  |  |  | 		int smallest = -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (int m = n + 1; m < num_points; m++) { | 
					
						
							|  |  |  | 			if (p.distance_to(r_verts[m]) > 0.0) { | 
					
						
							|  |  |  | 				Vector3 b = r_verts[m] - _pt_center_world; | 
					
						
							|  |  |  | 				b.normalize(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				double angle = a.dot(b); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (angle > smallest_angle) { | 
					
						
							|  |  |  | 					smallest_angle = angle; | 
					
						
							|  |  |  | 					smallest = m; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} // which side
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} // for m
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// swap smallest and n+1 vert
 | 
					
						
							|  |  |  | 		if (smallest != -1) { | 
					
						
							|  |  |  | 			Vector3 temp = r_verts[smallest]; | 
					
						
							|  |  |  | 			r_verts.set(smallest, r_verts[n + 1]); | 
					
						
							|  |  |  | 			r_verts.set(n + 1, temp); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} // for n
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// the vertices are now sorted, but may be in the opposite order to that wanted.
 | 
					
						
							|  |  |  | 	// we detect this by calculating the normal of the poly, then flipping the order if the normal is pointing
 | 
					
						
							|  |  |  | 	// the wrong way.
 | 
					
						
							|  |  |  | 	Plane plane = Plane(r_verts[0], r_verts[1], r_verts[2]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (portal_normal.dot(plane.normal) < 0.0f) { | 
					
						
							|  |  |  | 		// reverse winding order of verts
 | 
					
						
							|  |  |  | 		r_verts.invert(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Plane Portal::_plane_from_points_newell(const Vector<Vector3> &p_pts) { | 
					
						
							|  |  |  | 	int num_points = p_pts.size(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (num_points < 3) { | 
					
						
							|  |  |  | 		return Plane(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Vector3 normal; | 
					
						
							|  |  |  | 	Vector3 center; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (int i = 0; i < num_points; i++) { | 
					
						
							|  |  |  | 		int j = (i + 1) % num_points; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const Vector3 &pi = p_pts[i]; | 
					
						
							|  |  |  | 		const Vector3 &pj = p_pts[j]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		center += pi; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		normal.x += (((pi.z) + (pj.z)) * ((pj.y) - (pi.y))); | 
					
						
							|  |  |  | 		normal.y += (((pi.x) + (pj.x)) * ((pj.z) - (pi.z))); | 
					
						
							|  |  |  | 		normal.z += (((pi.y) + (pj.y)) * ((pj.x) - (pi.x))); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	normal.normalize(); | 
					
						
							|  |  |  | 	center /= num_points; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_pt_center_world = center; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// point and normal
 | 
					
						
							|  |  |  | 	return Plane(center, normal); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Portal::_bind_methods() { | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("set_portal_active", "p_active"), &Portal::set_portal_active); | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("get_portal_active"), &Portal::get_portal_active); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("set_two_way", "p_two_way"), &Portal::set_two_way); | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("is_two_way"), &Portal::is_two_way); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("set_use_default_margin", "p_use"), &Portal::set_use_default_margin); | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("get_use_default_margin"), &Portal::get_use_default_margin); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("set_portal_margin", "p_margin"), &Portal::set_portal_margin); | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("get_portal_margin"), &Portal::get_portal_margin); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("set_linked_room", "p_room"), &Portal::set_linked_room); | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("get_linked_room"), &Portal::get_linked_room); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("set_points", "points"), &Portal::set_points); | 
					
						
							|  |  |  | 	ClassDB::bind_method(D_METHOD("get_points"), &Portal::get_points); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "portal_active"), "set_portal_active", "get_portal_active"); | 
					
						
							|  |  |  | 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "two_way"), "set_two_way", "is_two_way"); | 
					
						
							|  |  |  | 	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "linked_room", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Room"), "set_linked_room", "get_linked_room"); | 
					
						
							|  |  |  | 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_margin"), "set_use_default_margin", "get_use_default_margin"); | 
					
						
							|  |  |  | 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "portal_margin", PROPERTY_HINT_RANGE, "0.0,10.0,0.01"), "set_portal_margin", "get_portal_margin"); | 
					
						
							|  |  |  | 	ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "points"), "set_points", "get_points"); | 
					
						
							|  |  |  | } |