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 13:34:09 +00: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 08:15:14 +00: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 10:48:34 +00:00
|
|
|
_margin = 1.0;
|
2021-07-16 08:15:14 +00: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 13:34:09 +00: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 17:58:19 +00: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 08:15:14 +00: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 08:15:14 +00: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 08:15:14 +00: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 08:15:14 +00: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 08:15:14 +00: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 08:15:14 +00:00
|
|
|
WARN_PRINT("Linked room path is not a room.");
|
2021-02-04 10:43:08 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-16 08:15:14 +00: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 10:48:34 +00: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");
|
|
|
|
}
|