Portal occlusion culling

Adds support for occlusion culling via rooms and portals.
This commit is contained in:
lawnjelly 2021-02-04 10:43:08 +00:00
parent b0b2b7df31
commit eb6f98ec55
78 changed files with 10865 additions and 45 deletions

79
core/bitfield_dynamic.cpp Normal file
View File

@ -0,0 +1,79 @@
/*************************************************************************/
/* bitfield_dynamic.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 "bitfield_dynamic.h"
#include "core/os/memory.h"
#include <string.h>
void BitFieldDynamic::copy_from(const BitFieldDynamic &p_source) {
create(p_source.get_num_bits(), false);
memcpy(_data, p_source.get_data(), p_source.get_num_bytes());
}
void BitFieldDynamic::create(uint32_t p_num_bits, bool p_blank) {
// first delete any initial
destroy();
_num_bits = p_num_bits;
if (p_num_bits) {
_num_bytes = (p_num_bits / 8) + 1;
_data = (uint8_t *)memalloc(_num_bytes);
if (p_blank) {
blank(false);
}
}
}
void BitFieldDynamic::destroy() {
if (_data) {
memfree(_data);
_data = nullptr;
}
_num_bytes = 0;
_num_bits = 0;
}
void BitFieldDynamic::blank(bool p_set_or_zero) {
if (p_set_or_zero) {
memset(_data, 255, _num_bytes);
} else {
memset(_data, 0, _num_bytes);
}
}
void BitFieldDynamic::invert() {
for (uint32_t n = 0; n < _num_bytes; n++) {
_data[n] = ~_data[n];
}
}

111
core/bitfield_dynamic.h Normal file
View File

@ -0,0 +1,111 @@
/*************************************************************************/
/* bitfield_dynamic.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef BITFIELD_DYNAMIC_H
#define BITFIELD_DYNAMIC_H
#include "core/error_macros.h"
class BitFieldDynamic {
public:
~BitFieldDynamic() { destroy(); }
private:
// prevent copying (see effective C++ scott meyers)
// there is no implementation for copy constructor, hence compiler will complain if you try to copy
// feel free to add one if needed...
BitFieldDynamic &operator=(const BitFieldDynamic &);
public:
// create automatically blanks
void create(uint32_t p_num_bits, bool p_blank = true);
void destroy();
// public funcs
uint32_t get_num_bits() const { return _num_bits; }
uint32_t get_bit(uint32_t p_bit) const;
void set_bit(uint32_t p_bit, uint32_t p_set);
bool check_and_set(uint32_t p_bit);
void blank(bool p_set_or_zero = false);
void invert();
void copy_from(const BitFieldDynamic &p_source);
// loading / saving
uint8_t *get_data() { return _data; }
const uint8_t *get_data() const { return _data; }
uint32_t get_num_bytes() const { return _num_bytes; }
protected:
// member vars
uint8_t *_data = nullptr;
uint32_t _num_bytes = 0;
uint32_t _num_bits = 0;
};
inline uint32_t BitFieldDynamic::get_bit(uint32_t p_bit) const {
DEV_ASSERT(_data);
uint32_t byte_number = p_bit >> 3; // divide by 8
DEV_ASSERT(byte_number < _num_bytes);
uint8_t uc = _data[byte_number];
uint32_t bit_set = uc & (1 << (p_bit & 7));
return bit_set;
}
inline bool BitFieldDynamic::check_and_set(uint32_t p_bit) {
DEV_ASSERT(_data);
uint32_t byte_number = p_bit >> 3; // divide by 8
DEV_ASSERT(byte_number < _num_bytes);
uint8_t &uc = _data[byte_number];
uint32_t mask = (1 << (p_bit & 7));
uint32_t bit_set = uc & mask;
if (bit_set) {
return false;
}
// set
uc = uc | mask;
return true;
}
inline void BitFieldDynamic::set_bit(uint32_t p_bit, uint32_t p_set) {
DEV_ASSERT(_data);
uint32_t byte_number = p_bit >> 3; // divide by 8
DEV_ASSERT(byte_number < _num_bytes);
uint8_t uc = _data[byte_number];
uint32_t mask = 1 << (p_bit & 7);
if (p_set) {
uc = uc | mask;
} else {
uc &= ~mask;
}
_data[byte_number] = uc;
}
#endif

View File

@ -83,6 +83,10 @@ float Engine::get_time_scale() const {
return _time_scale; return _time_scale;
} }
void Engine::set_portals_active(bool p_active) {
_portals_active = p_active;
}
Dictionary Engine::get_version_info() const { Dictionary Engine::get_version_info() const {
Dictionary dict; Dictionary dict;
dict["major"] = VERSION_MAJOR; dict["major"] = VERSION_MAJOR;
@ -224,6 +228,7 @@ Engine::Engine() {
_frame_ticks = 0; _frame_ticks = 0;
_frame_step = 0; _frame_step = 0;
editor_hint = false; editor_hint = false;
_portals_active = false;
} }
Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr) : Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr) :

View File

@ -60,6 +60,7 @@ private:
bool _gpu_pixel_snap; bool _gpu_pixel_snap;
uint64_t _physics_frames; uint64_t _physics_frames;
float _physics_interpolation_fraction; float _physics_interpolation_fraction;
bool _portals_active;
uint64_t _idle_frames; uint64_t _idle_frames;
bool _in_physics; bool _in_physics;
@ -106,6 +107,8 @@ public:
Object *get_singleton_object(const String &p_name) const; Object *get_singleton_object(const String &p_name) const;
_FORCE_INLINE_ bool get_use_gpu_pixel_snap() const { return _gpu_pixel_snap; } _FORCE_INLINE_ bool get_use_gpu_pixel_snap() const { return _gpu_pixel_snap; }
bool are_portals_active() const { return _portals_active; }
void set_portals_active(bool p_active);
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; } _FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; }

View File

@ -320,6 +320,24 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li
} \ } \
} }
/**
* Should assert only if making a build with dev asserts.
* This should be a 'free' check for program flow and should not be needed in any releases,
* only used in dev builds.
*/
// #define DEV_ASSERTS_ENABLED
#ifdef DEV_ASSERTS_ENABLED
#define DEV_ASSERT(m_cond) \
{ \
if (unlikely(!(m_cond))) { \
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: DEV_ASSERT failed \"" _STR(m_cond) "\" is false."); \
GENERATE_TRAP \
} \
}
#else
#define DEV_ASSERT(m_cond)
#endif
/** /**
* If `m_cond` evaluates to `true`, crashes the engine immediately with a generic error message. * If `m_cond` evaluates to `true`, crashes the engine immediately with a generic error message.
* Only use this if there's no sensible fallback (i.e. the error is unrecoverable). * Only use this if there's no sensible fallback (i.e. the error is unrecoverable).

View File

@ -43,6 +43,44 @@ bool AABB::operator!=(const AABB &p_rval) const {
return ((position != p_rval.position) || (size != p_rval.size)); return ((position != p_rval.position) || (size != p_rval.size));
} }
bool AABB::create_from_points(const Vector<Vector3> &p_points) {
if (!p_points.size()) {
return false;
}
Vector3 begin = p_points[0];
Vector3 end = begin;
for (int n = 1; n < p_points.size(); n++) {
const Vector3 &pt = p_points[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;
}
}
position = begin;
size = end - begin;
return true;
}
void AABB::merge_with(const AABB &p_aabb) { void AABB::merge_with(const AABB &p_aabb) {
Vector3 beg_1, beg_2; Vector3 beg_1, beg_2;
Vector3 end_1, end_2; Vector3 end_1, end_2;

View File

@ -58,6 +58,7 @@ public:
void set_position(const Vector3 &p_pos) { position = p_pos; } void set_position(const Vector3 &p_pos) { position = p_pos; }
const Vector3 &get_size() const { return size; } const Vector3 &get_size() const { return size; }
void set_size(const Vector3 &p_size) { size = p_size; } void set_size(const Vector3 &p_size) { size = p_size; }
Vector3 get_center() const { return position + (size * 0.5); }
bool operator==(const AABB &p_rval) const; bool operator==(const AABB &p_rval) const;
bool operator!=(const AABB &p_rval) const; bool operator!=(const AABB &p_rval) const;
@ -98,6 +99,7 @@ public:
AABB expand(const Vector3 &p_vector) const; AABB expand(const Vector3 &p_vector) const;
_FORCE_INLINE_ void project_range_in_plane(const Plane &p_plane, real_t &r_min, real_t &r_max) const; _FORCE_INLINE_ void project_range_in_plane(const Plane &p_plane, real_t &r_min, real_t &r_max) const;
_FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */ _FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */
bool create_from_points(const Vector<Vector3> &p_points);
_FORCE_INLINE_ AABB abs() const { _FORCE_INLINE_ AABB abs() const {
return AABB(Vector3(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0), position.z + MIN(size.z, 0)), size.abs()); return AABB(Vector3(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0), position.z + MIN(size.z, 0)), size.abs());

View File

@ -65,6 +65,7 @@ public:
bool is_degenerate() const; bool is_degenerate() const;
real_t get_area() const; real_t get_area() const;
real_t get_twice_area_squared() const;
Vector3 get_median_point() const; Vector3 get_median_point() const;
Vector3 get_closest_point_to(const Vector3 &p_point) const; Vector3 get_closest_point_to(const Vector3 &p_point) const;
@ -96,6 +97,12 @@ public:
} }
}; };
inline real_t Face3::get_twice_area_squared() const {
Vector3 edge1 = vertex[1] - vertex[0];
Vector3 edge2 = vertex[2] - vertex[0];
return edge1.cross(edge2).length_squared();
}
bool Face3::intersects_aabb2(const AABB &p_aabb) const { bool Face3::intersects_aabb2(const AABB &p_aabb) const {
Vector3 perp = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]); Vector3 perp = (vertex[0] - vertex[2]).cross(vertex[0] - vertex[1]);

View File

@ -1175,7 +1175,127 @@ Vector<Vector<Point2>> Geometry::_polypath_offset(const Vector<Point2> &p_polypa
return polypaths; return polypaths;
} }
Vector<Vector3> Geometry::compute_convex_mesh_points(const Plane *p_planes, int p_plane_count) { real_t Geometry::calculate_convex_hull_volume(const Geometry::MeshData &p_md) {
if (!p_md.vertices.size()) {
return 0.0;
}
// find center
Vector3 center;
for (int n = 0; n < p_md.vertices.size(); n++) {
center += p_md.vertices[n];
}
center /= p_md.vertices.size();
Face3 fa;
real_t volume = 0.0;
// volume of each cone is 1/3 * height * area of face
for (int f = 0; f < p_md.faces.size(); f++) {
const Geometry::MeshData::Face &face = p_md.faces[f];
real_t height = 0.0;
real_t face_area = 0.0;
for (int c = 0; c < face.indices.size() - 2; c++) {
fa.vertex[0] = p_md.vertices[face.indices[0]];
fa.vertex[1] = p_md.vertices[face.indices[c + 1]];
fa.vertex[2] = p_md.vertices[face.indices[c + 2]];
if (!c) {
// calculate height
Plane plane(fa.vertex[0], fa.vertex[1], fa.vertex[2]);
height = -plane.distance_to(center);
}
face_area += Math::sqrt(fa.get_twice_area_squared());
}
volume += face_area * height;
}
volume *= (1.0 / 3.0) * 0.5;
return volume;
}
// note this function is slow, because it builds meshes etc. Not ideal to use in realtime.
// Planes must face OUTWARD from the center of the convex hull, by convention.
bool Geometry::convex_hull_intersects_convex_hull(const Plane *p_planes_a, int p_plane_count_a, const Plane *p_planes_b, int p_plane_count_b) {
if (!p_plane_count_a || !p_plane_count_b) {
return false;
}
// OR alternative approach, we can call compute_convex_mesh_points()
// with both sets of planes, to get an intersection. Not sure which method is
// faster... this may be faster with more complex hulls.
// the usual silliness to get from one vector format to another...
PoolVector<Plane> planes_a;
PoolVector<Plane> planes_b;
{
planes_a.resize(p_plane_count_a);
PoolVector<Plane>::Write w = planes_a.write();
memcpy(w.ptr(), p_planes_a, p_plane_count_a * sizeof(Plane));
}
{
planes_b.resize(p_plane_count_b);
PoolVector<Plane>::Write w = planes_b.write();
memcpy(w.ptr(), p_planes_b, p_plane_count_b * sizeof(Plane));
}
Geometry::MeshData md_A = build_convex_mesh(planes_a);
Geometry::MeshData md_B = build_convex_mesh(planes_b);
// hull can't be built
if (!md_A.vertices.size() || !md_B.vertices.size()) {
return false;
}
// first check the points against the planes
for (int p = 0; p < p_plane_count_a; p++) {
const Plane &plane = p_planes_a[p];
for (int n = 0; n < md_B.vertices.size(); n++) {
if (!plane.is_point_over(md_B.vertices[n])) {
return true;
}
}
}
for (int p = 0; p < p_plane_count_b; p++) {
const Plane &plane = p_planes_b[p];
for (int n = 0; n < md_A.vertices.size(); n++) {
if (!plane.is_point_over(md_A.vertices[n])) {
return true;
}
}
}
// now check edges
for (int n = 0; n < md_A.edges.size(); n++) {
const Vector3 &pt_a = md_A.vertices[md_A.edges[n].a];
const Vector3 &pt_b = md_A.vertices[md_A.edges[n].b];
if (segment_intersects_convex(pt_a, pt_b, p_planes_b, p_plane_count_b, nullptr, nullptr)) {
return true;
}
}
for (int n = 0; n < md_B.edges.size(); n++) {
const Vector3 &pt_a = md_B.vertices[md_B.edges[n].a];
const Vector3 &pt_b = md_B.vertices[md_B.edges[n].b];
if (segment_intersects_convex(pt_a, pt_b, p_planes_a, p_plane_count_a, nullptr, nullptr)) {
return true;
}
}
return false;
}
Vector<Vector3> Geometry::compute_convex_mesh_points(const Plane *p_planes, int p_plane_count, real_t p_epsilon) {
Vector<Vector3> points; Vector<Vector3> points;
// Iterate through every unique combination of any three planes. // Iterate through every unique combination of any three planes.
@ -1191,8 +1311,8 @@ Vector<Vector3> Geometry::compute_convex_mesh_points(const Plane *p_planes, int
bool excluded = false; bool excluded = false;
for (int n = 0; n < p_plane_count; n++) { for (int n = 0; n < p_plane_count; n++) {
if (n != i && n != j && n != k) { if (n != i && n != j && n != k) {
real_t dp = p_planes[n].normal.dot(convex_shape_point); real_t dist = p_planes[n].distance_to(convex_shape_point);
if (dp - p_planes[n].d > CMP_EPSILON) { if (dist > p_epsilon) {
excluded = true; excluded = true;
break; break;
} }
@ -1242,3 +1362,65 @@ Vector<Geometry::PackRectsResult> Geometry::partial_pack_rects(const Vector<Vect
return ret; return ret;
} }
// adapted from:
// https://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order
void Geometry::sort_polygon_winding(Vector<Vector2> &r_verts, bool p_clockwise) {
// sort winding order of a (primarily convex) polygon.
// It can handle some concave polygons, but not
// where a vertex 'goes back on' a previous vertex ..
// i.e. it will change the shape in some concave cases.
struct ElementComparator {
Vector2 center;
bool operator()(const Vector2 &a, const Vector2 &b) const {
if (a.x - center.x >= 0 && b.x - center.x < 0) {
return true;
}
if (a.x - center.x < 0 && b.x - center.x >= 0) {
return false;
}
if (a.x - center.x == 0 && b.x - center.x == 0) {
if (a.y - center.y >= 0 || b.y - center.y >= 0) {
return a.y > b.y;
}
return b.y > a.y;
}
// compute the cross product of vectors (center -> a) x (center -> b)
real_t det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
if (det < 0.0) {
return true;
}
if (det > 0.0) {
return false;
}
// points a and b are on the same line from the center
// check which point is closer to the center
real_t d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
real_t d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
return d1 > d2;
}
};
int npoints = r_verts.size();
if (!npoints) {
return;
}
// first calculate center
Vector2 center;
for (int n = 0; n < npoints; n++) {
center += r_verts[n];
}
center /= npoints;
SortArray<Vector2, ElementComparator> sorter;
sorter.compare.center = center;
sorter.sort(r_verts.ptrw(), r_verts.size());
// if not clockwise, reverse order
if (!p_clockwise) {
r_verts.invert();
}
}

View File

@ -1071,6 +1071,7 @@ public:
static PoolVector<Plane> build_box_planes(const Vector3 &p_extents); static PoolVector<Plane> build_box_planes(const Vector3 &p_extents);
static PoolVector<Plane> build_cylinder_planes(real_t p_radius, real_t p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z); static PoolVector<Plane> build_cylinder_planes(real_t p_radius, real_t p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z);
static PoolVector<Plane> build_capsule_planes(real_t p_radius, real_t p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z); static PoolVector<Plane> build_capsule_planes(real_t p_radius, real_t p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z);
static void sort_polygon_winding(Vector<Vector2> &r_verts, bool p_clockwise = true);
static void make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size); static void make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size);
@ -1081,7 +1082,9 @@ public:
}; };
static Vector<PackRectsResult> partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size); static Vector<PackRectsResult> partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size);
static Vector<Vector3> compute_convex_mesh_points(const Plane *p_planes, int p_plane_count); static Vector<Vector3> compute_convex_mesh_points(const Plane *p_planes, int p_plane_count, real_t p_epsilon = CMP_EPSILON);
static bool convex_hull_intersects_convex_hull(const Plane *p_planes_a, int p_plane_count_a, const Plane *p_planes_b, int p_plane_count_b);
static real_t calculate_convex_hull_volume(const Geometry::MeshData &p_md);
private: private:
static Vector<Vector<Point2>> _polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open = false); static Vector<Vector<Point2>> _polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open = false);

View File

@ -33,18 +33,13 @@
#include "core/map.h" #include "core/map.h"
uint32_t QuickHull::debug_stop_after = 0xFFFFFFFF; uint32_t QuickHull::debug_stop_after = 0xFFFFFFFF;
bool QuickHull::_flag_warnings = true;
Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_mesh) { Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_mesh, real_t p_over_tolerance_epsilon) {
/* CREATE AABB VOLUME */ /* CREATE AABB VOLUME */
AABB aabb; AABB aabb;
for (int i = 0; i < p_points.size(); i++) { aabb.create_from_points(p_points);
if (i == 0) {
aabb.position = p_points[i];
} else {
aabb.expand_to(p_points[i]);
}
}
if (aabb.size == Vector3()) { if (aabb.size == Vector3()) {
return ERR_CANT_CREATE; return ERR_CANT_CREATE;
@ -171,7 +166,7 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_me
faces.push_back(f); faces.push_back(f);
} }
real_t over_tolerance = 3 * UNIT_EPSILON * (aabb.size.x + aabb.size.y + aabb.size.z); real_t over_tolerance = p_over_tolerance_epsilon * (aabb.size.x + aabb.size.y + aabb.size.z);
/* COMPUTE AVAILABLE VERTICES */ /* COMPUTE AVAILABLE VERTICES */
@ -366,6 +361,10 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_me
//fill faces //fill faces
bool warning_f = false;
bool warning_o_equal_e = false;
bool warning_o = false;
for (List<Geometry::MeshData::Face>::Element *E = ret_faces.front(); E; E = E->next()) { for (List<Geometry::MeshData::Face>::Element *E = ret_faces.front(); E; E = E->next()) {
Geometry::MeshData::Face &f = E->get(); Geometry::MeshData::Face &f = E->get();
@ -376,10 +375,20 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_me
Map<Edge, RetFaceConnect>::Element *F = ret_edges.find(e); Map<Edge, RetFaceConnect>::Element *F = ret_edges.find(e);
ERR_CONTINUE(!F); if (unlikely(!F)) {
warning_f = true;
continue;
}
List<Geometry::MeshData::Face>::Element *O = F->get().left == E ? F->get().right : F->get().left; List<Geometry::MeshData::Face>::Element *O = F->get().left == E ? F->get().right : F->get().left;
ERR_CONTINUE(O == E); if (unlikely(O == E)) {
ERR_CONTINUE(O == nullptr); warning_o_equal_e = true;
continue;
}
if (unlikely(!O)) {
warning_o = true;
continue;
}
if (O->get().plane.is_equal_approx(f.plane)) { if (O->get().plane.is_equal_approx(f.plane)) {
//merge and delete edge and contiguous face, while repointing edges (uuugh!) //merge and delete edge and contiguous face, while repointing edges (uuugh!)
@ -434,6 +443,18 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_me
} }
} }
if (_flag_warnings) {
if (warning_f) {
WARN_PRINT("QuickHull : !F");
}
if (warning_o_equal_e) {
WARN_PRINT("QuickHull : O == E");
}
if (warning_o) {
WARN_PRINT("QuickHull : O == nullptr");
}
}
//fill mesh //fill mesh
r_mesh.faces.clear(); r_mesh.faces.clear();
r_mesh.faces.resize(ret_faces.size()); r_mesh.faces.resize(ret_faces.size());
@ -451,7 +472,28 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_me
r_mesh.edges.write[idx++] = e; r_mesh.edges.write[idx++] = e;
} }
r_mesh.vertices = p_points; // we are only interested in outputting the points that are used
Vector<int> out_indices;
for (int n = 0; n < r_mesh.faces.size(); n++) {
Geometry::MeshData::Face face = r_mesh.faces[n];
for (int i = 0; i < face.indices.size(); i++) {
face.indices.set(i, find_or_create_output_index(face.indices[i], out_indices));
}
r_mesh.faces.set(n, face);
}
for (int n = 0; n < r_mesh.edges.size(); n++) {
Geometry::MeshData::Edge e = r_mesh.edges[n];
e.a = find_or_create_output_index(e.a, out_indices);
e.b = find_or_create_output_index(e.b, out_indices);
r_mesh.edges.set(n, e);
}
// rejig the final vertices
r_mesh.vertices.resize(out_indices.size());
for (int n = 0; n < out_indices.size(); n++) {
r_mesh.vertices.set(n, p_points[out_indices[n]]);
}
return OK; return OK;
} }

View File

@ -84,9 +84,20 @@ private:
} }
}; };
static int find_or_create_output_index(int p_old_index, Vector<int> &r_out_indices) {
for (int n = 0; n < r_out_indices.size(); n++) {
if (r_out_indices[n] == p_old_index) {
return n;
}
}
r_out_indices.push_back(p_old_index);
return r_out_indices.size() - 1;
}
public: public:
static uint32_t debug_stop_after; static uint32_t debug_stop_after;
static Error build(const Vector<Vector3> &p_points, Geometry::MeshData &r_mesh); static bool _flag_warnings;
static Error build(const Vector<Vector3> &p_points, Geometry::MeshData &r_mesh, real_t p_over_tolerance_epsilon = 3.0 * UNIT_EPSILON);
}; };
#endif // QUICK_HULL_H #endif // QUICK_HULL_H

View File

@ -129,6 +129,7 @@ struct Vector3 {
_FORCE_INLINE_ Vector3 reflect(const Vector3 &p_normal) const; _FORCE_INLINE_ Vector3 reflect(const Vector3 &p_normal) const;
bool is_equal_approx(const Vector3 &p_v) const; bool is_equal_approx(const Vector3 &p_v) const;
inline bool is_equal_approx(const Vector3 &p_v, real_t p_tolerance) const;
/* Operators */ /* Operators */
@ -451,4 +452,8 @@ Vector3 Vector3::reflect(const Vector3 &p_normal) const {
return 2.0 * p_normal * this->dot(p_normal) - *this; return 2.0 * p_normal * this->dot(p_normal) - *this;
} }
bool Vector3::is_equal_approx(const Vector3 &p_v, real_t p_tolerance) const {
return Math::is_equal_approx(x, p_v.x, p_tolerance) && Math::is_equal_approx(y, p_v.y, p_tolerance) && Math::is_equal_approx(z, p_v.z, p_tolerance);
}
#endif // VECTOR3_H #endif // VECTOR3_H

View File

@ -678,6 +678,7 @@ public:
void call_multilevel(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper void call_multilevel(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper
void notification(int p_notification, bool p_reversed = false); void notification(int p_notification, bool p_reversed = false);
virtual void notification_callback(int p_message_type) {}
virtual String to_string(); virtual String to_string();
//used mainly by script, get and set all INCLUDING string //used mainly by script, get and set all INCLUDING string

View File

@ -93,3 +93,74 @@ public:
_used_size--; _used_size--;
} }
}; };
// a pooled list which automatically keeps a list of the active members
template <class T, bool force_trivial = false>
class TrackedPooledList {
public:
int pool_size() const { return _pool.size(); }
int active_size() const { return _active_list.size(); }
uint32_t get_active_id(uint32_t p_index) const {
return _active_list[p_index];
}
const T &get_active(uint32_t p_index) const {
return _pool[get_active_id(p_index)];
}
T &get_active(uint32_t p_index) {
return _pool[get_active_id(p_index)];
}
const T &operator[](uint32_t p_index) const {
return _pool[p_index];
}
T &operator[](uint32_t p_index) {
return _pool[p_index];
}
T *request(uint32_t &r_id) {
T *item = _pool.request(r_id);
// add to the active list
uint32_t active_list_id = _active_list.size();
_active_list.push_back(r_id);
// expand the active map (this should be in sync with the pool list
if (_pool.size() > (int)_active_map.size()) {
_active_map.resize(_pool.size());
}
// store in the active map
_active_map[r_id] = active_list_id;
return item;
}
void free(const uint32_t &p_id) {
_pool.free(p_id);
// remove from the active list.
uint32_t list_id = _active_map[p_id];
// zero the _active map to detect bugs (only in debug?)
_active_map[p_id] = -1;
_active_list.remove_unordered(list_id);
// keep the replacement in sync with the correct list Id
if (list_id < (uint32_t)_active_list.size()) {
// which pool id has been replaced in the active list
uint32_t replacement_id = _active_list[list_id];
// keep that replacements map up to date with the new position
_active_map[replacement_id] = list_id;
}
}
private:
PooledList<T, force_trivial> _pool;
LocalVector<uint32_t, uint32_t> _active_map;
LocalVector<uint32_t, uint32_t> _active_list;
};

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CullInstance" inherits="Spatial" version="3.4">
<brief_description>
Parent of all nodes that can be culled by the Portal system.
</brief_description>
<description>
Provides common functionality to nodes that can be culled by the [Portal] system.
[code]Static[/code] and [code]Dynamic[/code] objects are the most efficiently managed objects in the system, but there are some caveats. They are expected to be present initially when [Room]s are converted using the [RoomManager] [code]rooms_convert[/code] function, and their lifetime should be the same as the game level (i.e. present until you call [code]rooms_clear[/code] on the [RoomManager]. Although you shouldn't create / delete these objects during gameplay, you can manage their visibility with the standard [code]hide[/code] and [code]show[/code] commands.
[code]Roaming[/code] objects on the other hand, require extra processing to keep track of which [Room] they are within. This enables them to be culled effectively, wherever they are.
[code]Global[/code] objects are not culled by the portal system, and use view frustum culling only.
Objects that are not [code]Static[/code] or [code]Dynamic[/code] can be freely created and deleted during the lifetime of the game level.
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="include_in_bound" type="bool" setter="set_include_in_bound" getter="get_include_in_bound" default="true">
When a manual bound has not been explicitly specified for a [Room], the convex hull bound will be estimated from the geometry of the objects within the room. This setting determines whether the geometry of an object is included in this estimate of the room bound.
[b]Note:[/b] This setting is only relevant when the object is set to [code]PORTAL_MODE_STATIC[/code] or [code]PORTAL_MODE_DYNAMIC[/code], and for [Portal]s.
</member>
<member name="portal_mode" type="int" setter="set_portal_mode" getter="get_portal_mode" enum="CullInstance.PortalMode" default="0">
When using [Room]s and [Portal]s, this specifies how the [CullInstance] is processed in the system.
</member>
</members>
<constants>
<constant name="PORTAL_MODE_STATIC" value="0" enum="PortalMode">
Use for instances within [Room]s that will [b]not move[/b] - e.g. walls, floors.
[b]Note:[/b] If you attempt to delete a [code]PORTAL_MODE_STATIC[/code] instance while the room graph is loaded (converted), it will unload the room graph and deactivate portal culling. This is because the [b]room graph[/b] data has been invalidated. You will need to reconvert the rooms using the [RoomManager] to activate the system again.
</constant>
<constant name="PORTAL_MODE_DYNAMIC" value="1" enum="PortalMode">
Use for instances within rooms that will move but [b]not change room[/b] - e.g. moving platforms.
[b]Note:[/b] If you attempt to delete a [code]PORTAL_MODE_DYNAMIC[/code] instance while the room graph is loaded (converted), it will unload the room graph and deactivate portal culling. This is because the [b]room graph[/b] data has been invalidated. You will need to reconvert the rooms using the [RoomManager] to activate the system again.
</constant>
<constant name="PORTAL_MODE_ROAMING" value="2" enum="PortalMode">
Use for instances that will move [b]between[/b] [Room]s - e.g. players.
</constant>
<constant name="PORTAL_MODE_GLOBAL" value="3" enum="PortalMode">
Use for instances that will be frustum culled only - e.g. first person weapon, debug.
</constant>
<constant name="PORTAL_MODE_IGNORE" value="4" enum="PortalMode">
Use for instances that will not be shown at all - e.g. [b]manual room bounds[/b] (specified by prefix [i]'Bound_'[/i]).
</constant>
</constants>
</class>

41
doc/classes/Portal.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="Portal" inherits="Spatial" version="3.4">
<brief_description>
Portal nodes are used to enable visibility between [Room]s.
</brief_description>
<description>
[Portal]s are a special type of [MeshInstance] that allow the portal culling system to 'see' from one room to the next. They often correspond to doors and windows in level geometry. By only allowing [Camera]s to see through portals, this allows the system to cull out all the objects in rooms that cannot be seen through portals. This is a form of [b]occlusion culling[/b], and can greatly increase performance.
There are some limitations to the form of portals:
They must be single sided convex polygons, and usually you would orientate their front faces [b]outward[/b] from the [Room] they are placed in. The vertices should be positioned on a single plane (although their positioning does not have to be perfect).
There is no need to place an opposite portal in an adjacent room, links are made two-way automatically.
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="linked_room" type="NodePath" setter="set_linked_room" getter="get_linked_room" default="NodePath(&quot;&quot;)">
This is a shortcut for setting the linked [Room] in the name of the [Portal] (the name is used during conversion).
</member>
<member name="points" type="PoolVector2Array" setter="set_points" getter="get_points" default="PoolVector2Array( 1, -1, 1, 1, -1, 1, -1, -1 )">
The points defining the shape of the [Portal] polygon (which should be convex).
These are defined in 2D, with [code]0,0[/code] being the origin of the [Portal] node's [code]global_transform[/code].
[code]Note:[/code] These raw points are sanitized for winding order internally.
</member>
<member name="portal_active" type="bool" setter="set_portal_active" getter="get_portal_active" default="true">
Visibility through [Portal]s can be turned on and off at runtime - this is useful for having closable doors.
</member>
<member name="portal_margin" type="float" setter="set_portal_margin" getter="get_portal_margin" default="1.0">
Some objects are so big that they may be present in more than one [Room] ('sprawling'). As we often don't want objects that *just* breach the edges to be assigned to neighbouring rooms, you can assign an extra margin through the [Portal] to allow objects to breach without sprawling.
</member>
<member name="two_way" type="bool" setter="set_two_way" getter="is_two_way" default="true">
Portals default to being two way - see through in both directions, however you can make them one way, visible from the source room only.
</member>
<member name="use_default_margin" type="bool" setter="set_use_default_margin" getter="get_use_default_margin" default="true">
In most cases you will want to use the default [Portal] margin in your portals (this is set in the [RoomManager]).
If you want to override this default, set this value to [code]false[/code], and the local [code]portal_margin[/code] will take effect.
</member>
</members>
<constants>
</constants>
</class>

31
doc/classes/Room.xml Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="Room" inherits="Spatial" version="3.4">
<brief_description>
Room node, used to group objects together locally for [Portal] culling.
</brief_description>
<description>
The [Portal] culling system requires levels to be built using objects grouped together by location in areas called [Room]s. In many cases these will correspond to actual rooms in buildings, but not necessarily (a canyon area may be treated as a room).
Any [VisualInstance] that is a child or grandchild of a [Room] will be assigned to that room, if the [code]portal_mode[/code] of that [VisualInstance] is set to [code]STATIC[/code] (does not move) or [code]DYNAMIC[/code] (moves only within the room).
Internally the room boundary must form a [b]convex hull[/b], and by default this is determined automatically by the geometry of the objects you place within the room.
You can alternatively precisely specify a [b]manual bound[/b]. If you place a [MeshInstance] with a name prefixed by [code]Bound_[/code], it will turn off the bound generation from geometry, and instead use the vertices of this MeshInstance to directly calculate a convex hull during the conversion stage (see [RoomManager]).
In order to see from one room into an adjacent room, [Portal]s must be placed over non-occluded openings between rooms. These will often be placed over doors and windows.
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="points" type="PoolVector3Array" setter="set_points" getter="get_points" default="PoolVector3Array( )">
If [code]points[/code] are set, the [Room] bounding convex hull will be built from these points. If no points are set, the room bound will either be derived from a manual bound ([MeshInstance] with name prefix [code]Bound_[/code]), or from the geometry within the room.
Note that you can use the [code]Generate Points[/code] editor button to get started. This will use either the geometry or manual bound to generate the room hull, and save the resulting points, allowing you to edit them to further refine the bound.
</member>
<member name="room_simplify" type="float" setter="set_room_simplify" getter="get_room_simplify" default="0.5">
The [code]simplify[/code] value determines to what degree room hulls (bounds) are simplified, by removing similar planes. A value of 0 gives no simplification, 1 gives maximum simplification.
</member>
<member name="use_default_simplify" type="bool" setter="set_use_default_simplify" getter="get_use_default_simplify" default="true">
The room hull simplification can either use the default value set in the [RoomManager], or override this and use the per room setting.
</member>
</members>
<constants>
</constants>
</class>

24
doc/classes/RoomGroup.xml Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="RoomGroup" inherits="Spatial" version="3.4">
<brief_description>
Groups [Room]s together to allow common functionality.
</brief_description>
<description>
Although [Room] behaviour can be specified individually, sometimes it is faster and more convenient to write functionality for a group of rooms.
[RoomGroup]s should be placed as children of the [b]room list[/b] (the parent [Node] of your [Room]s), and [Room]s should be placed in turn as children of a [RoomGroup] in order to assign them to the RoomGroup.
A [RoomGroup] can for example be used to specify [Room]s that are [b]outside[/b], and switch on or off a directional light, sky, or rain effect as the player enters / exits the area.
[RoomGroup]s receive [b]gameplay callbacks[/b] when the [code]gameplay_monitor[/code] is switched on, as [code]signal[/code]s or [code]notification[/code]s as they enter and exit the [b]gameplay area[/b] (see [RoomManager] for details).
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="roomgroup_priority" type="int" setter="set_roomgroup_priority" getter="get_roomgroup_priority" default="0">
This priority will be applied to [Room]s within the group. The [Room] priority allows the use of [b]internal rooms[/b], rooms [i]within[/i] another room or rooms.
When the [Camera] is within more than one room (regular and internal), the higher priority room will take precedence. So with for example, a house inside a terrain 'room', you would make the house higher priority, so that when the camera is within the house, the house is used as the source room, but outside the house, the terrain room would be used instead.
</member>
</members>
<constants>
</constants>
</class>

123
doc/classes/RoomManager.xml Normal file
View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="RoomManager" inherits="Spatial" version="3.4">
<brief_description>
The RoomManager node is used to control the portal culling system.
</brief_description>
<description>
In order to utilize the portal occlusion culling system, you must build your level using [Room]s and [Portal]s. Before these can be used at runtime, they must undergo a short conversion process to build the [code]room graph[/code], runtime data needed for portal culling. The [code]room graph[/code] is controlled by the [RoomManager] node, and the [RoomManager] also contains settings that are common throughout the portal system.
</description>
<tutorials>
</tutorials>
<methods>
<method name="rooms_clear">
<return type="void">
</return>
<description>
This function clears all converted data from the [b]room graph[/b]. Use this before unloading a level, when transitioning from level to level, or returning to a main menu.
</description>
</method>
<method name="rooms_convert">
<return type="void">
</return>
<description>
This is the most important function in the whole portal culling system. Without it, the system cannot function.
First it goes through every [Room] that is a child of the [code]room list[/code] node (and [RoomGroup]s within) and converts and adds it to the [code]room graph[/code].
This works for both [Room] nodes, and [Spatial] nodes that follow a special naming convention. They should begin with the prefix [i]'Room_'[/i], followed by the name you wish to give the room, e.g. [i]'Room_lounge'[/i]. This will automatically convert such [Spatial]s to [Room] nodes for you. This is useful if you want to build you entire room system in e.g. Blender, and reimport multiple times as you work on the level.
The conversion will try to assign [VisualInstance]s that are children and grandchildren of the [Room] to the room. These should be given a suitable [code]portal mode[/code] (see the [CullInstance] documentation). The default [code]portal mode[/code] is [code]STATIC[/code] - objects which are not expected to move while the level is played, which will typically be most objects.
The conversion will usually use the geometry of these [VisualInstance]s (and the [Portal]s) to calculate a convex hull bound for the room. These bounds will be shown in the editor with a wireframe. Alternatively you can specify a manual custom bound for any room, see the [Room] documentation.
By definition, [Camera]s within a room can see everything else within the room (that is one advantage to using convex hulls). However, in order to see from one room into adjacent rooms, you must place [Portal]s, which represent openings that the camera can see through, like windows and doors.
[Portal]s are really just specialized [MeshInstance]s. In fact you will usually first create a portal by creating a [MeshInstance], especially a [code]plane[/code] mesh instance. You would move the plane in the editor to cover a window or doorway, with the front face pointing outward from the room. To let the conversion process know you want this mesh to be a portal, again we use a special naming convention. [MeshInstance]s to be converted to a [Portal] should start with the prefix [i]'Portal_'[/i].
You now have a choice - you can leave the name as [i]'Portal_'[/i] and allow the system to automatically detect the nearest [Room] to link. In most cases this will work fine.
An alternative method is to specify the [Room] to link to manually, appending a suffix to the portal name, which should be the name of the room you intend to link to. For example [i]'Portal_lounge'[/i] will attempt to link to the room named [i]'Room_lounge'[/i].
There is a special case here - Godot does not allow two nodes to share the same name. What if you want to manually have more than one portal leading into the same room? Surely they will need to both be called, e.g. [i]'Portal_lounge'[/i]?
The solution is a wildcard character. After the room name, if you use the character [i]'*'[/i], this character and anything following it will be ignored. So you can use for example [i]'Portal_lounge*0'[/i], [i]'Portal_lounge*1'[/i] etc.
Note that [Portal]s that have already been converted to [Portal] nodes (rather than [MeshInstance]s) still need to follow the same naming convention, as they will be relinked each time during conversion.
It is recommended that you only place objects in rooms that are desired to stay within those rooms - i.e. [code]portal mode[/code]s [code]STATIC[/code] or [code]DYNAMIC[/code] (not crossing portals). [code]GLOBAL[/code] and [code]ROAMING[/code] objects are best placed in another part of the scene tree, to avoid confusion. See [CullInstance] for a full description of portal modes.
</description>
</method>
</methods>
<members>
<member name="active" type="bool" setter="rooms_set_active" getter="rooms_get_active" default="true">
Switches the portal culling system on and off.
It is important to note that when portal culling is active, it is responsible for [b]all[/b] the 3d culling. Some editor functionality may be more difficult to use, so switching the active flag is intended to be used to make sure your [Room] / [Portal] layout works within the editor.
Switching to [code]active[/code] will have no effect when the [code]room graph[/code] is unloaded (the rooms have not yet been converted).
</member>
<member name="debug_sprawl" type="bool" setter="set_debug_sprawl" getter="get_debug_sprawl" default="false">
Large objects can 'sprawl' over (be present in) more than one room. It can be useful to visualize which objects are sprawling outside the current room.
Toggling this setting turns this debug view on and off.
</member>
<member name="default_portal_margin" type="float" setter="set_default_portal_margin" getter="get_default_portal_margin" default="1.0">
Usually we don't want objects that only [b]just[/b] cross a boundary into an adjacent [Room] to sprawl into that room. To prevent this, each [Portal] has an extra margin, or tolerance zone where objects can enter without sprawling to a neighbouring room.
In most cases you can set this here for all portals. It is possible to override the margin for each portal.
</member>
<member name="flip_portal_meshes" type="bool" setter="set_flip_portal_meshes" getter="get_flip_portal_meshes" default="false">
The default convention is for portal normals to point outward (face outward) from the source room. If you accidentally build your level with portals facing the wrong way, this setting can fix the problem. It will flip named portal meshes (i.e. [code]Portal_[/code]) on the initial convertion to [Portal] nodes.
</member>
<member name="gameplay_monitor" type="bool" setter="set_gameplay_monitor_enabled" getter="get_gameplay_monitor_enabled" default="false">
When using a partial or full PVS, the gameplay monitor allows you to receive callbacks when roaming objects or rooms enter or exit the [b]gameplay area[/b]. The gameplay area is defined as either the primary, or secondary PVS.
These callbacks allow you to, for example, reduce processing for objects that are far from the player, or turn on and off AI.
You can either choose to receive callbacks as notifications through the [code]_notification[/code] function, or as signals.
[code]NOTIFICATION_ENTER_GAMEPLAY[/code]
[code]NOTIFICATION_EXIT_GAMEPLAY[/code]
Signals: [code]"gameplay_entered"[/code], [code]"gameplay_exited"[/code]
</member>
<member name="merge_meshes" type="bool" setter="set_merge_meshes" getter="get_merge_meshes" default="false">
If enabled, the system will attempt to merge similar meshes (particularly in terms of materials) within [Room]s during conversion. This can significantly reduce the number of drawcalls and state changes required during rendering, albeit at a cost of reduced culling granularity.
[b]Note:[/b] This operates at runtime during the conversion process, and will only operate on exported or running projects, in order to prevent accidental alteration to the scene and loss of data.
</member>
<member name="overlap_warning_threshold" type="int" setter="set_overlap_warning_threshold" getter="get_overlap_warning_threshold" default="1">
When converting rooms, the editor will warn you if overlap is detected between rooms. Overlap can interfere with determining the room that cameras and objects are within. A small amount can be acceptable, depending on your level. Here you can alter the threshold at which the editor warning appears. There are no other side effects.
</member>
<member name="portal_depth_limit" type="int" setter="set_portal_depth_limit" getter="get_portal_depth_limit" default="16">
Portal rendering is recursive - each time a portal is seen through an earlier portal there is some cost. For this reason, and to prevent the possibility of infinite loops, this setting provides a hard limit on the recursion depth.
[b]Note:[/b] This value is unused when using [code]Full[/code] PVS mode.
</member>
<member name="preview_camera" type="NodePath" setter="set_preview_camera_path" getter="get_preview_camera_path" default="NodePath(&quot;&quot;)">
Portal culling normally operates using the current [Camera] / [Camera]s, however for debugging purposes within the editor, you can use this setting to override this behaviour and force it to use a particular camera to get a better idea of what the occlusion culling is doing.
</member>
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" override="true" default="10000" />
<member name="pvs_mode" type="int" setter="set_pvs_mode" getter="get_pvs_mode" enum="RoomManager.PVSMode" default="1">
Optionally during conversion the potentially visible set (PVS) of rooms that are potentially visible from each room can be calculated. This can be used either to aid in dynamic portal culling, or to totally replace portal culling.
In [code]Full[/code] PVS Mode, all objects within the potentially visible rooms will be frustum culled, and rendered if they are within the view frustum.
</member>
<member name="remove_danglers" type="bool" setter="set_remove_danglers" getter="get_remove_danglers" default="true">
If enabled, while merging meshes, the system will also attempt to remove [Spatial] nodes that no longer have any children.
Reducing the number of [Node]s in the scene tree can make traversal more efficient, but can be switched off in case you wish to use empty [Spatial]s for markers or some other purpose.
</member>
<member name="room_simplify" type="float" setter="set_room_simplify" getter="get_room_simplify" default="0.5">
During the conversion process, the geometry of objects within [Room]s, or a custom specified manual bound, are used to generate a [b]convex hull bound[/b].
This convex hull is [b]required[/b] in the visibility system, and is used for many purposes. Most importantly, it is used to decide whether the [Camera] (or an object) is within a [Room]. The convex hull generating algorithm is good, but occasionally it can create too many (or too few) planes to give a good representation of the room volume.
The [code]room_simplify[/code] value can be used to gain fine control over this process. It determines how similar planes can be for them to be considered the same (and duplicates removed). The value can be set between 0 (no simplification) and 1 (maximum simplification).
The value set here is the default for all rooms, but individual rooms can override this value if desired.
The room convex hulls are shown as a wireframe in the editor.
</member>
<member name="roomlist" type="NodePath" setter="set_roomlist_path" getter="get_roomlist_path" default="NodePath(&quot;&quot;)">
For the [Room] conversion process to succeed, you must point the [RoomManager] to the parent [Node] of your [Room]s and [RoomGroup]s, which we refer to as the [code]roomlist[/code] (the roomlist is not a special node type, it is normally just a [Spatial]).
</member>
<member name="show_debug" type="bool" setter="set_show_debug" getter="get_show_debug" default="true">
Show debugging information - including [Portal]s, and conversion logs.
[b]Note:[/b] This will automatically be disabled in exports.
</member>
<member name="show_margins" type="bool" setter="set_show_margins" getter="get_show_margins" default="true">
Shows the [Portal] margins when the portal gizmo is used in the editor.
</member>
<member name="use_secondary_pvs" type="bool" setter="set_use_secondary_pvs" getter="get_use_secondary_pvs" default="false">
When receiving gameplay callbacks when objects enter and exit gameplay, the [b]gameplay area[/b] can be defined by either the primary PVS (potentially visible set) of [Room]s, or the secondary PVS (the primary PVS and their neighbouring [Room]s).
Sometimes using the larger gameplay area of the secondary PVS may be preferable.
</member>
<member name="use_signals" type="bool" setter="set_use_signals" getter="get_use_signals" default="true">
Gameplay callbacks can either be sent as [code]signals[/code] or [code]notifications[/code].
</member>
</members>
<constants>
<constant name="RoomManager::PVS_MODE_DISABLED" value="0" enum="PVSMode">
Use only [Portal]s at runtime to determine visibility. PVS will not be generated at [Room]s conversion, and gameplay notifications cannot be used.
</constant>
<constant name="RoomManager::PVS_MODE_PARTIAL" value="1" enum="PVSMode">
Use a combination of PVS and [Portal]s to determine visibility (this is usually fastest and most accurate).
</constant>
<constant name="RoomManager::PVS_MODE_FULL" value="2" enum="PVSMode">
Use only the PVS (potentially visible set) of [Room]s to determine visibility.
</constant>
</constants>
</class>

View File

@ -328,6 +328,16 @@
</member> </member>
</members> </members>
<signals> <signals>
<signal name="gameplay_entered">
<description>
Emitted by portal system gameplay monitor when a node enters the gameplay area.
</description>
</signal>
<signal name="gameplay_exited">
<description>
Emitted by portal system gameplay monitor when a node exits the gameplay area.
</description>
</signal>
<signal name="visibility_changed"> <signal name="visibility_changed">
<description> <description>
Emitted when node visibility changes. Emitted when node visibility changes.
@ -348,5 +358,11 @@
<constant name="NOTIFICATION_VISIBILITY_CHANGED" value="43"> <constant name="NOTIFICATION_VISIBILITY_CHANGED" value="43">
Spatial nodes receives this notification when their visibility changes. Spatial nodes receives this notification when their visibility changes.
</constant> </constant>
<constant name="NOTIFICATION_ENTER_GAMEPLAY" value="45">
Spatial nodes receives this notification if the portal system gameplay monitor detects they have entered the gameplay area.
</constant>
<constant name="NOTIFICATION_EXIT_GAMEPLAY" value="46">
Spatial nodes receives this notification if the portal system gameplay monitor detects they have exited the gameplay area.
</constant>
</constants> </constants>
</class> </class>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<class name="VisibilityNotifier" inherits="Spatial" version="3.4"> <class name="VisibilityNotifier" inherits="CullInstance" version="3.4">
<brief_description> <brief_description>
Detects approximately when the node is visible on screen. Detects approximately when the node is visible on screen.
</brief_description> </brief_description>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<class name="VisualInstance" inherits="Spatial" version="3.4"> <class name="VisualInstance" inherits="CullInstance" version="3.4">
<brief_description> <brief_description>
Parent of all visual 3D nodes. Parent of all visual 3D nodes.
</brief_description> </brief_description>

View File

@ -142,6 +142,7 @@
#include "editor/plugins/physical_bone_plugin.h" #include "editor/plugins/physical_bone_plugin.h"
#include "editor/plugins/polygon_2d_editor_plugin.h" #include "editor/plugins/polygon_2d_editor_plugin.h"
#include "editor/plugins/resource_preloader_editor_plugin.h" #include "editor/plugins/resource_preloader_editor_plugin.h"
#include "editor/plugins/room_manager_editor_plugin.h"
#include "editor/plugins/root_motion_editor_plugin.h" #include "editor/plugins/root_motion_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h"
#include "editor/plugins/script_text_editor.h" #include "editor/plugins/script_text_editor.h"
@ -6798,6 +6799,8 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(Particles2DEditorPlugin(this))); add_editor_plugin(memnew(Particles2DEditorPlugin(this)));
add_editor_plugin(memnew(GIProbeEditorPlugin(this))); add_editor_plugin(memnew(GIProbeEditorPlugin(this)));
add_editor_plugin(memnew(BakedLightmapEditorPlugin(this))); add_editor_plugin(memnew(BakedLightmapEditorPlugin(this)));
add_editor_plugin(memnew(RoomManagerEditorPlugin(this)));
add_editor_plugin(memnew(RoomEditorPlugin(this)));
add_editor_plugin(memnew(Path2DEditorPlugin(this))); add_editor_plugin(memnew(Path2DEditorPlugin(this)));
add_editor_plugin(memnew(PathEditorPlugin(this))); add_editor_plugin(memnew(PathEditorPlugin(this)));
add_editor_plugin(memnew(Line2DEditorPlugin(this))); add_editor_plugin(memnew(Line2DEditorPlugin(this)));

View File

@ -0,0 +1 @@
<svg height="128" viewBox="0 0 128 128" width="128" xmlns="http://www.w3.org/2000/svg"><path d="m64 8.0195312c-12.43753 0-23.589964 6.6011958-31.417969 16.7871098-7.828004 10.185914-12.529297 24.008895-12.529297 39.193359-.000001 15.184465 4.701292 29.007444 12.529297 39.19336 7.828005 10.18591 18.980438 16.78711 31.417969 16.78711 12.437533 0 23.589962-6.6012 31.417969-16.78711 7.828011-10.185916 12.529301-24.008893 12.529301-39.19336 0-15.184466-4.70129-29.007445-12.529301-39.193359-7.828007-10.185915-18.980437-16.787113-31.417969-16.7871098zm0 22.8515628c4.94429-.000001 9.753307 3.104902 13.587891 9.044922 3.834584 5.940019 6.380859 14.529037 6.380859 24.083984 0 9.554948-2.546275 18.143964-6.380859 24.083984-3.834585 5.940021-8.6436 9.044923-13.587891 9.044922-4.94429 0-9.753307-3.104901-13.587891-9.044922-3.834584-5.94002-6.380859-14.529037-6.380859-24.083984 0-9.554946 2.546276-18.143965 6.380859-24.083984 3.834584-5.94002 8.643601-9.044922 13.587891-9.044922z" fill-opacity=".294118"/><path d="m64.000002 12.020316c-22.062147 0-39.94705 23.272097-39.94705 51.979684-.000003 28.707589 17.884902 51.97969 39.94705 51.97969 22.062151.00001 39.947058-23.272097 39.947058-51.97969 0-28.707591-17.884908-51.97969-39.947058-51.979684zm0 14.85134c13.237288-.000001 23.96823 16.622925 23.968231 37.128344.000001 20.505422-10.730942 37.12835-23.968231 37.12835-13.237288 0-23.96823-16.62293-23.968229-37.12835.000001-20.505418 10.730942-37.128343 23.968229-37.128344z" fill="#f7f5cf"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc9c9c" fill-opacity=".99608" fill-rule="evenodd" stroke-width=".428588"><path d="m3.9841079 1.0003375a.4286146.4286463 0 0 0 -.1757831.04437l-2.5714304 1.2858095a.4286146.4286463 0 0 0 -.2368844.3833985v2.5716206a.4286146.4286463 0 0 0 .2368844.3833988l2.5714304 1.2858102a.4286146.4286463 0 0 0 .3833703 0l2.5714305-1.2858102a.4286146.4286463 0 0 0 .2368844-.3833988v-2.5716206a.4286146.4286463 0 0 0 -.2368844-.3833985l-2.5714305-1.2858104a.4286146.4286463 0 0 0 -.2075916-.0443691zm.4444717 1.121741 1.7142869.8572068v1.6131347l-1.7142869-.8572067zm-.4285717 2.3573191 1.6130154.8061599-1.6130154.8069746-1.6130156-.8069746z"/><path d="m11.9841 1.0003375a.4286146.4286463 0 0 0 -.175783.04437l-2.5714305 1.2858095a.4286146.4286463 0 0 0 -.2368844.3833985v2.5716206a.4286146.4286463 0 0 0 .2368844.3833988l2.5714305 1.2858102a.4286146.4286463 0 0 0 .38337 0l2.571431-1.2858102a.4286146.4286463 0 0 0 .236884-.3833988v-2.5716206a.4286146.4286463 0 0 0 -.236884-.3833985l-2.571431-1.2858104a.4286146.4286463 0 0 0 -.207591-.044369zm.444472 1.121741 1.714287.8572068v1.6131347l-1.714287-.8572067zm-.428572 2.3573191 1.613015.8061599-1.613015.8069746-1.613016-.8069746z"/><path d="m3.9841079 9.0003376a.4286146.4286463 0 0 0 -.1757831.04437l-2.5714304 1.2858094a.4286146.4286463 0 0 0 -.2368844.383399v2.57162a.4286146.4286463 0 0 0 .2368844.383399l2.5714304 1.28581a.4286146.4286463 0 0 0 .3833703 0l2.5714305-1.28581a.4286146.4286463 0 0 0 .2368844-.383399v-2.57162a.4286146.4286463 0 0 0 -.2368844-.383399l-2.5714305-1.2858103a.4286146.4286463 0 0 0 -.2075916-.0443691zm.4444717 1.1217414 1.7142869.857206v1.613135l-1.7142869-.857207zm-.4285717 2.357319 1.6130154.80616-1.6130154.806974-1.6130156-.806974z"/><path d="m11.9841 9.0003376a.4286146.4286463 0 0 0 -.175783.04437l-2.5714305 1.2858094a.4286146.4286463 0 0 0 -.2368844.383399v2.57162a.4286146.4286463 0 0 0 .2368844.383399l2.5714305 1.28581a.4286146.4286463 0 0 0 .38337 0l2.571431-1.28581a.4286146.4286463 0 0 0 .236884-.383399v-2.57162a.4286146.4286463 0 0 0 -.236884-.383399l-2.571431-1.2858103a.4286146.4286463 0 0 0 -.207591-.044369zm.444472 1.1217414 1.714287.857206v1.613135l-1.714287-.857207zm-.428572 2.357319 1.613015.80616-1.613015.806974-1.613016-.806974z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.9628906 1.0019531c-.1425373.00487-.2823815.0401577-.4101562.1035157l-6 3c-.3387788.1694423-.55276062.5157413-.5527344.8945312v6c-.00002622.37879.2139556.725089.5527344.894531l6 3c.2815593.140782.6129719.140782.8945312 0l6.0000004-3c.338779-.169442.55276-.515741.552734-.894531v-6c.000026-.3787899-.213955-.7250889-.552734-.8945312l-6.0000004-3c-.1502255-.0745765-.316789-.11017047-.484375-.1035157zm-3.9628906 2.9980469h1.7109375l2.2890625 2 2-2h2v8h-2v-5l-2 2-2-2v5h-2z" fill="#fc9c9c" fill-opacity=".996078"/></svg>

After

Width:  |  Height:  |  Size: 613 B

View File

@ -0,0 +1,172 @@
/*************************************************************************/
/* room_manager_editor_plugin.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 "room_manager_editor_plugin.h"
#include "editor/spatial_editor_gizmos.h"
void RoomManagerEditorPlugin::_rooms_convert() {
if (_room_manager) {
_room_manager->rooms_convert();
}
}
void RoomManagerEditorPlugin::_flip_portals() {
if (_room_manager) {
_room_manager->rooms_flip_portals();
}
}
void RoomManagerEditorPlugin::edit(Object *p_object) {
RoomManager *s = Object::cast_to<RoomManager>(p_object);
if (!s) {
return;
}
_room_manager = s;
}
bool RoomManagerEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("RoomManager");
}
void RoomManagerEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
button_rooms_convert->show();
button_flip_portals->show();
} else {
button_rooms_convert->hide();
button_flip_portals->hide();
}
}
void RoomManagerEditorPlugin::_bind_methods() {
ClassDB::bind_method("_rooms_convert", &RoomManagerEditorPlugin::_rooms_convert);
ClassDB::bind_method("_flip_portals", &RoomManagerEditorPlugin::_flip_portals);
}
RoomManagerEditorPlugin::RoomManagerEditorPlugin(EditorNode *p_node) {
editor = p_node;
button_flip_portals = memnew(ToolButton);
button_flip_portals->set_icon(editor->get_gui_base()->get_icon("Portal", "EditorIcons"));
button_flip_portals->set_text(TTR("Flip Portals"));
button_flip_portals->hide();
button_flip_portals->connect("pressed", this, "_flip_portals");
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, button_flip_portals);
button_rooms_convert = memnew(ToolButton);
button_rooms_convert->set_icon(editor->get_gui_base()->get_icon("RoomGroup", "EditorIcons"));
button_rooms_convert->set_text(TTR("Convert Rooms"));
button_rooms_convert->hide();
button_rooms_convert->connect("pressed", this, "_rooms_convert");
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, button_rooms_convert);
_room_manager = nullptr;
Ref<RoomGizmoPlugin> room_gizmo_plugin = Ref<RoomGizmoPlugin>(memnew(RoomGizmoPlugin));
SpatialEditor::get_singleton()->add_gizmo_plugin(room_gizmo_plugin);
Ref<PortalGizmoPlugin> portal_gizmo_plugin = Ref<PortalGizmoPlugin>(memnew(PortalGizmoPlugin));
SpatialEditor::get_singleton()->add_gizmo_plugin(portal_gizmo_plugin);
}
RoomManagerEditorPlugin::~RoomManagerEditorPlugin() {
}
///////////////////////
void RoomEditorPlugin::_generate_points() {
if (_room) {
PoolVector<Vector3> old_pts = _room->get_points();
// only generate points if none already exist
if (_room->_bound_pts.size()) {
_room->set_points(PoolVector<Vector3>());
}
PoolVector<Vector3> pts = _room->generate_points();
// allow the user to undo generating points, because it is
// frustrating to lose old data
undo_redo->create_action("generate_points");
undo_redo->add_do_property(_room, "points", pts);
undo_redo->add_undo_property(_room, "points", old_pts);
undo_redo->commit_action();
}
}
void RoomEditorPlugin::edit(Object *p_object) {
Room *s = Object::cast_to<Room>(p_object);
if (!s) {
return;
}
_room = s;
if (SpatialEditor::get_singleton()->is_visible() && s->_planes.size()) {
String string = String(s->get_name()) + " [" + itos(s->_planes.size()) + " planes]";
SpatialEditor::get_singleton()->set_message(string);
}
}
bool RoomEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Room");
}
void RoomEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
button_generate->show();
} else {
button_generate->hide();
}
}
void RoomEditorPlugin::_bind_methods() {
ClassDB::bind_method("_generate_points", &RoomEditorPlugin::_generate_points);
}
RoomEditorPlugin::RoomEditorPlugin(EditorNode *p_node) {
editor = p_node;
button_generate = memnew(ToolButton);
button_generate->set_icon(editor->get_gui_base()->get_icon("Room", "EditorIcons"));
button_generate->set_text(TTR("Generate Points"));
button_generate->hide();
button_generate->connect("pressed", this, "_generate_points");
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, button_generate);
_room = nullptr;
undo_redo = EditorNode::get_undo_redo();
}
RoomEditorPlugin::~RoomEditorPlugin() {
}

View File

@ -0,0 +1,92 @@
/*************************************************************************/
/* room_manager_editor_plugin.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef ROOM_MANAGER_EDITOR_PLUGIN_H
#define ROOM_MANAGER_EDITOR_PLUGIN_H
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "scene/3d/room.h"
#include "scene/3d/room_manager.h"
#include "scene/resources/material.h"
class RoomManagerEditorPlugin : public EditorPlugin {
GDCLASS(RoomManagerEditorPlugin, EditorPlugin);
RoomManager *_room_manager;
ToolButton *button_rooms_convert;
ToolButton *button_flip_portals;
EditorNode *editor;
void _rooms_convert();
void _flip_portals();
protected:
static void _bind_methods();
public:
virtual String get_name() const { return "RoomManager"; }
bool has_main_screen() const { return false; }
virtual void edit(Object *p_object);
virtual bool handles(Object *p_object) const;
virtual void make_visible(bool p_visible);
RoomManagerEditorPlugin(EditorNode *p_node);
~RoomManagerEditorPlugin();
};
///////////////////////
class RoomEditorPlugin : public EditorPlugin {
GDCLASS(RoomEditorPlugin, EditorPlugin);
Room *_room;
ToolButton *button_generate;
EditorNode *editor;
UndoRedo *undo_redo;
void _generate_points();
protected:
static void _bind_methods();
public:
virtual String get_name() const { return "Room"; }
bool has_main_screen() const { return false; }
virtual void edit(Object *p_object);
virtual bool handles(Object *p_object) const;
virtual void make_visible(bool p_visible);
RoomEditorPlugin(EditorNode *p_node);
~RoomEditorPlugin();
};
#endif

View File

@ -3019,6 +3019,7 @@ void SpatialEditorViewport::_init_gizmo_instance(int p_idx) {
VS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false); VS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF); VS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer); VS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer);
VS::get_singleton()->instance_set_portal_mode(move_gizmo_instance[i], VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
move_plane_gizmo_instance[i] = VS::get_singleton()->instance_create(); move_plane_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid()); VS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid());
@ -3026,6 +3027,7 @@ void SpatialEditorViewport::_init_gizmo_instance(int p_idx) {
VS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false); VS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF); VS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer); VS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer);
VS::get_singleton()->instance_set_portal_mode(move_plane_gizmo_instance[i], VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
rotate_gizmo_instance[i] = VS::get_singleton()->instance_create(); rotate_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid()); VS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid());
@ -3033,6 +3035,7 @@ void SpatialEditorViewport::_init_gizmo_instance(int p_idx) {
VS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false); VS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF); VS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer); VS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer);
VS::get_singleton()->instance_set_portal_mode(rotate_gizmo_instance[i], VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
scale_gizmo_instance[i] = VS::get_singleton()->instance_create(); scale_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid()); VS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid());
@ -3040,6 +3043,7 @@ void SpatialEditorViewport::_init_gizmo_instance(int p_idx) {
VS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false); VS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF); VS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer); VS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer);
VS::get_singleton()->instance_set_portal_mode(scale_gizmo_instance[i], VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
scale_plane_gizmo_instance[i] = VS::get_singleton()->instance_create(); scale_plane_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid()); VS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid());
@ -3047,6 +3051,7 @@ void SpatialEditorViewport::_init_gizmo_instance(int p_idx) {
VS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false); VS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF); VS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer); VS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer);
VS::get_singleton()->instance_set_portal_mode(scale_plane_gizmo_instance[i], VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
} }
// Rotation white outline // Rotation white outline
@ -6001,6 +6006,15 @@ void SpatialEditor::set_can_preview(Camera *p_preview) {
} }
} }
void SpatialEditor::set_message(String p_message, float p_time) {
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
SpatialEditorViewport *viewport = get_editor_viewport(i);
if (viewport->is_visible()) {
viewport->set_message(p_message, p_time);
}
}
}
VSplitContainer *SpatialEditor::get_shader_split() { VSplitContainer *SpatialEditor::get_shader_split() {
return shader_split; return shader_split;
} }

View File

@ -781,6 +781,7 @@ public:
void set_over_gizmo_handle(int idx) { over_gizmo_handle = idx; } void set_over_gizmo_handle(int idx) { over_gizmo_handle = idx; }
void set_can_preview(Camera *p_preview); void set_can_preview(Camera *p_preview);
void set_message(String p_message, float p_time = 5);
SpatialEditorViewport *get_editor_viewport(int p_idx) { SpatialEditorViewport *get_editor_viewport(int p_idx) {
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr); ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr);

View File

@ -44,9 +44,11 @@
#include "scene/3d/navigation_mesh.h" #include "scene/3d/navigation_mesh.h"
#include "scene/3d/particles.h" #include "scene/3d/particles.h"
#include "scene/3d/physics_joint.h" #include "scene/3d/physics_joint.h"
#include "scene/3d/portal.h"
#include "scene/3d/position_3d.h" #include "scene/3d/position_3d.h"
#include "scene/3d/ray_cast.h" #include "scene/3d/ray_cast.h"
#include "scene/3d/reflection_probe.h" #include "scene/3d/reflection_probe.h"
#include "scene/3d/room.h"
#include "scene/3d/soft_body.h" #include "scene/3d/soft_body.h"
#include "scene/3d/spring_arm.h" #include "scene/3d/spring_arm.h"
#include "scene/3d/sprite_3d.h" #include "scene/3d/sprite_3d.h"
@ -162,6 +164,7 @@ void EditorSpatialGizmo::set_spatial_node(Spatial *p_node) {
void EditorSpatialGizmo::Instance::create_instance(Spatial *p_base, bool p_hidden) { void EditorSpatialGizmo::Instance::create_instance(Spatial *p_base, bool p_hidden) {
instance = VS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world()->get_scenario()); instance = VS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world()->get_scenario());
VS::get_singleton()->instance_set_portal_mode(instance, VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
VS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id()); VS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id());
if (skin_reference.is_valid()) { if (skin_reference.is_valid()) {
VS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton()); VS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton());
@ -4429,3 +4432,277 @@ void JointSpatialGizmoPlugin::CreateGeneric6DOFJointGizmo(
#undef ADD_VTX #undef ADD_VTX
} }
////
RoomGizmoPlugin::RoomGizmoPlugin() {
create_material("room", Color(0.5, 1.0, 0.0), false, true, false);
create_material("room_overlap", Color(1.0, 0.0, 0.0), false, false, false);
}
bool RoomGizmoPlugin::has_gizmo(Spatial *p_spatial) {
if (Object::cast_to<Room>(p_spatial)) {
return true;
}
return false;
}
String RoomGizmoPlugin::get_name() const {
return "Room";
}
int RoomGizmoPlugin::get_priority() const {
return -1;
}
void RoomGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
p_gizmo->clear();
Room *room = Object::cast_to<Room>(p_gizmo->get_spatial_node());
if (room) {
const Geometry::MeshData &md = room->_bound_mesh_data;
if (!md.edges.size())
return;
Vector<Vector3> lines;
Transform tr = room->get_global_transform();
tr.affine_invert();
Ref<Material> material = get_material("room", p_gizmo);
Ref<Material> material_overlap = get_material("room_overlap", p_gizmo);
Color color(1, 1, 1, 1);
for (int n = 0; n < md.edges.size(); n++) {
Vector3 a = md.vertices[md.edges[n].a];
Vector3 b = md.vertices[md.edges[n].b];
// xform
a = tr.xform(a);
b = tr.xform(b);
lines.push_back(a);
lines.push_back(b);
}
p_gizmo->add_lines(lines, material, false, color);
// overlap zones
for (int z = 0; z < room->_gizmo_overlap_zones.size(); z++) {
const Geometry::MeshData &md_overlap = room->_gizmo_overlap_zones[z];
Vector<Vector3> pts;
for (int f = 0; f < md_overlap.faces.size(); f++) {
const Geometry::MeshData::Face &face = md_overlap.faces[f];
for (int c = 0; c < face.indices.size() - 2; c++) {
pts.push_back(tr.xform(md_overlap.vertices[face.indices[0]]));
pts.push_back(tr.xform(md_overlap.vertices[face.indices[c + 1]]));
pts.push_back(tr.xform(md_overlap.vertices[face.indices[c + 2]]));
}
}
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array array;
array.resize(Mesh::ARRAY_MAX);
array[Mesh::ARRAY_VERTEX] = pts;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material_overlap);
}
}
}
////
PortalGizmoPlugin::PortalGizmoPlugin() {
create_icon_material("portal_icon", SpatialEditor::get_singleton()->get_icon("GizmoPortal", "EditorIcons"), true);
create_material("portal", Color(1.0, 1.0, 1.0, 1.0), false, false, true);
create_material("portal_margin", Color(1.0, 0.1, 0.1, 0.3), false, false, false);
create_material("portal_edge", Color(0.0, 0.0, 0.0, 0.3), false, false, false);
create_material("portal_arrow", Color(1.0, 1.0, 1.0, 1.0), false, false, false);
}
bool PortalGizmoPlugin::has_gizmo(Spatial *p_spatial) {
if (Object::cast_to<Portal>(p_spatial)) {
return true;
}
return false;
}
String PortalGizmoPlugin::get_name() const {
return "Portal";
}
int PortalGizmoPlugin::get_priority() const {
return -1;
}
void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
p_gizmo->clear();
Portal *portal = Object::cast_to<Portal>(p_gizmo->get_spatial_node());
if (portal) {
// warnings
if (portal->_warning_outside_room_aabb || portal->_warning_facing_wrong_way) {
Ref<Material> icon = get_material("portal_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
}
Transform tr = portal->get_global_transform();
tr.affine_invert();
Ref<Material> material_portal = get_material("portal", p_gizmo);
Ref<Material> material_margin = get_material("portal_margin", p_gizmo);
Ref<Material> material_edge = get_material("portal_edge", p_gizmo);
Ref<Material> material_arrow = get_material("portal_arrow", p_gizmo);
Color color(1, 1, 1, 1);
Color color_portal_front(0.05, 0.05, 1.0, 0.3);
Color color_portal_back(1.0, 1.0, 0.0, 0.15);
// make sure world points are up to date
portal->portal_update();
int num_points = portal->_pts_world.size();
// prevent compiler warnings later on
if (num_points < 3) {
return;
}
// margins
real_t margin = portal->get_active_portal_margin();
bool show_margins = Portal::_settings_gizmo_show_margins;
if (margin < 0.05f) {
show_margins = false;
}
PoolVector<Vector3> pts_portal;
PoolVector<Color> cols_portal;
PoolVector<Vector3> pts_margin;
Vector<Vector3> edge_pts;
Vector3 portal_normal_world_space = portal->_plane.normal;
portal_normal_world_space *= margin;
// this may not be necessary, dealing with non uniform scales,
// possible the affine_invert dealt with this earlier .. but it's just for
// the editor so not performance critical
Basis normal_basis = tr.basis;
Vector3 portal_normal = normal_basis.xform(portal_normal_world_space);
Vector3 pt_portal_first = tr.xform(portal->_pts_world[0]);
for (int n = 0; n < num_points; n++) {
Vector3 pt = portal->_pts_world[n];
pt = tr.xform(pt);
// CI for visual studio can't seem to get around the possibility
// that this could cause a divide by zero, so using a local to preclude the
// possibility of aliasing from another thread
int m = (n + 1) % num_points;
Vector3 pt_next = portal->_pts_world[m];
pt_next = tr.xform(pt_next);
// don't need the first and last triangles
if ((n != 0) && (n != (num_points - 1))) {
pts_portal.push_back(pt_portal_first);
pts_portal.push_back(pt);
pts_portal.push_back(pt_next);
cols_portal.push_back(color_portal_front);
cols_portal.push_back(color_portal_front);
cols_portal.push_back(color_portal_front);
pts_portal.push_back(pt_next);
pts_portal.push_back(pt);
pts_portal.push_back(pt_portal_first);
cols_portal.push_back(color_portal_back);
cols_portal.push_back(color_portal_back);
cols_portal.push_back(color_portal_back);
}
if (show_margins) {
Vector3 pt0 = pt - portal_normal;
Vector3 pt1 = pt + portal_normal;
Vector3 pt2 = pt_next - portal_normal;
Vector3 pt3 = pt_next + portal_normal;
pts_margin.push_back(pt0);
pts_margin.push_back(pt2);
pts_margin.push_back(pt1);
pts_margin.push_back(pt2);
pts_margin.push_back(pt3);
pts_margin.push_back(pt1);
edge_pts.push_back(pt0);
edge_pts.push_back(pt2);
edge_pts.push_back(pt1);
edge_pts.push_back(pt3);
}
}
// portal itself
{
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array array;
array.resize(Mesh::ARRAY_MAX);
array[Mesh::ARRAY_VERTEX] = pts_portal;
array[Mesh::ARRAY_COLOR] = cols_portal;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material_portal);
}
if (show_margins) {
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array array;
array.resize(Mesh::ARRAY_MAX);
array[Mesh::ARRAY_VERTEX] = pts_margin;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material_margin);
// lines around the outside of mesh
p_gizmo->add_lines(edge_pts, material_edge, false, color);
} // only if the margin is sufficient to be worth drawing
// arrow
if (show_margins) {
const int arrow_points = 7;
const float arrow_length = 0.5; // 1.5
const float arrow_width = 0.1; // 0.3
const float arrow_barb = 0.27; // 0.8
Vector3 arrow[arrow_points] = {
Vector3(0, 0, -1),
Vector3(0, arrow_barb, 0),
Vector3(0, arrow_width, 0),
Vector3(0, arrow_width, arrow_length),
Vector3(0, -arrow_width, arrow_length),
Vector3(0, -arrow_width, 0),
Vector3(0, -arrow_barb, 0)
};
int arrow_sides = 2;
Vector<Vector3> lines;
for (int i = 0; i < arrow_sides; i++) {
for (int j = 0; j < arrow_points; j++) {
Basis ma(Vector3(0, 0, 1), Math_PI * i / arrow_sides);
Vector3 v1 = arrow[j] - Vector3(0, 0, arrow_length);
Vector3 v2 = arrow[(j + 1) % arrow_points] - Vector3(0, 0, arrow_length);
lines.push_back(ma.xform(v1));
lines.push_back(ma.xform(v2));
}
}
p_gizmo->add_lines(lines, material_arrow, false, color);
}
} // was portal
}

View File

@ -429,4 +429,30 @@ public:
JointSpatialGizmoPlugin(); JointSpatialGizmoPlugin();
}; };
class RoomGizmoPlugin : public EditorSpatialGizmoPlugin {
GDCLASS(RoomGizmoPlugin, EditorSpatialGizmoPlugin);
protected:
virtual bool has_gizmo(Spatial *p_spatial);
String get_name() const;
int get_priority() const;
void redraw(EditorSpatialGizmo *p_gizmo);
public:
RoomGizmoPlugin();
};
class PortalGizmoPlugin : public EditorSpatialGizmoPlugin {
GDCLASS(PortalGizmoPlugin, EditorSpatialGizmoPlugin);
protected:
virtual bool has_gizmo(Spatial *p_spatial);
String get_name() const;
int get_priority() const;
void redraw(EditorSpatialGizmo *p_gizmo);
public:
PortalGizmoPlugin();
};
#endif // SPATIAL_EDITOR_GIZMOS_H #endif // SPATIAL_EDITOR_GIZMOS_H

View File

@ -69,6 +69,7 @@
#include "servers/physics_2d_server.h" #include "servers/physics_2d_server.h"
#include "servers/physics_server.h" #include "servers/physics_server.h"
#include "servers/register_server_types.h" #include "servers/register_server_types.h"
#include "servers/visual_server_callbacks.h"
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
#include "editor/doc/doc_data.h" #include "editor/doc/doc_data.h"
@ -103,6 +104,8 @@ static CameraServer *camera_server = nullptr;
static ARVRServer *arvr_server = nullptr; static ARVRServer *arvr_server = nullptr;
static PhysicsServer *physics_server = nullptr; static PhysicsServer *physics_server = nullptr;
static Physics2DServer *physics_2d_server = nullptr; static Physics2DServer *physics_2d_server = nullptr;
static VisualServerCallbacks *visual_server_callbacks = nullptr;
// We error out if setup2() doesn't turn this true // We error out if setup2() doesn't turn this true
static bool _start_success = false; static bool _start_success = false;
@ -1452,6 +1455,10 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
if (use_debug_profiler && script_debugger) { if (use_debug_profiler && script_debugger) {
script_debugger->profiling_start(); script_debugger->profiling_start();
} }
visual_server_callbacks = memnew(VisualServerCallbacks);
VisualServer::get_singleton()->callbacks_register(visual_server_callbacks);
_start_success = true; _start_success = true;
locale = String(); locale = String();
@ -2098,6 +2105,7 @@ bool Main::iteration() {
if (OS::get_singleton()->get_main_loop()->idle(step * time_scale)) { if (OS::get_singleton()->get_main_loop()->idle(step * time_scale)) {
exit = true; exit = true;
} }
visual_server_callbacks->flush();
message_queue->flush(); message_queue->flush();
VisualServer::get_singleton()->sync(); //sync if still drawing from previous frames. VisualServer::get_singleton()->sync(); //sync if still drawing from previous frames.
@ -2290,6 +2298,10 @@ void Main::cleanup(bool p_force) {
message_queue->flush(); message_queue->flush();
memdelete(message_queue); memdelete(message_queue);
if (visual_server_callbacks) {
memdelete(visual_server_callbacks);
}
unregister_core_driver_types(); unregister_core_driver_types();
unregister_core_types(); unregister_core_types();

View File

@ -0,0 +1,66 @@
/*************************************************************************/
/* cull_instance.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 "cull_instance.h"
VARIANT_ENUM_CAST(CullInstance::PortalMode);
void CullInstance::set_portal_mode(CullInstance::PortalMode p_mode) {
_portal_mode = p_mode;
_refresh_portal_mode();
}
CullInstance::PortalMode CullInstance::get_portal_mode() const {
return _portal_mode;
}
void CullInstance::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_portal_mode", "mode"), &CullInstance::set_portal_mode);
ClassDB::bind_method(D_METHOD("get_portal_mode"), &CullInstance::get_portal_mode);
ClassDB::bind_method(D_METHOD("set_include_in_bound"), &CullInstance::set_include_in_bound);
ClassDB::bind_method(D_METHOD("get_include_in_bound"), &CullInstance::get_include_in_bound);
ADD_GROUP("Portals", "");
BIND_ENUM_CONSTANT(PORTAL_MODE_STATIC);
BIND_ENUM_CONSTANT(PORTAL_MODE_DYNAMIC);
BIND_ENUM_CONSTANT(PORTAL_MODE_ROAMING);
BIND_ENUM_CONSTANT(PORTAL_MODE_GLOBAL);
BIND_ENUM_CONSTANT(PORTAL_MODE_IGNORE);
ADD_PROPERTY(PropertyInfo(Variant::INT, "portal_mode", PROPERTY_HINT_ENUM, "Static,Dynamic,Roaming,Global,Ignore"), "set_portal_mode", "get_portal_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_in_bound"), "set_include_in_bound", "get_include_in_bound");
}
CullInstance::CullInstance() {
_portal_mode = PORTAL_MODE_STATIC;
_include_in_bound = true;
}

66
scene/3d/cull_instance.h Normal file
View File

@ -0,0 +1,66 @@
/*************************************************************************/
/* cull_instance.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef CULL_INSTANCE_H
#define CULL_INSTANCE_H
#include "scene/3d/spatial.h"
class CullInstance : public Spatial {
GDCLASS(CullInstance, Spatial);
public:
enum PortalMode {
PORTAL_MODE_STATIC, // not moving within a room
PORTAL_MODE_DYNAMIC, // moving within room
PORTAL_MODE_ROAMING, // moving between rooms
PORTAL_MODE_GLOBAL, // frustum culled only
PORTAL_MODE_IGNORE, // don't show at all - e.g. manual bounds, hidden portals
};
void set_portal_mode(CullInstance::PortalMode p_mode);
CullInstance::PortalMode get_portal_mode() const;
void set_include_in_bound(bool p_enable) { _include_in_bound = p_enable; }
bool get_include_in_bound() const { return _include_in_bound; }
CullInstance();
protected:
virtual void _refresh_portal_mode() = 0;
static void _bind_methods();
private:
PortalMode _portal_mode;
bool _include_in_bound;
};
#endif

View File

@ -821,6 +821,328 @@ void MeshInstance::create_debug_tangents() {
} }
} }
bool MeshInstance::is_mergeable_with(const MeshInstance &p_other) {
if (!get_mesh().is_valid() || !p_other.get_mesh().is_valid()) {
return false;
}
Ref<Mesh> rmesh_a = get_mesh();
Ref<Mesh> rmesh_b = p_other.get_mesh();
int num_surfaces = rmesh_a->get_surface_count();
if (num_surfaces != rmesh_b->get_surface_count()) {
return false;
}
for (int n = 0; n < num_surfaces; n++) {
// materials must match
if (get_active_material(n) != p_other.get_active_material(n)) {
return false;
}
// formats must match
uint32_t format_a = rmesh_a->surface_get_format(n);
uint32_t format_b = rmesh_b->surface_get_format(n);
if (format_a != format_b) {
return false;
}
}
// NOTE : These three commented out sections below are more conservative
// checks for whether to allow mesh merging. I am not absolutely sure a priori
// how conservative we need to be, so we can further enable this if testing
// shows they are required.
// if (get_surface_material_count() != p_other.get_surface_material_count()) {
// return false;
// }
// for (int n = 0; n < get_surface_material_count(); n++) {
// if (get_surface_material(n) != p_other.get_surface_material(n)) {
// return false;
// }
// }
// test only allow identical meshes
// if (get_mesh() != p_other.get_mesh()) {
// return false;
// }
return true;
}
void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, int p_surface_id, PoolVector<Vector3> &r_verts, PoolVector<Vector3> &r_norms, PoolVector<real_t> &r_tangents, PoolVector<Color> &r_colors, PoolVector<Vector2> &r_uvs, PoolVector<Vector2> &r_uv2s, PoolVector<int> &r_inds) {
_merge_log("\t\t\tmesh data from " + p_mi.get_name());
// get the mesh verts in local space
Ref<Mesh> rmesh = p_mi.get_mesh();
if (rmesh->get_surface_count() <= p_surface_id) {
return;
}
Array arrays = rmesh->surface_get_arrays(p_surface_id);
PoolVector<Vector3> verts = arrays[VS::ARRAY_VERTEX];
PoolVector<Vector3> normals = arrays[VS::ARRAY_NORMAL];
PoolVector<real_t> tangents = arrays[VS::ARRAY_TANGENT];
PoolVector<Color> colors = arrays[VS::ARRAY_COLOR];
PoolVector<Vector2> uvs = arrays[VS::ARRAY_TEX_UV];
PoolVector<Vector2> uv2s = arrays[VS::ARRAY_TEX_UV2];
PoolVector<int> indices = arrays[VS::ARRAY_INDEX];
// NEW .. the checking for valid triangles should be on WORLD SPACE vertices,
// NOT model space
// special case, if no indices, create some
int num_indices_before = indices.size();
if (!_ensure_indices_valid(indices, verts)) {
_merge_log("\tignoring INVALID TRIANGLES (duplicate indices or zero area triangle) detected in " + p_mi.get_name() + ", num inds before / after " + itos(num_indices_before) + " / " + itos(indices.size()));
}
// the first index of this mesh is offset from the verts we already have stored in the merged mesh
int first_index = r_verts.size();
// transform verts to world space
Transform tr = p_mi.get_global_transform();
// to transform normals
Basis normal_basis = tr.basis.inverse();
normal_basis.transpose();
for (int n = 0; n < verts.size(); n++) {
Vector3 pt_world = tr.xform(verts[n]);
r_verts.push_back(pt_world);
if (normals.size()) {
Vector3 pt_norm = normal_basis.xform(normals[n]);
pt_norm.normalize();
r_norms.push_back(pt_norm);
}
if (tangents.size()) {
int tstart = n * 4;
Vector3 pt_tangent = Vector3(tangents[tstart], tangents[tstart + 1], tangents[tstart + 2]);
real_t fourth = tangents[tstart + 3];
pt_tangent = normal_basis.xform(pt_tangent);
pt_tangent.normalize();
r_tangents.push_back(pt_tangent.x);
r_tangents.push_back(pt_tangent.y);
r_tangents.push_back(pt_tangent.z);
r_tangents.push_back(fourth);
}
if (colors.size()) {
r_colors.push_back(colors[n]);
}
if (uvs.size()) {
r_uvs.push_back(uvs[n]);
}
if (uv2s.size()) {
r_uv2s.push_back(uv2s[n]);
}
}
// indices
for (int n = 0; n < indices.size(); n++) {
int ind = indices[n] + first_index;
r_inds.push_back(ind);
}
}
bool MeshInstance::_ensure_indices_valid(PoolVector<int> &r_indices, const PoolVector<Vector3> &p_verts) {
// no indices? create some
if (!r_indices.size()) {
_merge_log("\t\t\t\tindices are blank, creating...");
// indices are blank!! let's create some, assuming the mesh is using triangles
r_indices.resize(p_verts.size());
PoolVector<int>::Write write = r_indices.write();
int *pi = write.ptr();
// this is assuming each triangle vertex is unique
for (int n = 0; n < p_verts.size(); n++) {
*pi = n;
pi++;
}
}
if (!_check_for_valid_indices(r_indices, p_verts, nullptr)) {
LocalVector<int, int32_t> new_inds;
_check_for_valid_indices(r_indices, p_verts, &new_inds);
// copy the new indices
r_indices.resize(new_inds.size());
PoolVector<int>::Write write = r_indices.write();
int *pi = write.ptr();
for (int n = 0; n < new_inds.size(); n++) {
pi[n] = new_inds[n];
}
return false;
}
return true;
}
// check for invalid tris, or make a list of the valid triangles, depending on whether r_inds is set
bool MeshInstance::_check_for_valid_indices(const PoolVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int, int32_t> *r_inds) {
int nTris = p_inds.size();
nTris /= 3;
int indCount = 0;
for (int t = 0; t < nTris; t++) {
int i0 = p_inds[indCount++];
int i1 = p_inds[indCount++];
int i2 = p_inds[indCount++];
bool ok = true;
// if the indices are the same, the triangle is invalid
if (i0 == i1) {
ok = false;
}
if (i1 == i2) {
ok = false;
}
if (i0 == i2) {
ok = false;
}
// check positions
if (ok) {
// vertex positions
const Vector3 &p0 = p_verts[i0];
const Vector3 &p1 = p_verts[i1];
const Vector3 &p2 = p_verts[i2];
// if the area is zero, the triangle is invalid (and will crash xatlas if we use it)
if (_triangle_is_degenerate(p0, p1, p2, 0.00001)) {
_merge_log("\t\tdetected zero area triangle, ignoring");
ok = false;
}
}
if (ok) {
// if the triangle is ok, we will output it if we are outputting
if (r_inds) {
r_inds->push_back(i0);
r_inds->push_back(i1);
r_inds->push_back(i2);
}
} else {
// if triangle not ok, return failed check if we are not outputting
if (!r_inds) {
return false;
}
}
}
return true;
}
bool MeshInstance::_triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) {
// not interested in the actual area, but numerical stability
Vector3 edge1 = p_b - p_a;
Vector3 edge2 = p_c - p_a;
// for numerical stability keep these values reasonably high
edge1 *= 1024.0;
edge2 *= 1024.0;
Vector3 vec = edge1.cross(edge2);
real_t sl = vec.length_squared();
if (sl <= p_epsilon) {
return true;
}
return false;
}
bool MeshInstance::create_by_merging(Vector<MeshInstance *> p_list) {
// must be at least 2 meshes to merge
if (p_list.size() < 2) {
// should not happen but just in case
return false;
}
// use the first mesh instance to get common data like number of surfaces
const MeshInstance *first = p_list[0];
Ref<ArrayMesh> am;
am.instance();
for (int s = 0; s < first->get_mesh()->get_surface_count(); s++) {
PoolVector<Vector3> verts;
PoolVector<Vector3> normals;
PoolVector<real_t> tangents;
PoolVector<Color> colors;
PoolVector<Vector2> uvs;
PoolVector<Vector2> uv2s;
PoolVector<int> inds;
for (int n = 0; n < p_list.size(); n++) {
_merge_into_mesh_data(*p_list[n], s, verts, normals, tangents, colors, uvs, uv2s, inds);
} // for n through source meshes
if (!verts.size()) {
WARN_PRINT_ONCE("No vertices for surface");
}
// sanity check on the indices
for (int n = 0; n < inds.size(); n++) {
int i = inds[n];
if (i >= verts.size()) {
WARN_PRINT_ONCE("Mesh index out of range, invalid mesh, aborting");
return false;
}
}
Array arr;
arr.resize(Mesh::ARRAY_MAX);
arr[Mesh::ARRAY_VERTEX] = verts;
if (normals.size()) {
arr[Mesh::ARRAY_NORMAL] = normals;
}
if (tangents.size()) {
arr[Mesh::ARRAY_TANGENT] = tangents;
}
if (colors.size()) {
arr[Mesh::ARRAY_COLOR] = colors;
}
if (uvs.size()) {
arr[Mesh::ARRAY_TEX_UV] = uvs;
}
if (uv2s.size()) {
arr[Mesh::ARRAY_TEX_UV2] = uv2s;
}
arr[Mesh::ARRAY_INDEX] = inds;
am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT);
} // for s through surfaces
// set all the surfaces on the mesh
set_mesh(am);
// set merged materials
int num_surfaces = first->get_mesh()->get_surface_count();
for (int n = 0; n < num_surfaces; n++) {
set_surface_material(n, first->get_active_material(n));
}
return true;
}
void MeshInstance::_merge_log(String p_string) {
print_verbose(p_string);
}
void MeshInstance::_bind_methods() { void MeshInstance::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance::set_mesh); ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance::get_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance::get_mesh);

View File

@ -94,6 +94,14 @@ protected:
void _initialize_skinning(bool p_force_reset = false, bool p_call_attach_skeleton = true); void _initialize_skinning(bool p_force_reset = false, bool p_call_attach_skeleton = true);
void _update_skinning(); void _update_skinning();
private:
// merging
void _merge_into_mesh_data(const MeshInstance &p_mi, int p_surface_id, PoolVector<Vector3> &r_verts, PoolVector<Vector3> &r_norms, PoolVector<real_t> &r_tangents, PoolVector<Color> &r_colors, PoolVector<Vector2> &r_uvs, PoolVector<Vector2> &r_uv2s, PoolVector<int> &r_inds);
bool _ensure_indices_valid(PoolVector<int> &r_indices, const PoolVector<Vector3> &p_verts);
bool _check_for_valid_indices(const PoolVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int, int32_t> *r_inds);
bool _triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon);
void _merge_log(String p_string);
protected: protected:
bool _set(const StringName &p_name, const Variant &p_value); bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const; bool _get(const StringName &p_name, Variant &r_ret) const;
@ -133,6 +141,10 @@ public:
void create_debug_tangents(); void create_debug_tangents();
// merging
bool is_mergeable_with(const MeshInstance &p_other);
bool create_by_merging(Vector<MeshInstance *> p_list);
virtual AABB get_aabb() const; virtual AABB get_aabb() const;
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;

639
scene/3d/portal.cpp Normal file
View File

@ -0,0 +1,639 @@
/*************************************************************************/
/* 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"
#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();
// 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);
}
}
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() {
_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();
_margin = 1.0f;
_default_margin = 1.0f;
_use_default_margin = true;
}
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();
} 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;
}
void Portal::resolve_links(const RID &p_from_room_rid) {
Room *linkedroom = nullptr;
if (has_node(_settings_path_linkedroom)) {
linkedroom = Object::cast_to<Room>(get_node(_settings_path_linkedroom));
}
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) {
// 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;
_changed();
break;
}
}
if (!success) {
WARN_PRINT("Could not set portal name, set name manually instead.");
}
} else {
_changed();
}
} else {
WARN_PRINT("Linked room cannot be portal's parent room, ignoring.");
}
} else {
WARN_PRINT("Linked room path is not a room, ignoring.");
}
} else {
WARN_PRINT("Linked room path not found.");
}
}
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) {
return _default_margin;
}
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");
}

174
scene/3d/portal.h Normal file
View File

@ -0,0 +1,174 @@
/*************************************************************************/
/* portal.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_H
#define PORTAL_H
#include "core/rid.h"
#include "spatial.h"
class RoomManager;
class MeshInstance;
class Portal : public Spatial {
GDCLASS(Portal, Spatial);
RID _portal_rid;
friend class RoomManager;
friend class PortalGizmoPlugin;
public:
// ui interface .. will have no effect after room conversion
void set_linked_room(const NodePath &link_path);
NodePath get_linked_room() const;
// open and close doors
void set_portal_active(bool p_active);
bool get_portal_active() const;
// whether the portal can be seen through in both directions or not
void set_two_way(bool p_two_way) {
_settings_two_way = p_two_way;
_changed();
}
bool is_two_way() const { return _settings_two_way; }
void clear();
// whether to use the room manager default
void set_use_default_margin(bool p_use);
bool get_use_default_margin() const;
// custom portal margin (per portal) .. only valid if use_default_margin is off
void set_portal_margin(real_t p_margin);
real_t get_portal_margin() const;
// either the default margin or the custom portal margin, depending on the setting
real_t get_active_portal_margin() const;
// the raw points are used for the IDE Inspector, and also to allow the user
// to edit the geometry of the portal at runtime (they can also just change the portal node transform)
void set_points(const PoolVector<Vector2> &p_points);
PoolVector<Vector2> get_points() const;
Portal();
~Portal();
// whether the convention is that the normal of the portal points outward (false) or inward (true)
// normally I'd recommend portal normal faces outward. But you may make a booboo, so this can work
// with either convention.
static bool _portal_plane_convention;
private:
// updates world coords when the tranform changes, and updates the visual server
void portal_update();
void set_linked_room_internal(const NodePath &link_path);
bool try_set_unique_name(const String &p_name);
bool is_portal_internal(int p_room_outer) const { return _internal && (_linkedroom_ID[0] != p_room_outer); }
bool create_from_mesh_instance(const MeshInstance *p_mi);
void flip();
void _sanitize_points();
void _update_aabb();
Vector3 _vec2to3(const Vector2 &p_pt) const { return Vector3(p_pt.x, p_pt.y, 0.0); }
void _sort_verts_clockwise(bool portal_plane_convention, Vector<Vector3> &r_verts);
Plane _plane_from_points_newell(const Vector<Vector3> &p_pts);
void resolve_links(const RID &p_from_room_rid);
void _changed();
// nodepath to the room this outgoing portal leads to
NodePath _settings_path_linkedroom;
// portal can be turned on and off at runtime, for e.g.
// opening and closing a door
bool _settings_active;
// user can choose not to include the portal in the convex hull of the room
// during conversion
bool _settings_include_in_bound;
// portals can be seen through one way or two way
bool _settings_two_way;
// room from and to, ID in the room manager
int _linkedroom_ID[2];
// whether the portal is from a room within a room
bool _internal;
// normal determined by winding order
Vector<Vector3> _pts_world;
// points in local space of the plane,
// not necessary in correct winding order
// (as they can be edited by the user)
// Note: these are saved by the IDE
PoolVector<Vector2> _pts_local_raw;
// sanitized
Vector<Vector2> _pts_local;
AABB _aabb_local;
// center of the world points
Vector3 _pt_center_world;
// portal plane in world space, always pointing OUTWARD from the source room
Plane _plane;
// extension margin
real_t _margin;
real_t _default_margin;
bool _use_default_margin;
// for editing
#ifdef TOOLS_ENABLED
ObjectID _room_manager_godot_ID;
// warnings
bool _warning_outside_room_aabb = false;
bool _warning_facing_wrong_way = false;
#endif
// this is read from the gizmo
static bool _settings_gizmo_show_margins;
public:
// makes sure portals are not converted more than once per
// call to rooms_convert
int _conversion_tick = -1;
protected:
static void _bind_methods();
void _notification(int p_what);
};
#endif

226
scene/3d/room.cpp Normal file
View File

@ -0,0 +1,226 @@
/*************************************************************************/
/* room.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 "room.h"
#include "portal.h"
#include "room_manager.h"
#include "servers/visual_server.h"
void Room::SimplifyInfo::set_simplify(real_t p_value, real_t p_room_size) {
_plane_simplify = CLAMP(p_value, 0.0, 1.0);
// just for reference in case we later want to use degrees...
// _plane_simplify_dot = Math::cos(Math::deg2rad(_plane_simplify_degrees));
// _plane_simplify_dot = _plane_simplify;
// _plane_simplify_dot *= _plane_simplify_dot;
// _plane_simplify_dot = 1.0 - _plane_simplify_dot;
// distance based on size of room
// _plane_simplify_dist = p_room_size * 0.1 * _plane_simplify;
// _plane_simplify_dist = MAX(_plane_simplify_dist, 0.08);
// test fix
_plane_simplify_dot = 0.99;
_plane_simplify_dist = 0.08;
// print_verbose("plane simplify dot : " + String(Variant(_plane_simplify_dot)));
// print_verbose("plane simplify dist : " + String(Variant(_plane_simplify_dist)));
}
bool Room::SimplifyInfo::add_plane_if_unique(LocalVector<Plane, int32_t> &r_planes, const Plane &p) const {
for (int n = 0; n < r_planes.size(); n++) {
const Plane &o = r_planes[n];
// this is a fudge factor for how close planes can be to be considered the same ...
// to prevent ridiculous amounts of planes
const real_t d = _plane_simplify_dist; // 0.08f
if (Math::abs(p.d - o.d) > d) {
continue;
}
real_t dot = p.normal.dot(o.normal);
if (dot < _plane_simplify_dot) // 0.98f
{
continue;
}
// match!
return false;
}
r_planes.push_back(p);
return true;
}
void Room::clear() {
_room_ID = -1;
_planes.clear();
_preliminary_planes.clear();
_roomgroups.clear();
_portals.clear();
_bound_mesh_data.edges.clear();
_bound_mesh_data.faces.clear();
_bound_mesh_data.vertices.clear();
_aabb = AABB();
#ifdef TOOLS_ENABLED
_gizmo_overlap_zones.clear();
#endif
}
Room::Room() {
_room_rid = VisualServer::get_singleton()->room_create();
}
Room::~Room() {
if (_room_rid != RID()) {
VisualServer::get_singleton()->free(_room_rid);
}
}
void Room::set_room_simplify(real_t p_value) {
_simplify_info.set_simplify(p_value, _aabb.get_longest_axis_size());
}
void Room::set_use_default_simplify(bool p_use) {
_use_default_simplify = p_use;
}
void Room::set_points(const PoolVector<Vector3> &p_points) {
_bound_pts = p_points;
#ifdef TOOLS_ENABLED
if (p_points.size()) {
_changed(true);
}
#endif
}
PoolVector<Vector3> Room::get_points() const {
return _bound_pts;
}
PoolVector<Vector3> Room::generate_points() {
PoolVector<Vector3> pts_returned;
#ifdef TOOLS_ENABLED
// do a rooms convert to make sure the planes are up to date
RoomManager *rm = RoomManager::active_room_manager;
if (rm) {
rm->rooms_convert();
}
if (!_planes.size()) {
return pts_returned;
}
// scale an epsilon using 10.0 for a normal sized room
real_t scaled_epsilon = _aabb.get_longest_axis_size() / 10.0;
scaled_epsilon = MAX(scaled_epsilon * 0.01, 0.001);
LocalVector<Vector3, int32_t> pts;
pts = Geometry::compute_convex_mesh_points(&_planes[0], _planes.size(), scaled_epsilon);
// eliminate duplicates
for (int n = 0; n < pts.size(); n++) {
const Vector3 &a = pts[n];
for (int m = n + 1; m < pts.size(); m++) {
const Vector3 &b = pts[m];
if (a.is_equal_approx(b, scaled_epsilon)) {
// remove b
pts.remove_unordered(m);
m--; // repeat m as the new m is the old last
}
}
}
// convert vector to poolvector
pts_returned.resize(pts.size());
Transform tr = get_global_transform();
tr.affine_invert();
for (int n = 0; n < pts.size(); n++) {
// the points should be saved in LOCAL space,
// so that if we move the room afterwards, the bound points
// will also move in relation to the room.
pts_returned.set(n, tr.xform(pts[n]));
}
#endif
return pts_returned;
}
// extra editor links to the room manager to allow unloading
// on change, or re-converting
void Room::_changed(bool p_regenerate_bounds) {
#ifdef TOOLS_ENABLED
RoomManager *rm = RoomManager::active_room_manager;
if (!rm) {
return;
}
if (p_regenerate_bounds) {
rm->_room_regenerate_bound(this);
}
rm->_rooms_changed();
#endif
}
void Room::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
ERR_FAIL_COND(get_world().is_null());
VisualServer::get_singleton()->room_set_scenario(_room_rid, get_world()->get_scenario());
} break;
case NOTIFICATION_EXIT_WORLD: {
VisualServer::get_singleton()->room_set_scenario(_room_rid, RID());
} break;
}
}
void Room::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_default_simplify", "p_use"), &Room::set_use_default_simplify);
ClassDB::bind_method(D_METHOD("get_use_default_simplify"), &Room::get_use_default_simplify);
ClassDB::bind_method(D_METHOD("set_room_simplify", "p_value"), &Room::set_room_simplify);
ClassDB::bind_method(D_METHOD("get_room_simplify"), &Room::get_room_simplify);
ClassDB::bind_method(D_METHOD("set_points", "points"), &Room::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &Room::get_points);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_simplify"), "set_use_default_simplify", "get_use_default_simplify");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "room_simplify", PROPERTY_HINT_RANGE, "0.0,1.0,0.005"), "set_room_simplify", "get_room_simplify");
ADD_GROUP("Bound", "");
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "points"), "set_points", "get_points");
}

131
scene/3d/room.h Normal file
View File

@ -0,0 +1,131 @@
/*************************************************************************/
/* room.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef ROOM_H
#define ROOM_H
#include "core/local_vector.h"
#include "core/rid.h"
#include "spatial.h"
class Portal;
class Room : public Spatial {
GDCLASS(Room, Spatial);
friend class RoomManager;
friend class RoomGroup;
friend class Portal;
friend class RoomGizmoPlugin;
friend class RoomEditorPlugin;
RID _room_rid;
public:
struct SimplifyInfo {
SimplifyInfo() { set_simplify(0.5); }
void set_simplify(real_t p_value, real_t p_room_size = 0.0);
bool add_plane_if_unique(LocalVector<Plane, int32_t> &r_planes, const Plane &p) const;
real_t _plane_simplify = 0.5;
real_t _plane_simplify_dot = 0.98;
real_t _plane_simplify_dist = 0.08;
};
Room();
~Room();
void set_room_simplify(real_t p_value);
real_t get_room_simplify() const { return _simplify_info._plane_simplify; }
// whether to use the room manager default
void set_use_default_simplify(bool p_use);
bool get_use_default_simplify() const { return _use_default_simplify; }
void set_points(const PoolVector<Vector3> &p_points);
PoolVector<Vector3> get_points() const;
// editor only
PoolVector<Vector3> generate_points();
private:
void clear();
void _changed(bool p_regenerate_bounds = false);
// planes forming convex hull of room
LocalVector<Plane, int32_t> _planes;
// preliminary planes are created during the first conversion pass,
// they do not include the portals, and are used for identifying auto
// linkage of rooms by portals
LocalVector<Plane, int32_t> _preliminary_planes;
Geometry::MeshData _bound_mesh_data;
AABB _aabb;
// editable points making up the bound
PoolVector<Vector3> _bound_pts;
#ifdef TOOLS_ENABLED
// to help with editing, when converting, we can generate overlap zones
// that occur between rooms. Ideally these should not occur, as rooms
// should be convex and non-overlapping. But if they do occur, they should
// be minimized.
Vector<Geometry::MeshData> _gizmo_overlap_zones;
#endif
// makes sure lrooms are not converted more than once per
// call to rooms_convert
int _conversion_tick = -1;
// room ID during conversion, used for matching portals links to rooms
int _room_ID;
// room priority allows rooms to be placed inside other rooms,
// such as a house on a landscape room.
// If the camera is inside more than one room, the higher priority room
// will *win* (e.g. house, rather than landscape)
int _room_priority = 0;
// a room may be in one or several roomgroups
LocalVector<int, int32_t> _roomgroups;
// list of portal ids from or to this room, just used in conversion to determine room bound
LocalVector<int, int32_t> _portals;
// each room now stores simplification data
SimplifyInfo _simplify_info;
bool _use_default_simplify = true;
protected:
static void _bind_methods();
void _notification(int p_what);
};
#endif

84
scene/3d/room_group.cpp Normal file
View File

@ -0,0 +1,84 @@
/*************************************************************************/
/* room_group.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 "room_group.h"
#include "room.h"
#include "room_manager.h"
void RoomGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_roomgroup_priority", "p_priority"), &RoomGroup::set_roomgroup_priority);
ClassDB::bind_method(D_METHOD("get_roomgroup_priority"), &RoomGroup::get_roomgroup_priority);
ADD_PROPERTY(PropertyInfo(Variant::INT, "roomgroup_priority", PROPERTY_HINT_RANGE, "-16,16,1", PROPERTY_USAGE_DEFAULT), "set_roomgroup_priority", "get_roomgroup_priority");
}
RoomGroup::RoomGroup() {
_room_group_rid = VisualServer::get_singleton()->roomgroup_create();
}
RoomGroup::~RoomGroup() {
if (_room_group_rid != RID()) {
VisualServer::get_singleton()->free(_room_group_rid);
}
}
void RoomGroup::clear() {
_roomgroup_ID = -1;
}
void RoomGroup::add_room(Room *p_room) {
VisualServer::get_singleton()->roomgroup_add_room(_room_group_rid, p_room->_room_rid);
}
// extra editor links to the room manager to allow unloading
// on change, or re-converting
void RoomGroup::_changed() {
#ifdef TOOLS_ENABLED
RoomManager *rm = RoomManager::active_room_manager;
if (!rm) {
return;
}
rm->_rooms_changed();
#endif
}
void RoomGroup::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
ERR_FAIL_COND(get_world().is_null());
VisualServer::get_singleton()->roomgroup_set_scenario(_room_group_rid, get_world()->get_scenario());
} break;
case NOTIFICATION_EXIT_WORLD: {
VisualServer::get_singleton()->roomgroup_set_scenario(_room_group_rid, RID());
} break;
}
}

79
scene/3d/room_group.h Normal file
View File

@ -0,0 +1,79 @@
/*************************************************************************/
/* room_group.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef ROOM_GROUP_H
#define ROOM_GROUP_H
#include "core/rid.h"
#include "spatial.h"
class Room;
class RoomGroup : public Spatial {
GDCLASS(RoomGroup, Spatial);
friend class RoomManager;
RID _room_group_rid;
public:
RoomGroup();
~RoomGroup();
void add_room(Room *p_room);
void set_roomgroup_priority(int p_priority) {
_settings_priority = p_priority;
_changed();
}
int get_roomgroup_priority() const { return _settings_priority; }
private:
void clear();
void _changed();
// roomgroup ID during conversion
int _roomgroup_ID;
// the roomgroup can be used to set a number of rooms to a different priority
// to allow a group of rooms WITHIN another room / rooms.
// This is for e.g. buildings on landscape.
int _settings_priority = 0;
// makes sure lrooms are not converted more than once per
// call to rooms_convert
int _conversion_tick = -1;
protected:
static void _bind_methods();
void _notification(int p_what);
};
#endif

1921
scene/3d/room_manager.cpp Normal file

File diff suppressed because it is too large Load Diff

273
scene/3d/room_manager.h Normal file
View File

@ -0,0 +1,273 @@
/*************************************************************************/
/* room_manager.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef ROOM_MANAGER_H
#define ROOM_MANAGER_H
#include "core/local_vector.h"
#include "room.h"
#include "spatial.h"
class Portal;
class RoomGroup;
class MeshInstance;
class GeometryInstance;
#define GODOT_PORTAL_DELINEATOR String("_")
#define GODOT_PORTAL_WILDCARD String("*")
class RoomManager : public Spatial {
GDCLASS(RoomManager, Spatial);
public:
enum PVSMode {
PVS_MODE_DISABLED,
PVS_MODE_PARTIAL,
PVS_MODE_FULL,
};
void set_roomlist_path(const NodePath &p_path) {
_settings_path_roomlist = p_path;
}
NodePath get_roomlist_path() const {
return _settings_path_roomlist;
}
void set_preview_camera_path(const NodePath &p_path);
NodePath get_preview_camera_path() const {
return _settings_path_preview_camera;
}
void rooms_set_active(bool p_active);
bool rooms_get_active() const;
void set_show_debug(bool p_show);
bool get_show_debug() const;
void set_show_margins(bool p_show);
bool get_show_margins() const;
void set_debug_sprawl(bool p_enable);
bool get_debug_sprawl() const;
void set_merge_meshes(bool p_enable);
bool get_merge_meshes() const;
void set_remove_danglers(bool p_enable);
bool get_remove_danglers() const;
void set_room_simplify(real_t p_value);
real_t get_room_simplify() const;
void set_default_portal_margin(real_t p_dist);
real_t get_default_portal_margin() const;
void set_overlap_warning_threshold(int p_value) { _overlap_warning_threshold = p_value; }
int get_overlap_warning_threshold() const { return (int)_overlap_warning_threshold; }
void set_portal_depth_limit(int p_limit);
int get_portal_depth_limit() const { return _portal_depth_limit; }
void set_flip_portal_meshes(bool p_flip);
bool get_flip_portal_meshes() const;
void set_pvs_mode(PVSMode p_mode);
PVSMode get_pvs_mode() const;
void set_pvs_filename(String p_filename);
String get_pvs_filename() const;
void set_use_secondary_pvs(bool p_enable) { _settings_use_secondary_pvs = p_enable; }
bool get_use_secondary_pvs() const { return _settings_use_secondary_pvs; }
void set_use_signals(bool p_enable) { _settings_use_signals = p_enable; }
bool get_use_signals() const { return _settings_use_signals; }
void set_gameplay_monitor_enabled(bool p_enable) { _settings_gameplay_monitor_enabled = p_enable; }
bool get_gameplay_monitor_enabled() const { return _settings_gameplay_monitor_enabled; }
void rooms_convert();
void rooms_clear();
void rooms_flip_portals();
// for internal use in the editor..
// either we can clear the rooms and unload,
// or reconvert.
void _rooms_changed();
#ifdef TOOLS_ENABLED
// for a preview, we allow the editor to change the bound
bool _room_regenerate_bound(Room *p_room);
#endif
RoomManager();
~RoomManager();
// an easy way of grabbing the active room manager for tools purposes
#ifdef TOOLS_ENABLED
static RoomManager *active_room_manager;
#endif
private:
// funcs
bool resolve_preview_camera_path();
void _preview_camera_update();
// conversion
// FIRST PASS
void _convert_rooms_recursive(Spatial *p_node, LocalVector<Portal *> &r_portals, LocalVector<RoomGroup *> &r_roomgroups, int p_roomgroup = -1);
void _convert_room(Spatial *p_node, LocalVector<Portal *> &r_portals, const LocalVector<RoomGroup *> &p_roomgroups, int p_roomgroup);
int _convert_roomgroup(Spatial *p_node, LocalVector<RoomGroup *> &r_roomgroups);
void _find_portals_recursive(Spatial *p_node, Room *p_room, LocalVector<Portal *> &r_portals);
void _convert_portal(Room *p_room, Spatial *p_node, LocalVector<Portal *> &portals);
// SECOND PASS
void _second_pass_portals(Spatial *p_roomlist, LocalVector<Portal *> &r_portals);
void _second_pass_rooms(const LocalVector<RoomGroup *> &p_roomgroups, const LocalVector<Portal *> &p_portals);
void _second_pass_room(Room *p_room, const LocalVector<RoomGroup *> &p_roomgroups, const LocalVector<Portal *> &p_portals);
bool _convert_manual_bound(Room *p_room, Spatial *p_node, const LocalVector<Portal *> &p_portals);
void _check_portal_for_warnings(Portal *p_portal, const AABB &p_room_aabb_without_portals);
void _find_statics_recursive(Room *p_room, Spatial *p_node, Vector<Vector3> &r_room_pts);
bool _convert_room_hull_preliminary(Room *p_room, const Vector<Vector3> &p_room_pts, const LocalVector<Portal *> &p_portals);
bool _bound_findpoints_mesh_instance(MeshInstance *p_mi, Vector<Vector3> &r_room_pts, AABB &r_aabb);
bool _bound_findpoints_geom_instance(GeometryInstance *p_gi, Vector<Vector3> &r_room_pts, AABB &r_aabb);
// THIRD PASS
void _third_pass_portals(Spatial *p_roomlist, LocalVector<Portal *> &r_portals);
void _third_pass_rooms(const LocalVector<Portal *> &p_portals);
bool _convert_room_hull_final(Room *p_room, const LocalVector<Portal *> &p_portals);
void _build_simplified_bound(const Room *p_room, Geometry::MeshData &r_md, LocalVector<Plane, int32_t> &r_planes, int p_num_portal_planes);
// misc
bool _add_plane_if_unique(const Room *p_room, LocalVector<Plane, int32_t> &r_planes, const Plane &p);
void _update_portal_margins(Spatial *p_node, real_t p_margin);
Node *_check_roomlist_validity_recursive(Node *p_node);
void _cleanup_after_conversion();
Error _build_room_convex_hull(const Room *p_room, const Vector<Vector3> &p_points, Geometry::MeshData &r_mesh);
#ifdef TOOLS_ENABLED
void _generate_room_overlap_zones();
#endif
// merging
void _merge_meshes_in_room(Room *p_room);
void _list_mergeable_mesh_instances(Spatial *p_node, LocalVector<MeshInstance *, int32_t> &r_list);
void _merge_log(String p_string) { debug_print_line(p_string); }
bool _remove_redundant_dangling_nodes(Spatial *p_node);
// helper funcs
bool _name_starts_with(const Node *p_node, String p_search_string, bool p_allow_no_delineator = false);
void _check_for_misnamed_node(const Node *p_node, String p_start_string);
template <class NODE_TYPE>
NODE_TYPE *_resolve_path(NodePath p_path) const;
template <class NODE_TYPE>
bool _node_is_type(Node *p_node) const;
template <class T>
T *_change_node_type(Spatial *p_node, String p_prefix, bool p_delete = true);
void _update_gizmos_recursive(Node *p_node);
void _set_owner_recursive(Node *p_node, Node *p_owner);
void _flip_portals_recursive(Spatial *p_node);
Error _build_convex_hull(const Vector<Vector3> &p_points, Geometry::MeshData &r_mesh, real_t p_epsilon = 3.0 * UNIT_EPSILON);
// output strings during conversion process
void convert_log(String p_string, int p_priority = 0) { debug_print_line(p_string, p_priority); }
// only prints when user has set 'debug' in the room manager inspector
// also does not show in non editor builds
void debug_print_line(String p_string, int p_priority = 0);
public:
static String _find_name_after(Node *p_node, String p_string_start);
static void show_warning(const String &p_string, const String &p_extra_string = "", bool p_alert = true);
private:
// accessible from UI
NodePath _settings_path_roomlist;
NodePath _settings_path_preview_camera;
// resolved node
Spatial *_roomlist = nullptr;
bool _warning_misnamed_nodes_detected = false;
bool _warning_portal_link_room_not_found = false;
bool _warning_portal_autolink_failed = false;
bool _warning_room_overlap_detected = false;
// merge suitable meshes in rooms?
bool _settings_merge_meshes = false;
// remove redundant childless spatials after merging
bool _settings_remove_danglers = true;
bool _active = true;
// portals, room hulls etc
bool _show_debug = true;
bool _debug_sprawl = false;
// pvs
PVSMode _pvs_mode = PVS_MODE_PARTIAL;
String _pvs_filename;
bool _settings_use_secondary_pvs = false;
bool _settings_use_signals = true;
bool _settings_gameplay_monitor_enabled = false;
int _conversion_tick = 0;
// just used during conversion, could be invalidated
// later by user deleting rooms etc.
LocalVector<Room *, int32_t> _rooms;
// advanced params
real_t _default_portal_margin = 1.0;
real_t _overlap_warning_threshold = 1.0;
Room::SimplifyInfo _room_simplify_info;
int _portal_depth_limit = 16;
// debug override camera
ObjectID _godot_preview_camera_ID = -1;
// local version of the godot camera frustum,
// to prevent updating the visual server (and causing
// a screen refresh) where not necessary.
Vector3 _godot_camera_pos;
Vector<Plane> _godot_camera_planes;
protected:
static void _bind_methods();
void _notification(int p_what);
};
VARIANT_ENUM_CAST(RoomManager::PVSMode);
#endif

View File

@ -35,6 +35,7 @@
#include "scene/main/scene_tree.h" #include "scene/main/scene_tree.h"
#include "scene/main/viewport.h" #include "scene/main/viewport.h"
#include "scene/scene_string_names.h" #include "scene/scene_string_names.h"
#include "servers/visual_server_callbacks.h"
/* /*
@ -119,6 +120,25 @@ void Spatial::_propagate_transform_changed(Spatial *p_origin) {
data.children_lock--; data.children_lock--;
} }
void Spatial::notification_callback(int p_message_type) {
switch (p_message_type) {
default:
break;
case VisualServerCallbacks::CALLBACK_NOTIFICATION_ENTER_GAMEPLAY: {
notification(NOTIFICATION_ENTER_GAMEPLAY);
} break;
case VisualServerCallbacks::CALLBACK_NOTIFICATION_EXIT_GAMEPLAY: {
notification(NOTIFICATION_EXIT_GAMEPLAY);
} break;
case VisualServerCallbacks::CALLBACK_SIGNAL_ENTER_GAMEPLAY: {
emit_signal("gameplay_entered");
} break;
case VisualServerCallbacks::CALLBACK_SIGNAL_EXIT_GAMEPLAY: {
emit_signal("gameplay_exited");
} break;
}
}
void Spatial::_notification(int p_what) { void Spatial::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
@ -759,6 +779,8 @@ void Spatial::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_ENTER_WORLD); BIND_CONSTANT(NOTIFICATION_ENTER_WORLD);
BIND_CONSTANT(NOTIFICATION_EXIT_WORLD); BIND_CONSTANT(NOTIFICATION_EXIT_WORLD);
BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED); BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);
BIND_CONSTANT(NOTIFICATION_ENTER_GAMEPLAY);
BIND_CONSTANT(NOTIFICATION_EXIT_GAMEPLAY);
//ADD_PROPERTY( PropertyInfo(Variant::TRANSFORM,"transform/global",PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR ), "set_global_transform", "get_global_transform") ; //ADD_PROPERTY( PropertyInfo(Variant::TRANSFORM,"transform/global",PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR ), "set_global_transform", "get_global_transform") ;
ADD_GROUP("Transform", ""); ADD_GROUP("Transform", "");
@ -774,6 +796,8 @@ void Spatial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "SpatialGizmo", 0), "set_gizmo", "get_gizmo"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "SpatialGizmo", 0), "set_gizmo", "get_gizmo");
ADD_SIGNAL(MethodInfo("visibility_changed")); ADD_SIGNAL(MethodInfo("visibility_changed"));
ADD_SIGNAL(MethodInfo("gameplay_entered"));
ADD_SIGNAL(MethodInfo("gameplay_exited"));
} }
Spatial::Spatial() : Spatial::Spatial() :

View File

@ -139,8 +139,11 @@ public:
NOTIFICATION_EXIT_WORLD = 42, NOTIFICATION_EXIT_WORLD = 42,
NOTIFICATION_VISIBILITY_CHANGED = 43, NOTIFICATION_VISIBILITY_CHANGED = 43,
NOTIFICATION_LOCAL_TRANSFORM_CHANGED = 44, NOTIFICATION_LOCAL_TRANSFORM_CHANGED = 44,
NOTIFICATION_ENTER_GAMEPLAY = 45,
NOTIFICATION_EXIT_GAMEPLAY = 46,
}; };
virtual void notification_callback(int p_message_type);
Spatial *get_parent_spatial() const; Spatial *get_parent_spatial() const;
Ref<World> get_world() const; Ref<World> get_world() const;

View File

@ -41,7 +41,13 @@
void VisibilityNotifier::_enter_camera(Camera *p_camera) { void VisibilityNotifier::_enter_camera(Camera *p_camera) {
ERR_FAIL_COND(cameras.has(p_camera)); ERR_FAIL_COND(cameras.has(p_camera));
cameras.insert(p_camera); cameras.insert(p_camera);
if (cameras.size() == 1) {
bool in_gameplay = _in_gameplay;
if (!Engine::get_singleton()->are_portals_active()) {
in_gameplay = true;
}
if ((cameras.size() == 1) && in_gameplay) {
emit_signal(SceneStringNames::get_singleton()->screen_entered); emit_signal(SceneStringNames::get_singleton()->screen_entered);
_screen_enter(); _screen_enter();
} }
@ -53,8 +59,13 @@ void VisibilityNotifier::_exit_camera(Camera *p_camera) {
ERR_FAIL_COND(!cameras.has(p_camera)); ERR_FAIL_COND(!cameras.has(p_camera));
cameras.erase(p_camera); cameras.erase(p_camera);
bool in_gameplay = _in_gameplay;
if (!Engine::get_singleton()->are_portals_active()) {
in_gameplay = true;
}
emit_signal(SceneStringNames::get_singleton()->camera_exited, p_camera); emit_signal(SceneStringNames::get_singleton()->camera_exited, p_camera);
if (cameras.size() == 0) { if ((cameras.size() == 0) && (in_gameplay)) {
emit_signal(SceneStringNames::get_singleton()->screen_exited); emit_signal(SceneStringNames::get_singleton()->screen_exited);
_screen_exit(); _screen_exit();
@ -79,25 +90,85 @@ AABB VisibilityNotifier::get_aabb() const {
return aabb; return aabb;
} }
void VisibilityNotifier::_refresh_portal_mode() {
// only create in the visual server if we are roaming.
// All other cases don't require a visual server rep.
// Global and ignore are the same (existing client side functionality only).
// Static and dynamic require only a one off creation at conversion.
if (get_portal_mode() == PORTAL_MODE_ROAMING) {
if (is_inside_world()) {
if (_cull_instance_rid == RID()) {
_cull_instance_rid = VisualServer::get_singleton()->ghost_create();
}
if (is_inside_world() && get_world().is_valid() && get_world()->get_scenario().is_valid() && is_inside_tree()) {
AABB world_aabb = get_global_transform().xform(aabb);
VisualServer::get_singleton()->ghost_set_scenario(_cull_instance_rid, get_world()->get_scenario(), get_instance_id(), world_aabb);
}
} else {
if (_cull_instance_rid != RID()) {
VisualServer::get_singleton()->free(_cull_instance_rid);
_cull_instance_rid = RID();
}
}
} else {
if (_cull_instance_rid != RID()) {
VisualServer::get_singleton()->free(_cull_instance_rid);
_cull_instance_rid = RID();
}
}
}
void VisibilityNotifier::_notification(int p_what) { void VisibilityNotifier::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_WORLD: { case NOTIFICATION_ENTER_WORLD: {
world = get_world(); world = get_world();
ERR_FAIL_COND(!world.is_valid()); ERR_FAIL_COND(!world.is_valid());
world->_register_notifier(this, get_global_transform().xform(aabb));
AABB world_aabb = get_global_transform().xform(aabb);
world->_register_notifier(this, world_aabb);
_refresh_portal_mode();
} break; } break;
case NOTIFICATION_TRANSFORM_CHANGED: { case NOTIFICATION_TRANSFORM_CHANGED: {
world->_update_notifier(this, get_global_transform().xform(aabb)); AABB world_aabb = get_global_transform().xform(aabb);
world->_update_notifier(this, world_aabb);
if (_cull_instance_rid != RID()) {
VisualServer::get_singleton()->ghost_update(_cull_instance_rid, world_aabb);
}
} break; } break;
case NOTIFICATION_EXIT_WORLD: { case NOTIFICATION_EXIT_WORLD: {
ERR_FAIL_COND(!world.is_valid()); ERR_FAIL_COND(!world.is_valid());
world->_remove_notifier(this); world->_remove_notifier(this);
if (_cull_instance_rid != RID()) {
VisualServer::get_singleton()->ghost_set_scenario(_cull_instance_rid, RID(), get_instance_id(), AABB());
}
} break;
case NOTIFICATION_ENTER_GAMEPLAY: {
_in_gameplay = true;
if (cameras.size() && Engine::get_singleton()->are_portals_active()) {
emit_signal(SceneStringNames::get_singleton()->screen_entered);
_screen_enter();
}
} break;
case NOTIFICATION_EXIT_GAMEPLAY: {
_in_gameplay = false;
if (cameras.size() && Engine::get_singleton()->are_portals_active()) {
emit_signal(SceneStringNames::get_singleton()->screen_exited);
_screen_exit();
}
} break; } break;
} }
} }
bool VisibilityNotifier::is_on_screen() const { bool VisibilityNotifier::is_on_screen() const {
return cameras.size() != 0; if (!Engine::get_singleton()->are_portals_active()) {
return cameras.size() != 0;
}
return (cameras.size() != 0) && _in_gameplay;
} }
void VisibilityNotifier::_bind_methods() { void VisibilityNotifier::_bind_methods() {
@ -116,6 +187,13 @@ void VisibilityNotifier::_bind_methods() {
VisibilityNotifier::VisibilityNotifier() { VisibilityNotifier::VisibilityNotifier() {
aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2)); aabb = AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2));
set_notify_transform(true); set_notify_transform(true);
_in_gameplay = false;
}
VisibilityNotifier::~VisibilityNotifier() {
if (_cull_instance_rid != RID()) {
VisualServer::get_singleton()->free(_cull_instance_rid);
}
} }
////////////////////////////////////// //////////////////////////////////////
@ -152,6 +230,20 @@ void VisibilityEnabler::_find_nodes(Node *p_node) {
add = true; add = true;
} }
{
AnimationTree *at = Object::cast_to<AnimationTree>(p_node);
if (at) {
add = true;
}
}
{
AnimationTreePlayer *atp = Object::cast_to<AnimationTreePlayer>(p_node);
if (atp) {
add = true;
}
}
if (add) { if (add) {
p_node->connect(SceneStringNames::get_singleton()->tree_exiting, this, "_node_removed", varray(p_node), CONNECT_ONESHOT); p_node->connect(SceneStringNames::get_singleton()->tree_exiting, this, "_node_removed", varray(p_node), CONNECT_ONESHOT);
nodes[p_node] = meta; nodes[p_node] = meta;

View File

@ -31,21 +31,26 @@
#ifndef VISIBILITY_NOTIFIER_H #ifndef VISIBILITY_NOTIFIER_H
#define VISIBILITY_NOTIFIER_H #define VISIBILITY_NOTIFIER_H
#include "scene/3d/spatial.h" #include "scene/3d/cull_instance.h"
class World; class World;
class Camera; class Camera;
class VisibilityNotifier : public Spatial { class VisibilityNotifier : public CullInstance {
GDCLASS(VisibilityNotifier, Spatial); GDCLASS(VisibilityNotifier, CullInstance);
Ref<World> world; Ref<World> world;
Set<Camera *> cameras; Set<Camera *> cameras;
AABB aabb; AABB aabb;
// if using rooms and portals
RID _cull_instance_rid;
bool _in_gameplay;
protected: protected:
virtual void _screen_enter() {} virtual void _screen_enter() {}
virtual void _screen_exit() {} virtual void _screen_exit() {}
virtual void _refresh_portal_mode();
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();
@ -60,6 +65,7 @@ public:
bool is_on_screen() const; bool is_on_screen() const;
VisibilityNotifier(); VisibilityNotifier();
~VisibilityNotifier();
}; };
class VisibilityEnabler : public VisibilityNotifier { class VisibilityEnabler : public VisibilityNotifier {

View File

@ -38,6 +38,10 @@ AABB VisualInstance::get_transformed_aabb() const {
return get_global_transform().xform(get_aabb()); return get_global_transform().xform(get_aabb());
} }
void VisualInstance::_refresh_portal_mode() {
VisualServer::get_singleton()->instance_set_portal_mode(instance, (VisualServer::InstancePortalMode)get_portal_mode());
}
void VisualInstance::_update_visibility() { void VisualInstance::_update_visibility() {
if (!is_inside_tree()) { if (!is_inside_tree()) {
return; return;
@ -137,7 +141,6 @@ void VisualInstance::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_layer_mask"), &VisualInstance::get_layer_mask); ClassDB::bind_method(D_METHOD("get_layer_mask"), &VisualInstance::get_layer_mask);
ClassDB::bind_method(D_METHOD("set_layer_mask_bit", "layer", "enabled"), &VisualInstance::set_layer_mask_bit); ClassDB::bind_method(D_METHOD("set_layer_mask_bit", "layer", "enabled"), &VisualInstance::set_layer_mask_bit);
ClassDB::bind_method(D_METHOD("get_layer_mask_bit", "layer"), &VisualInstance::get_layer_mask_bit); ClassDB::bind_method(D_METHOD("get_layer_mask_bit", "layer"), &VisualInstance::get_layer_mask_bit);
ClassDB::bind_method(D_METHOD("get_transformed_aabb"), &VisualInstance::get_transformed_aabb); ClassDB::bind_method(D_METHOD("get_transformed_aabb"), &VisualInstance::get_transformed_aabb);
ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_layer_mask", "get_layer_mask"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_layer_mask", "get_layer_mask");

View File

@ -33,11 +33,11 @@
#include "core/math/face3.h" #include "core/math/face3.h"
#include "core/rid.h" #include "core/rid.h"
#include "scene/3d/spatial.h" #include "scene/3d/cull_instance.h"
#include "scene/resources/material.h" #include "scene/resources/material.h"
class VisualInstance : public Spatial { class VisualInstance : public CullInstance {
GDCLASS(VisualInstance, Spatial); GDCLASS(VisualInstance, CullInstance);
OBJ_CATEGORY("3D Visual Nodes"); OBJ_CATEGORY("3D Visual Nodes");
RID base; RID base;
@ -48,6 +48,7 @@ class VisualInstance : public Spatial {
protected: protected:
void _update_visibility(); void _update_visibility();
virtual void _refresh_portal_mode();
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();
@ -57,7 +58,6 @@ public:
FACES_SOLID = 1, // solid geometry FACES_SOLID = 1, // solid geometry
FACES_ENCLOSING = 2, FACES_ENCLOSING = 2,
FACES_DYNAMIC = 4 // dynamic object geometry FACES_DYNAMIC = 4 // dynamic object geometry
}; };
RID get_instance() const; RID get_instance() const;

View File

@ -195,11 +195,15 @@
#include "scene/3d/path.h" #include "scene/3d/path.h"
#include "scene/3d/physics_body.h" #include "scene/3d/physics_body.h"
#include "scene/3d/physics_joint.h" #include "scene/3d/physics_joint.h"
#include "scene/3d/portal.h"
#include "scene/3d/position_3d.h" #include "scene/3d/position_3d.h"
#include "scene/3d/proximity_group.h" #include "scene/3d/proximity_group.h"
#include "scene/3d/ray_cast.h" #include "scene/3d/ray_cast.h"
#include "scene/3d/reflection_probe.h" #include "scene/3d/reflection_probe.h"
#include "scene/3d/remote_transform.h" #include "scene/3d/remote_transform.h"
#include "scene/3d/room.h"
#include "scene/3d/room_group.h"
#include "scene/3d/room_manager.h"
#include "scene/3d/skeleton.h" #include "scene/3d/skeleton.h"
#include "scene/3d/soft_body.h" #include "scene/3d/soft_body.h"
#include "scene/3d/spring_arm.h" #include "scene/3d/spring_arm.h"
@ -397,6 +401,7 @@ void register_scene_types() {
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
ClassDB::register_virtual_class<VisualInstance>(); ClassDB::register_virtual_class<VisualInstance>();
ClassDB::register_virtual_class<CullInstance>();
ClassDB::register_virtual_class<GeometryInstance>(); ClassDB::register_virtual_class<GeometryInstance>();
ClassDB::register_class<Camera>(); ClassDB::register_class<Camera>();
ClassDB::register_class<ClippedCamera>(); ClassDB::register_class<ClippedCamera>();
@ -426,6 +431,10 @@ void register_scene_types() {
ClassDB::register_class<NavigationMeshInstance>(); ClassDB::register_class<NavigationMeshInstance>();
ClassDB::register_class<NavigationMesh>(); ClassDB::register_class<NavigationMesh>();
ClassDB::register_class<Navigation>(); ClassDB::register_class<Navigation>();
ClassDB::register_class<Room>();
ClassDB::register_class<RoomGroup>();
ClassDB::register_class<RoomManager>();
ClassDB::register_class<Portal>();
ClassDB::register_class<RootMotionView>(); ClassDB::register_class<RootMotionView>();
ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor

View File

@ -3,3 +3,5 @@
Import("env") Import("env")
env.add_source_files(env.servers_sources, "*.cpp") env.add_source_files(env.servers_sources, "*.cpp")
SConscript("portals/SCsub")

View File

@ -0,0 +1,5 @@
#!/usr/bin/env python
Import("env")
env.add_source_files(env.servers_sources, "*.cpp")

View File

@ -0,0 +1,381 @@
/*************************************************************************/
/* portal_gameplay_monitor.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_gameplay_monitor.h"
#include "portal_renderer.h"
#include "portal_types.h"
#include "servers/visual/visual_server_globals.h"
#include "servers/visual/visual_server_scene.h"
PortalGameplayMonitor::PortalGameplayMonitor() {
_active_moving_pool_ids_prev = &_active_moving_pool_ids[0];
_active_moving_pool_ids_curr = &_active_moving_pool_ids[1];
_active_rghost_pool_ids_curr = &_active_rghost_pool_ids[0];
_active_rghost_pool_ids_prev = &_active_rghost_pool_ids[1];
_active_room_ids_prev = &_active_room_ids[0];
_active_room_ids_curr = &_active_room_ids[1];
_active_roomgroup_ids_prev = &_active_roomgroup_ids[0];
_active_roomgroup_ids_curr = &_active_roomgroup_ids[1];
_active_sghost_ids_prev = &_active_sghost_ids[0];
_active_sghost_ids_curr = &_active_sghost_ids[1];
}
bool PortalGameplayMonitor::_source_rooms_changed(const int *p_source_room_ids, int p_num_source_rooms) {
bool source_rooms_changed = false;
if (p_num_source_rooms == _source_rooms_prev.size()) {
for (int n = 0; n < p_num_source_rooms; n++) {
if (p_source_room_ids[n] != (int)_source_rooms_prev[n]) {
source_rooms_changed = true;
break;
}
}
} else {
source_rooms_changed = true;
}
if (source_rooms_changed) {
_source_rooms_prev.clear();
for (int n = 0; n < p_num_source_rooms; n++) {
_source_rooms_prev.push_back(p_source_room_ids[n]);
}
}
return source_rooms_changed;
}
void PortalGameplayMonitor::set_params(bool p_use_secondary_pvs, bool p_use_signals) {
_use_secondary_pvs = p_use_secondary_pvs;
_use_signals = p_use_signals;
if (_use_signals) {
_enter_callback_type = VisualServerCallbacks::CALLBACK_SIGNAL_ENTER_GAMEPLAY;
_exit_callback_type = VisualServerCallbacks::CALLBACK_SIGNAL_EXIT_GAMEPLAY;
} else {
_enter_callback_type = VisualServerCallbacks::CALLBACK_NOTIFICATION_ENTER_GAMEPLAY;
_exit_callback_type = VisualServerCallbacks::CALLBACK_NOTIFICATION_EXIT_GAMEPLAY;
}
}
// can work with 1 or multiple cameras
void PortalGameplayMonitor::update_gameplay(PortalRenderer &p_portal_renderer, const int *p_source_room_ids, int p_num_source_rooms) {
const PVS &pvs = p_portal_renderer.get_pvs();
_gameplay_tick++;
// if there is no change in the source room IDs, then we can optimize out a lot of the checks
// (anything not to do with roamers)
bool source_rooms_changed = _source_rooms_changed(p_source_room_ids, p_num_source_rooms);
// lock output
VisualServerCallbacks *callbacks = VSG::scene->get_callbacks();
callbacks->lock();
for (int n = 0; n < p_num_source_rooms; n++) {
const VSRoom &source_room = p_portal_renderer.get_room(p_source_room_ids[n]);
int pvs_size = source_room._pvs_size;
int pvs_first = source_room._pvs_first;
if (_use_secondary_pvs) {
pvs_size = source_room._secondary_pvs_size;
pvs_first = source_room._secondary_pvs_first;
}
for (int r = 0; r < pvs_size; r++) {
int room_id = pvs.get_pvs_room_id(pvs_first + r);
_update_gameplay_room(p_portal_renderer, room_id, source_rooms_changed);
} // for r through the rooms hit in the pvs
} // for n through source rooms
// find any moving that were active last tick that are no longer active, and send notifications
for (int n = 0; n < _active_moving_pool_ids_prev->size(); n++) {
int pool_id = (*_active_moving_pool_ids_prev)[n];
PortalRenderer::Moving &moving = p_portal_renderer.get_pool_moving(pool_id);
// gone out of view
if (moving.last_gameplay_tick_hit != _gameplay_tick) {
VisualServerCallbacks::Message msg;
msg.object_id = VSG::scene->_instance_get_object_ID(moving.instance);
msg.type = _exit_callback_type;
callbacks->push_message(msg);
}
}
// find any roaming ghosts that were active last tick that are no longer active, and send notifications
for (int n = 0; n < _active_rghost_pool_ids_prev->size(); n++) {
int pool_id = (*_active_rghost_pool_ids_prev)[n];
PortalRenderer::RGhost &moving = p_portal_renderer.get_pool_rghost(pool_id);
// gone out of view
if (moving.last_gameplay_tick_hit != _gameplay_tick) {
VisualServerCallbacks::Message msg;
msg.object_id = moving.object_id;
msg.type = VisualServerCallbacks::CALLBACK_NOTIFICATION_EXIT_GAMEPLAY;
callbacks->push_message(msg);
}
}
if (source_rooms_changed) {
// find any rooms that were active last tick that are no longer active, and send notifications
for (int n = 0; n < _active_room_ids_prev->size(); n++) {
int room_id = (*_active_room_ids_prev)[n];
const VSRoom &room = p_portal_renderer.get_room(room_id);
// gone out of view
if (room.last_gameplay_tick_hit != _gameplay_tick) {
VisualServerCallbacks::Message msg;
msg.object_id = room._godot_instance_ID;
msg.type = _exit_callback_type;
callbacks->push_message(msg);
}
}
// find any roomgroups that were active last tick that are no longer active, and send notifications
for (int n = 0; n < _active_roomgroup_ids_prev->size(); n++) {
int roomgroup_id = (*_active_roomgroup_ids_prev)[n];
const VSRoomGroup &roomgroup = p_portal_renderer.get_roomgroup(roomgroup_id);
// gone out of view
if (roomgroup.last_gameplay_tick_hit != _gameplay_tick) {
VisualServerCallbacks::Message msg;
msg.object_id = roomgroup._godot_instance_ID;
msg.type = _exit_callback_type;
callbacks->push_message(msg);
}
}
// find any static ghosts that were active last tick that are no longer active, and send notifications
for (int n = 0; n < _active_sghost_ids_prev->size(); n++) {
int id = (*_active_sghost_ids_prev)[n];
VSStaticGhost &ghost = p_portal_renderer.get_static_ghost(id);
// gone out of view
if (ghost.last_gameplay_tick_hit != _gameplay_tick) {
VisualServerCallbacks::Message msg;
msg.object_id = ghost.object_id;
msg.type = VisualServerCallbacks::CALLBACK_NOTIFICATION_EXIT_GAMEPLAY;
callbacks->push_message(msg);
}
}
} // only need to check these if the source rooms changed
// unlock
callbacks->unlock();
// swap the current and previous lists
_swap();
}
void PortalGameplayMonitor::_update_gameplay_room(PortalRenderer &p_portal_renderer, int p_room_id, bool p_source_rooms_changed) {
// get the room
VSRoom &room = p_portal_renderer.get_room(p_room_id);
int num_roamers = room._roamer_pool_ids.size();
VisualServerCallbacks *callbacks = VSG::scene->get_callbacks();
for (int n = 0; n < num_roamers; n++) {
uint32_t pool_id = room._roamer_pool_ids[n];
PortalRenderer::Moving &moving = p_portal_renderer.get_pool_moving(pool_id);
// done already?
if (moving.last_gameplay_tick_hit == _gameplay_tick)
continue;
// add to the active list
_active_moving_pool_ids_curr->push_back(pool_id);
// if wasn't present in the tick before, add the notification to enter
if (moving.last_gameplay_tick_hit != (_gameplay_tick - 1)) {
VisualServerCallbacks::Message msg;
msg.object_id = VSG::scene->_instance_get_object_ID(moving.instance);
msg.type = _enter_callback_type;
callbacks->push_message(msg);
}
// mark as done
moving.last_gameplay_tick_hit = _gameplay_tick;
}
// roaming ghosts
int num_rghosts = room._rghost_pool_ids.size();
for (int n = 0; n < num_rghosts; n++) {
uint32_t pool_id = room._rghost_pool_ids[n];
PortalRenderer::RGhost &moving = p_portal_renderer.get_pool_rghost(pool_id);
// done already?
if (moving.last_gameplay_tick_hit == _gameplay_tick)
continue;
// add to the active list
_active_rghost_pool_ids_curr->push_back(pool_id);
// if wasn't present in the tick before, add the notification to enter
if (moving.last_gameplay_tick_hit != (_gameplay_tick - 1)) {
VisualServerCallbacks::Message msg;
msg.object_id = moving.object_id;
msg.type = VisualServerCallbacks::CALLBACK_NOTIFICATION_ENTER_GAMEPLAY;
callbacks->push_message(msg);
}
// mark as done
moving.last_gameplay_tick_hit = _gameplay_tick;
}
// no need to progress from here
if (!p_source_rooms_changed) {
return;
}
// has the room come into gameplay?
// later tests only relevant if a room has just come into play
bool room_came_into_play = false;
if (room.last_gameplay_tick_hit != _gameplay_tick) {
room_came_into_play = true;
// add the room to the active list
_active_room_ids_curr->push_back(p_room_id);
// if wasn't present in the tick before, add the notification to enter
if (room.last_gameplay_tick_hit != (_gameplay_tick - 1)) {
VisualServerCallbacks::Message msg;
msg.object_id = room._godot_instance_ID;
msg.type = _enter_callback_type;
callbacks->push_message(msg);
}
// mark as done
room.last_gameplay_tick_hit = _gameplay_tick;
}
// no need to do later tests
if (!room_came_into_play) {
return;
}
///////////////////////////////////////////////////////////////////
// has the roomgroup come into gameplay?
for (int n = 0; n < room._roomgroup_ids.size(); n++) {
int roomgroup_id = room._roomgroup_ids[n];
VSRoomGroup &roomgroup = p_portal_renderer.get_roomgroup(roomgroup_id);
if (roomgroup.last_gameplay_tick_hit != _gameplay_tick) {
// add the room to the active list
_active_roomgroup_ids_curr->push_back(roomgroup_id);
// if wasn't present in the tick before, add the notification to enter
if (roomgroup.last_gameplay_tick_hit != (_gameplay_tick - 1)) {
VisualServerCallbacks::Message msg;
msg.object_id = roomgroup._godot_instance_ID;
msg.type = _enter_callback_type;
callbacks->push_message(msg);
}
// mark as done
roomgroup.last_gameplay_tick_hit = _gameplay_tick;
}
} // for through roomgroups
// static ghosts
int num_sghosts = room._static_ghost_ids.size();
for (int n = 0; n < num_sghosts; n++) {
uint32_t id = room._static_ghost_ids[n];
VSStaticGhost &ghost = p_portal_renderer.get_static_ghost(id);
// done already?
if (ghost.last_gameplay_tick_hit == _gameplay_tick)
continue;
// add to the active list
_active_sghost_ids_curr->push_back(id);
// if wasn't present in the tick before, add the notification to enter
if (ghost.last_gameplay_tick_hit != (_gameplay_tick - 1)) {
VisualServerCallbacks::Message msg;
msg.object_id = ghost.object_id;
msg.type = VisualServerCallbacks::CALLBACK_NOTIFICATION_ENTER_GAMEPLAY;
callbacks->push_message(msg);
}
// mark as done
ghost.last_gameplay_tick_hit = _gameplay_tick;
}
}
void PortalGameplayMonitor::_swap() {
LocalVector<uint32_t, int32_t> *temp = _active_moving_pool_ids_curr;
_active_moving_pool_ids_curr = _active_moving_pool_ids_prev;
_active_moving_pool_ids_prev = temp;
_active_moving_pool_ids_curr->clear();
temp = _active_rghost_pool_ids_curr;
_active_rghost_pool_ids_curr = _active_rghost_pool_ids_prev;
_active_rghost_pool_ids_prev = temp;
_active_rghost_pool_ids_curr->clear();
temp = _active_room_ids_curr;
_active_room_ids_curr = _active_room_ids_prev;
_active_room_ids_prev = temp;
_active_room_ids_curr->clear();
temp = _active_roomgroup_ids_curr;
_active_roomgroup_ids_curr = _active_roomgroup_ids_prev;
_active_roomgroup_ids_prev = temp;
_active_roomgroup_ids_curr->clear();
temp = _active_sghost_ids_curr;
_active_sghost_ids_curr = _active_sghost_ids_prev;
_active_sghost_ids_prev = temp;
_active_sghost_ids_curr->clear();
}

View File

@ -0,0 +1,87 @@
/*************************************************************************/
/* portal_gameplay_monitor.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_GAMEPLAY_MONITOR_H
#define PORTAL_GAMEPLAY_MONITOR_H
#include "core/local_vector.h"
#include "servers/visual_server_callbacks.h"
#include <stdint.h>
class PortalRenderer;
struct VSRoom;
class PortalGameplayMonitor {
public:
PortalGameplayMonitor();
// entering and exiting gameplay notifications (requires PVS)
void update_gameplay(PortalRenderer &p_portal_renderer, const int *p_source_room_ids, int p_num_source_rooms);
void set_params(bool p_use_secondary_pvs, bool p_use_signals);
private:
void _update_gameplay_room(PortalRenderer &p_portal_renderer, int p_room_id, bool p_source_rooms_changed);
bool _source_rooms_changed(const int *p_source_room_ids, int p_num_source_rooms);
void _swap();
uint32_t _gameplay_tick = 1;
// we need two version, current and previous
LocalVector<uint32_t, int32_t> _active_moving_pool_ids[2];
LocalVector<uint32_t, int32_t> *_active_moving_pool_ids_curr;
LocalVector<uint32_t, int32_t> *_active_moving_pool_ids_prev;
LocalVector<uint32_t, int32_t> _active_rghost_pool_ids[2];
LocalVector<uint32_t, int32_t> *_active_rghost_pool_ids_curr;
LocalVector<uint32_t, int32_t> *_active_rghost_pool_ids_prev;
LocalVector<uint32_t, int32_t> _active_room_ids[2];
LocalVector<uint32_t, int32_t> *_active_room_ids_curr;
LocalVector<uint32_t, int32_t> *_active_room_ids_prev;
LocalVector<uint32_t, int32_t> _active_roomgroup_ids[2];
LocalVector<uint32_t, int32_t> *_active_roomgroup_ids_curr;
LocalVector<uint32_t, int32_t> *_active_roomgroup_ids_prev;
LocalVector<uint32_t, int32_t> _active_sghost_ids[2];
LocalVector<uint32_t, int32_t> *_active_sghost_ids_curr;
LocalVector<uint32_t, int32_t> *_active_sghost_ids_prev;
LocalVector<uint32_t, int32_t> _source_rooms_prev;
VisualServerCallbacks::CallbackType _enter_callback_type = VisualServerCallbacks::CALLBACK_NOTIFICATION_ENTER_GAMEPLAY;
VisualServerCallbacks::CallbackType _exit_callback_type = VisualServerCallbacks::CALLBACK_NOTIFICATION_EXIT_GAMEPLAY;
bool _use_secondary_pvs = false;
bool _use_signals = false;
};
#endif

View File

@ -0,0 +1,37 @@
/*************************************************************************/
/* portal_pvs.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_pvs.h"
void PVS::clear() {
_room_pvs.clear();
_room_secondary_pvs.clear();
_loaded = false;
}

View File

@ -0,0 +1,59 @@
/*************************************************************************/
/* portal_pvs.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_PVS_H
#define PORTAL_PVS_H
#include "core/local_vector.h"
class PVS {
public:
void clear();
void add_to_pvs(int p_room_id) { _room_pvs.push_back(p_room_id); }
int32_t get_pvs_size() const { return _room_pvs.size(); }
int32_t get_pvs_room_id(int32_t p_entry) const { return _room_pvs[p_entry]; }
void add_to_secondary_pvs(int p_room_id) { _room_secondary_pvs.push_back(p_room_id); }
int32_t get_secondary_pvs_size() const { return _room_secondary_pvs.size(); }
int32_t get_secondary_pvs_room_id(int32_t p_entry) const { return _room_secondary_pvs[p_entry]; }
void set_loaded(bool p_loaded) { _loaded = p_loaded; }
bool is_loaded() const { return _loaded; }
private:
// pvs
LocalVector<uint16_t, int32_t> _room_pvs;
// secondary pvs is primary plus the immediate neighbors of the primary pvs
LocalVector<uint16_t, int32_t> _room_secondary_pvs;
bool _loaded = false;
};
#endif

View File

@ -0,0 +1,453 @@
/*************************************************************************/
/* portal_pvs_builder.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_pvs_builder.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/print_string.h"
#include "portal_renderer.h"
bool PVSBuilder::_log_active = false;
void PVSBuilder::find_neighbors(LocalVector<Neighbours> &r_neighbors) {
// first find the neighbors
int num_rooms = _portal_renderer->get_num_rooms();
for (int n = 0; n < num_rooms; n++) {
const VSRoom &room = _portal_renderer->get_room(n);
// go through each portal
int num_portals = room._portal_ids.size();
for (int p = 0; p < num_portals; p++) {
int portal_id = room._portal_ids[p];
const VSPortal &portal = _portal_renderer->get_portal(portal_id);
// everything depends on whether the portal is incoming or outgoing.
// if incoming we reverse the logic.
int outgoing = 1;
int room_a_id = portal._linkedroom_ID[0];
if (room_a_id != n) {
outgoing = 0;
DEV_ASSERT(portal._linkedroom_ID[1] == n);
}
// trace through this portal to the next room
int linked_room_id = portal._linkedroom_ID[outgoing];
// not relevant, portal doesn't go anywhere
if (linked_room_id == -1)
continue;
r_neighbors[n].room_ids.push_back(linked_room_id);
} // for p through portals
} // for n through rooms
// the secondary PVS is the primary PVS plus the neighbors
}
void PVSBuilder::create_secondary_pvs(int p_room_id, const LocalVector<Neighbours> &p_neighbors, BitFieldDynamic &r_bitfield_rooms) {
VSRoom &room = _portal_renderer->get_room(p_room_id);
room._secondary_pvs_first = _pvs->get_secondary_pvs_size();
// go through each primary PVS room, and add the neighbors in the secondary pvs
for (int r = 0; r < room._pvs_size; r++) {
int pvs_entry = room._pvs_first + r;
int pvs_room_id = _pvs->get_pvs_room_id(pvs_entry);
// add the visible rooms first
_pvs->add_to_secondary_pvs(pvs_room_id);
room._secondary_pvs_size += 1;
// now any neighbors of this that are not already added
const Neighbours &neigh = p_neighbors[pvs_room_id];
for (int n = 0; n < neigh.room_ids.size(); n++) {
int neigh_room_id = neigh.room_ids[n];
//log("\tconsidering neigh " + itos(neigh_room_id));
if (r_bitfield_rooms.check_and_set(neigh_room_id)) {
// add to the secondary pvs for this room
_pvs->add_to_secondary_pvs(neigh_room_id);
room._secondary_pvs_size += 1;
} // neighbor room has not been added yet
} // go through the neighbors
} // go through each room in the primary pvs
}
#ifdef GODOT_PVS_SUPPORT_SAVE_FILE
bool PVSBuilder::load_pvs(String p_filename) {
if (p_filename == "") {
return false;
}
Error err;
FileAccess *file = FileAccess::open(p_filename, FileAccess::READ, &err);
if (err || !file) {
if (file) {
memdelete(file);
}
return false;
}
// goto needs vars declaring ahead of time
int32_t num_rooms;
int32_t pvs_size;
if (!((file->get_8() == 'p') &&
(file->get_8() == 'v') &&
(file->get_8() == 's') &&
(file->get_8() == ' '))) {
goto failed;
}
num_rooms = file->get_32();
if (num_rooms != _portal_renderer->get_num_rooms()) {
goto failed;
}
for (int n = 0; n < num_rooms; n++) {
if (file->eof_reached())
goto failed;
VSRoom &room = _portal_renderer->get_room(n);
room._pvs_first = file->get_32();
room._pvs_size = file->get_32();
room._secondary_pvs_first = file->get_32();
room._secondary_pvs_size = file->get_32();
}
pvs_size = file->get_32();
for (int n = 0; n < pvs_size; n++) {
_pvs->add_to_pvs(file->get_16());
}
// secondary pvs
pvs_size = file->get_32();
for (int n = 0; n < pvs_size; n++) {
_pvs->add_to_secondary_pvs(file->get_16());
}
if (file) {
memdelete(file);
}
return true;
failed:
if (file) {
memdelete(file);
}
return false;
}
void PVSBuilder::save_pvs(String p_filename) {
if (p_filename == "") {
p_filename = "res://test.pvs";
}
Error err;
FileAccess *file = FileAccess::open(p_filename, FileAccess::WRITE, &err);
if (err || !file) {
if (file) {
memdelete(file);
}
return;
}
file->store_8('p');
file->store_8('v');
file->store_8('s');
file->store_8(' ');
// hash? NYI
// first save the room indices into the pvs
int num_rooms = _portal_renderer->get_num_rooms();
file->store_32(num_rooms);
for (int n = 0; n < num_rooms; n++) {
VSRoom &room = _portal_renderer->get_room(n);
file->store_32(room._pvs_first);
file->store_32(room._pvs_size);
file->store_32(room._secondary_pvs_first);
file->store_32(room._secondary_pvs_size);
}
int32_t pvs_size = _pvs->get_pvs_size();
file->store_32(pvs_size);
for (int n = 0; n < pvs_size; n++) {
int16_t room_id = _pvs->get_pvs_room_id(n);
file->store_16(room_id);
}
pvs_size = _pvs->get_secondary_pvs_size();
file->store_32(pvs_size);
for (int n = 0; n < pvs_size; n++) {
int16_t room_id = _pvs->get_secondary_pvs_room_id(n);
file->store_16(room_id);
}
if (file) {
memdelete(file);
}
}
#endif
void PVSBuilder::calculate_pvs(PortalRenderer &p_portal_renderer, String p_filename) {
_portal_renderer = &p_portal_renderer;
_pvs = &p_portal_renderer.get_pvs();
// attempt to load from file rather than create each time
#ifdef GODOT_PVS_SUPPORT_SAVE_FILE
if (load_pvs(p_filename)) {
print_line("loaded pvs successfully from file " + p_filename);
_pvs->set_loaded(true);
return;
}
#endif
uint32_t time_before = OS::get_singleton()->get_ticks_msec();
int num_rooms = _portal_renderer->get_num_rooms();
BitFieldDynamic bf;
bf.create(num_rooms);
LocalVector<Neighbours> neighbors;
neighbors.resize(num_rooms);
// find the immediate neighbors of each room -
// this is needed to create the secondary pvs
find_neighbors(neighbors);
for (int n = 0; n < num_rooms; n++) {
bf.blank();
//_visible_rooms.clear();
LocalVector<Plane, int32_t> dummy_planes;
VSRoom &room = _portal_renderer->get_room(n);
room._pvs_first = _pvs->get_pvs_size();
log("pvs from room : " + itos(n));
trace_rooms_recursive(0, n, n, -1, false, -1, dummy_planes, bf);
create_secondary_pvs(n, neighbors, bf);
if (_log_active) {
String string = "";
for (int i = 0; i < room._pvs_size; i++) {
int visible_room = _pvs->get_pvs_room_id(room._pvs_first + i);
string += itos(visible_room);
string += ", ";
}
log("\t" + string);
string = "secondary : ";
for (int i = 0; i < room._secondary_pvs_size; i++) {
int visible_room = _pvs->get_secondary_pvs_room_id(room._secondary_pvs_first + i);
string += itos(visible_room);
string += ", ";
}
log("\t" + string);
}
}
_pvs->set_loaded(true);
uint32_t time_after = OS::get_singleton()->get_ticks_msec();
print_verbose("calculated PVS in " + itos(time_after - time_before) + " ms.");
#ifdef GODOT_PVS_SUPPORT_SAVE_FILE
save_pvs(p_filename);
#endif
}
void PVSBuilder::logd(int p_depth, String p_string) {
return;
String string_long;
for (int n = 0; n < p_depth; n++) {
string_long += "\t";
}
string_long += p_string;
log(string_long);
}
void PVSBuilder::log(String p_string) {
if (_log_active) {
print_line(p_string);
}
}
void PVSBuilder::trace_rooms_recursive(int p_depth, int p_source_room_id, int p_room_id, int p_first_portal_id, bool p_first_portal_outgoing, int p_previous_portal_id, const LocalVector<Plane, int32_t> &p_planes, BitFieldDynamic &r_bitfield_rooms) {
// has this room been done already?
if (!r_bitfield_rooms.check_and_set(p_room_id)) {
return;
}
logd(p_depth, "trace_rooms_recursive room " + itos(p_room_id));
// get the room
const VSRoom &room = _portal_renderer->get_room(p_room_id);
// add to the room PVS of the source room
VSRoom &source_room = _portal_renderer->get_room(p_source_room_id);
_pvs->add_to_pvs(p_room_id);
source_room._pvs_size += 1;
// go through each portal
int num_portals = room._portal_ids.size();
for (int p = 0; p < num_portals; p++) {
int portal_id = room._portal_ids[p];
const VSPortal &portal = _portal_renderer->get_portal(portal_id);
// everything depends on whether the portal is incoming or outgoing.
// if incoming we reverse the logic.
int outgoing = 1;
int room_a_id = portal._linkedroom_ID[0];
if (room_a_id != p_room_id) {
outgoing = 0;
DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
}
// trace through this portal to the next room
int linked_room_id = portal._linkedroom_ID[outgoing];
logd(p_depth + 1, "portal to room " + itos(linked_room_id));
// not relevant, portal doesn't go anywhere
if (linked_room_id == -1)
continue;
// linked room done already?
if (r_bitfield_rooms.get_bit(linked_room_id))
continue;
// is it culled by the planes?
VSPortal::ClipResult overall_res = VSPortal::ClipResult::CLIP_INSIDE;
// while clipping to the planes we maintain a list of partial planes, so we can add them to the
// recursive next iteration of planes to check
static LocalVector<int> partial_planes;
partial_planes.clear();
for (int32_t l = 0; l < p_planes.size(); l++) {
VSPortal::ClipResult res = portal.clip_with_plane(p_planes[l]);
switch (res) {
case VSPortal::ClipResult::CLIP_OUTSIDE: {
overall_res = res;
} break;
case VSPortal::ClipResult::CLIP_PARTIAL: {
// if the portal intersects one of the planes, we should take this plane into account
// in the next call of this recursive trace, because it can be used to cull out more objects
overall_res = res;
partial_planes.push_back(l);
} break;
default: // suppress warning
break;
}
// if the portal was totally outside the 'frustum' then we can ignore it
if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE)
break;
}
// this portal is culled
if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) {
logd(p_depth + 2, "portal CLIP_OUTSIDE");
continue;
}
// construct new planes
LocalVector<Plane, int32_t> planes;
if (p_first_portal_id != -1) {
// add new planes
const VSPortal &first_portal = _portal_renderer->get_portal(p_first_portal_id);
portal.add_pvs_planes(first_portal, p_first_portal_outgoing, planes, outgoing != 0);
//#define GODOT_PVS_EXTRA_REJECT_TEST
#ifdef GODOT_PVS_EXTRA_REJECT_TEST
// extra reject test for pvs - was the previous portal points outside the planes formed by the new portal?
// not fully tested and not yet found a situation where needed, but will leave in in case testers find
// such a situation.
if (p_previous_portal_id != -1) {
const VSPortal &prev_portal = _portal_renderer->get_portal(p_previous_portal_id);
if (prev_portal._pvs_is_outside_planes(planes)) {
continue;
}
}
#endif
}
// if portal is totally inside the planes, don't copy the old planes ..
// i.e. we can now cull using the portal and forget about the rest of the frustum (yay)
if (overall_res != VSPortal::ClipResult::CLIP_INSIDE) {
// if it WASNT totally inside the existing frustum, we also need to add any existing planes
// that cut the portal.
for (uint32_t n = 0; n < partial_planes.size(); n++)
planes.push_back(p_planes[partial_planes[n]]);
}
// hopefully the portal actually leads somewhere...
if (linked_room_id != -1) {
// we either pass on the first portal id, or we start
// it here, because we are looking through the first portal
int first_portal_id = p_first_portal_id;
if (first_portal_id == -1) {
first_portal_id = portal_id;
p_first_portal_outgoing = outgoing != 0;
}
trace_rooms_recursive(p_depth + 1, p_source_room_id, linked_room_id, first_portal_id, p_first_portal_outgoing, portal_id, planes, r_bitfield_rooms);
} // linked room is valid
}
}

View File

@ -0,0 +1,71 @@
/*************************************************************************/
/* portal_pvs_builder.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_PVS_BUILDER_H
#define PORTAL_PVS_BUILDER_H
#include "core/bitfield_dynamic.h"
#include "core/local_vector.h"
#include "core/math/plane.h"
//#define GODOT_PVS_SUPPORT_SAVE_FILE
class PortalRenderer;
class PVS;
class PVSBuilder {
struct Neighbours {
LocalVector<int32_t, int32_t> room_ids;
};
public:
void calculate_pvs(PortalRenderer &p_portal_renderer, String p_filename);
private:
#ifdef GODOT_PVS_SUPPORT_SAVE_FILE
bool load_pvs(String p_filename);
void save_pvs(String p_filename);
#endif
void find_neighbors(LocalVector<Neighbours> &r_neighbors);
void logd(int p_depth, String p_string);
void log(String p_string);
void trace_rooms_recursive(int p_depth, int p_source_room_id, int p_room_id, int p_first_portal_id, bool p_first_portal_outgoing, int p_previous_portal_id, const LocalVector<Plane, int32_t> &p_planes, BitFieldDynamic &r_bitfield_rooms);
void create_secondary_pvs(int p_room_id, const LocalVector<Neighbours> &p_neighbors, BitFieldDynamic &r_bitfield_rooms);
PortalRenderer *_portal_renderer = nullptr;
PVS *_pvs = nullptr;
static bool _log_active;
};
#endif

View File

@ -0,0 +1,989 @@
/*************************************************************************/
/* portal_renderer.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_renderer.h"
#include "portal_pvs_builder.h"
#include "servers/visual/visual_server_globals.h"
#include "servers/visual/visual_server_scene.h"
OcclusionHandle PortalRenderer::instance_moving_create(VSInstance *p_instance, RID p_instance_rid, bool p_global, AABB p_aabb) {
uint32_t pool_id = 0;
Moving *moving = _moving_pool.request(pool_id);
moving->global = p_global;
moving->pool_id = pool_id;
moving->instance = p_instance;
moving->room_id = -1;
#ifdef PORTAL_RENDERER_STORE_MOVING_RIDS
moving->instance_rid = p_instance_rid;
#endif
// add to the appropriate list
if (p_global) {
moving->list_id = _moving_list_global.size();
_moving_list_global.push_back(pool_id);
} else {
// do we need a roaming master list? not sure yet
moving->list_id = _moving_list_roaming.size();
_moving_list_roaming.push_back(pool_id);
}
OcclusionHandle handle = pool_id + 1;
instance_moving_update(handle, p_aabb);
return handle;
}
void PortalRenderer::instance_moving_update(OcclusionHandle p_handle, const AABB &p_aabb, bool p_force_reinsert) {
// we can ignore these, they are statics / dynamics, and don't need updating
// .. these should have been filtered out before calling the visual server...
DEV_ASSERT(!_occlusion_handle_is_in_room(p_handle));
p_handle--;
Moving &moving = _moving_pool[p_handle];
moving.exact_aabb = p_aabb;
// globals (e.g. interface elements) need their aabb updated irrespective of whether the system is loaded
if (!_loaded || moving.global) {
return;
}
// quick reject for most roaming cases
if (!p_force_reinsert && moving.expanded_aabb.encloses(p_aabb)) {
return;
}
// using an expanded aabb allows us to make 'no op' moves
// where the new aabb is within the expanded
moving.expanded_aabb = p_aabb.grow(_roaming_expansion_margin);
// if we got to here, it is roaming (moving between rooms)
// remove from current rooms
_moving_remove_from_rooms(p_handle);
// add to new rooms
Vector3 center = p_aabb.position + (p_aabb.size * 0.5);
int new_room = find_room_within(center, moving.room_id);
moving.room_id = new_room;
if (new_room != -1) {
_bitfield_rooms.blank();
sprawl_roaming(p_handle, moving, new_room, true);
}
}
void PortalRenderer::_rghost_remove_from_rooms(uint32_t p_pool_id) {
RGhost &moving = _rghost_pool[p_pool_id];
// if we have unloaded the rooms and we try this, it will crash
if (_loaded) {
for (int n = 0; n < moving._rooms.size(); n++) {
VSRoom &room = get_room(moving._rooms[n]);
room.remove_rghost(p_pool_id);
}
}
// moving is now in no rooms
moving._rooms.clear();
}
void PortalRenderer::_moving_remove_from_rooms(uint32_t p_moving_pool_id) {
Moving &moving = _moving_pool[p_moving_pool_id];
// if we have unloaded the rooms and we try this, it will crash
if (_loaded) {
for (int n = 0; n < moving._rooms.size(); n++) {
VSRoom &room = get_room(moving._rooms[n]);
room.remove_roamer(p_moving_pool_id);
}
}
// moving is now in no rooms
moving._rooms.clear();
}
void PortalRenderer::_debug_print_global_list() {
_log("globals:");
for (int n = 0; n < _moving_list_global.size(); n++) {
uint32_t id = _moving_list_global[n];
const Moving &moving = _moving_pool[id];
_log("\t" + _addr_to_string(&moving));
}
}
void PortalRenderer::_log(String p_string, int p_priority) {
// change this for more debug output ..
// not selectable at runtime yet.
if (p_priority >= 1) {
print_line(p_string);
} else {
print_verbose(p_string);
}
}
void PortalRenderer::instance_moving_destroy(OcclusionHandle p_handle) {
// deleting an instance that is assigned to a room (STATIC or DYNAMIC)
// is special, it must set the PortalRenderer into unloaded state, because
// there will now be a dangling reference to the instance that was destroyed.
// The alternative is to remove the reference, but this is not currently supported
// (it would mean rejigging rooms etc)
if (_occlusion_handle_is_in_room(p_handle)) {
_ensure_unloaded();
return;
}
p_handle--;
Moving *moving = &_moving_pool[p_handle];
// if a roamer, remove from any current rooms
if (!moving->global) {
_moving_remove_from_rooms(p_handle);
}
// remove from list (and keep in sync)
uint32_t list_id = moving->list_id;
if (moving->global) {
_moving_list_global.remove_unordered(list_id);
// keep the replacement moving in sync with the correct list Id
if (list_id < (uint32_t)_moving_list_global.size()) {
uint32_t replacement_id = _moving_list_global[list_id];
Moving &replacement = _moving_pool[replacement_id];
replacement.list_id = list_id;
}
} else {
_moving_list_roaming.remove_unordered(list_id);
// keep the replacement moving in sync with the correct list Id
if (list_id < (uint32_t)_moving_list_roaming.size()) {
uint32_t replacement_id = _moving_list_roaming[list_id];
Moving &replacement = _moving_pool[replacement_id];
replacement.list_id = list_id;
}
}
moving->destroy();
// can now free the moving
_moving_pool.free(p_handle);
}
PortalHandle PortalRenderer::portal_create() {
uint32_t pool_id = 0;
VSPortal *portal = _portal_pool.request(pool_id);
// explicit constructor
portal->create();
portal->_portal_id = _portal_pool_ids.size();
_portal_pool_ids.push_back(pool_id);
// plus one based handles, 0 is unset
pool_id++;
return pool_id;
}
void PortalRenderer::portal_destroy(PortalHandle p_portal) {
ERR_FAIL_COND(!p_portal);
_ensure_unloaded();
// plus one based
p_portal--;
// remove from list of valid portals
VSPortal &portal = _portal_pool[p_portal];
int portal_id = portal._portal_id;
// we need to replace the last element in the list
_portal_pool_ids.remove_unordered(portal_id);
// and reset the id of the portal that was the replacement
if (portal_id < _portal_pool_ids.size()) {
int replacement_pool_id = _portal_pool_ids[portal_id];
VSPortal &replacement = _portal_pool[replacement_pool_id];
replacement._portal_id = portal_id;
}
// explicitly run destructor
_portal_pool[p_portal].destroy();
// return to the pool
_portal_pool.free(p_portal);
}
void PortalRenderer::portal_set_geometry(PortalHandle p_portal, const Vector<Vector3> &p_points) {
ERR_FAIL_COND(!p_portal);
p_portal--; // plus 1 based
VSPortal &portal = _portal_pool[p_portal];
portal._pts_world = p_points;
if (portal._pts_world.size() < 3) {
WARN_PRINT("Portal must have at least 3 vertices");
return;
}
// create plane from points
// Allow averaging in case of wonky portals.
// first calculate average normal
Vector3 average_normal = Vector3(0, 0, 0);
for (int t = 2; t < (int)portal._pts_world.size(); t++) {
Plane p = Plane(portal._pts_world[0], portal._pts_world[t - 1], portal._pts_world[t]);
average_normal += p.normal;
}
// average normal
average_normal /= portal._pts_world.size() - 2;
// detect user error
ERR_FAIL_COND_MSG(average_normal.length() < 0.1, "Nonsense portal detected, normals should be consistent");
if (average_normal.length() < 0.7) {
WARN_PRINT("Wonky portal detected, you may see culling errors");
}
// calc average point
Vector3 average_pt = Vector3(0, 0, 0);
for (unsigned int n = 0; n < portal._pts_world.size(); n++) {
average_pt += portal._pts_world[n];
}
average_pt /= portal._pts_world.size();
// use the average point and normal to derive the plane
portal._plane = Plane(average_pt, average_normal);
// aabb
AABB &bb = portal._aabb;
bb.position = p_points[0];
bb.size = Vector3(0, 0, 0);
for (int n = 1; n < p_points.size(); n++) {
bb.expand_to(p_points[n]);
}
}
void PortalRenderer::portal_link(PortalHandle p_portal, RoomHandle p_room_from, RoomHandle p_room_to, bool p_two_way) {
ERR_FAIL_COND(!p_portal);
p_portal--; // plus 1 based
VSPortal &portal = _portal_pool[p_portal];
ERR_FAIL_COND(!p_room_from);
p_room_from--;
VSRoom &room_from = _room_pool[p_room_from];
ERR_FAIL_COND(!p_room_to);
p_room_to--;
VSRoom &room_to = _room_pool[p_room_to];
portal._linkedroom_ID[0] = room_from._room_ID;
portal._linkedroom_ID[1] = room_to._room_ID;
// is the portal internal? internal portals are treated differently
portal._internal = room_from._priority > room_to._priority;
// if it is internal, mark the outer room as containing an internal room.
// this is used for rooms lookup.
if (portal._internal) {
room_to._contains_internal_rooms = true;
}
_log("portal_link from room " + itos(room_from._room_ID) + " to room " + itos(room_to._room_ID));
room_from._portal_ids.push_back(portal._portal_id);
// one way portals simply aren't added to the destination room, so they don't get seen through
if (p_two_way) {
room_to._portal_ids.push_back(portal._portal_id);
}
}
void PortalRenderer::portal_set_active(PortalHandle p_portal, bool p_active) {
ERR_FAIL_COND(!p_portal);
p_portal--; // plus 1 based
VSPortal &portal = _portal_pool[p_portal];
portal._active = p_active;
}
RoomGroupHandle PortalRenderer::roomgroup_create() {
uint32_t pool_id = 0;
VSRoomGroup *rg = _roomgroup_pool.request(pool_id);
// explicit constructor
rg->create();
// plus one based handles, 0 is unset
pool_id++;
return pool_id;
}
void PortalRenderer::roomgroup_prepare(RoomGroupHandle p_roomgroup, ObjectID p_roomgroup_object_id) {
// plus one based
p_roomgroup--;
VSRoomGroup &rg = _roomgroup_pool[p_roomgroup];
rg._godot_instance_ID = p_roomgroup_object_id;
}
void PortalRenderer::roomgroup_destroy(RoomGroupHandle p_roomgroup) {
ERR_FAIL_COND(!p_roomgroup);
_ensure_unloaded();
// plus one based
p_roomgroup--;
VSRoomGroup &rg = _roomgroup_pool[p_roomgroup];
// explicitly run destructor
rg.destroy();
// return to the pool
_roomgroup_pool.free(p_roomgroup);
}
void PortalRenderer::roomgroup_add_room(RoomGroupHandle p_roomgroup, RoomHandle p_room) {
// plus one based
p_roomgroup--;
VSRoomGroup &rg = _roomgroup_pool[p_roomgroup];
p_room--;
// add to room group
rg._room_ids.push_back(p_room);
// add the room group to the room
VSRoom &room = _room_pool[p_room];
room._roomgroup_ids.push_back(p_roomgroup);
}
// Cull Instances
RGhostHandle PortalRenderer::rghost_create(ObjectID p_object_id, const AABB &p_aabb) {
uint32_t pool_id = 0;
RGhost *moving = _rghost_pool.request(pool_id);
moving->pool_id = pool_id;
moving->object_id = p_object_id;
moving->room_id = -1;
RGhostHandle handle = pool_id + 1;
rghost_update(handle, p_aabb);
return handle;
}
void PortalRenderer::rghost_update(RGhostHandle p_handle, const AABB &p_aabb, bool p_force_reinsert) {
if (!_loaded) {
return;
}
p_handle--;
RGhost &moving = _rghost_pool[p_handle];
moving.exact_aabb = p_aabb;
// quick reject for most roaming cases
if (!p_force_reinsert && moving.expanded_aabb.encloses(p_aabb)) {
return;
}
// using an expanded aabb allows us to make 'no op' moves
// where the new aabb is within the expanded
moving.expanded_aabb = p_aabb.grow(_roaming_expansion_margin);
// if we got to here, it is roaming (moving between rooms)
// remove from current rooms
_rghost_remove_from_rooms(p_handle);
// add to new rooms
Vector3 center = p_aabb.position + (p_aabb.size * 0.5);
int new_room = find_room_within(center, moving.room_id);
moving.room_id = new_room;
if (new_room != -1) {
_bitfield_rooms.blank();
sprawl_roaming(p_handle, moving, new_room, false);
}
}
void PortalRenderer::rghost_destroy(RGhostHandle p_handle) {
p_handle--;
RGhost *moving = &_rghost_pool[p_handle];
// if a roamer, remove from any current rooms
_rghost_remove_from_rooms(p_handle);
moving->destroy();
// can now free the moving
_rghost_pool.free(p_handle);
}
// Rooms
RoomHandle PortalRenderer::room_create() {
uint32_t pool_id = 0;
VSRoom *room = _room_pool.request(pool_id);
// explicit constructor
room->create();
// keep our own internal list of rooms
room->_room_ID = _room_pool_ids.size();
_room_pool_ids.push_back(pool_id);
// plus one based handles, 0 is unset
pool_id++;
return pool_id;
}
void PortalRenderer::room_destroy(RoomHandle p_room) {
ERR_FAIL_COND(!p_room);
_ensure_unloaded();
// plus one based
p_room--;
// remove from list of valid rooms
VSRoom &room = _room_pool[p_room];
int room_id = room._room_ID;
// we need to replace the last element in the list
_room_pool_ids.remove_unordered(room_id);
// and reset the id of the portal that was the replacement
if (room_id < _room_pool_ids.size()) {
int replacement_pool_id = _room_pool_ids[room_id];
VSRoom &replacement = _room_pool[replacement_pool_id];
replacement._room_ID = room_id;
}
// explicitly run destructor
_room_pool[p_room].destroy();
// return to the pool
_room_pool.free(p_room);
}
OcclusionHandle PortalRenderer::room_add_ghost(RoomHandle p_room, ObjectID p_object_id, const AABB &p_aabb) {
ERR_FAIL_COND_V(!p_room, 0);
p_room--; // plus one based
VSStaticGhost ghost;
ghost.object_id = p_object_id;
_static_ghosts.push_back(ghost);
// sprawl immediately
// precreate a useful bitfield of rooms for use in sprawling
if ((int)_bitfield_rooms.get_num_bits() != get_num_rooms()) {
_bitfield_rooms.create(get_num_rooms());
}
// only can do if rooms exist
if (get_num_rooms()) {
// the last one was just added
int ghost_id = _static_ghosts.size() - 1;
// create a bitfield to indicate which rooms have been
// visited already, to prevent visiting rooms multiple times
_bitfield_rooms.blank();
sprawl_static_ghost(ghost_id, p_aabb, p_room);
}
return OCCLUSION_HANDLE_ROOM_BIT;
}
OcclusionHandle PortalRenderer::room_add_instance(RoomHandle p_room, RID p_instance, const AABB &p_aabb, bool p_dynamic, const Vector<Vector3> &p_object_pts) {
ERR_FAIL_COND_V(!p_room, 0);
p_room--; // plus one based
VSRoom &room = _room_pool[p_room];
VSStatic stat;
stat.instance = p_instance;
stat.source_room_id = room._room_ID;
stat.dynamic = p_dynamic;
stat.aabb = p_aabb;
_statics.push_back(stat);
// sprawl immediately
// precreate a useful bitfield of rooms for use in sprawling
if ((int)_bitfield_rooms.get_num_bits() != get_num_rooms()) {
_bitfield_rooms.create(get_num_rooms());
}
// only can do if rooms exist
if (get_num_rooms()) {
// the last one was just added
int static_id = _statics.size() - 1;
// pop last static
const VSStatic &st = _statics[static_id];
// create a bitfield to indicate which rooms have been
// visited already, to prevent visiting rooms multiple times
_bitfield_rooms.blank();
if (p_object_pts.size()) {
sprawl_static_geometry(static_id, st, st.source_room_id, p_object_pts);
} else {
sprawl_static(static_id, st, st.source_room_id);
}
}
return OCCLUSION_HANDLE_ROOM_BIT;
}
void PortalRenderer::room_prepare(RoomHandle p_room, int32_t p_priority) {
ERR_FAIL_COND(!p_room);
p_room--; // plus one based
VSRoom &room = _room_pool[p_room];
room._priority = p_priority;
}
void PortalRenderer::room_set_bound(RoomHandle p_room, ObjectID p_room_object_id, const Vector<Plane> &p_convex, const AABB &p_aabb, const Vector<Vector3> &p_verts) {
ERR_FAIL_COND(!p_room);
p_room--; // plus one based
VSRoom &room = _room_pool[p_room];
room._planes = p_convex;
room._verts = p_verts;
room._aabb = p_aabb;
room._godot_instance_ID = p_room_object_id;
}
void PortalRenderer::_add_portal_to_convex_hull(LocalVector<Plane, int32_t> &p_planes, const Plane &p) {
for (int n = 0; n < p_planes.size(); n++) {
Plane &o = p_planes[n];
// this is a fudge factor for how close the portal can be to an existing plane
// to be to be considered the same ...
// to prevent needless extra checks.
// the epsilons should probably be more exact here than for the convex hull simplification, as it is
// fairly crucial that the portal planes are reasonably accurate for determining the hull.
// and because the portal plane is more important, we will REPLACE the existing similar plane
// with the portal plane.
const real_t d = 0.03; // 0.08f
if (Math::abs(p.d - o.d) > d) {
continue;
}
real_t dot = p.normal.dot(o.normal);
if (dot < 0.99) // 0.98f
{
continue;
}
// match!
// replace the existing plane
o = p;
return;
}
// there is no existing plane that is similar, create a new one especially for the portal
p_planes.push_back(p);
}
void PortalRenderer::_rooms_add_portals_to_convex_hulls() {
for (int n = 0; n < get_num_rooms(); n++) {
VSRoom &room = get_room(n);
for (int p = 0; p < room._portal_ids.size(); p++) {
const VSPortal &portal = get_portal(room._portal_ids[p]);
// everything depends on whether the portal is incoming or outgoing.
// if incoming we reverse the logic.
int outgoing = 1;
int room_a_id = portal._linkedroom_ID[0];
if (room_a_id != n) {
outgoing = 0;
DEV_ASSERT(portal._linkedroom_ID[1] == n);
}
// do not add internal portals to the convex hull of outer rooms!
if (!outgoing && portal._internal) {
continue;
}
// add the portal plane
Plane portal_plane = portal._plane;
if (!outgoing) {
portal_plane = -portal_plane;
}
// add if sufficiently different from existing convex hull planes
_add_portal_to_convex_hull(room._planes, portal_plane);
}
}
}
void PortalRenderer::rooms_finalize(bool p_generate_pvs, bool p_cull_using_pvs, bool p_use_secondary_pvs, bool p_use_signals, String p_pvs_filename) {
_gameplay_monitor.set_params(p_use_secondary_pvs, p_use_signals);
// portals should also bound the rooms, the room geometry may extend past the portal
_rooms_add_portals_to_convex_hulls();
// the trace results can never have more hits than the number of static objects
_trace_results.create(_statics.size());
// precreate a useful bitfield of rooms for use in sprawling, if not created already
// (may not be necessary but just in case, rooms with no statics etc)
_bitfield_rooms.create(_room_pool_ids.size());
// the rooms looksup is a pre-calced grid structure for faster lookup of the nearest room
// from position
_rooms_lookup_bsp.create(*this);
// calculate the roaming expansion margin based on the average room size
Vector3 total_size = Vector3(0, 0, 0);
for (int n = 0; n < get_num_rooms(); n++) {
total_size += get_room(n)._aabb.size;
}
if (get_num_rooms()) {
total_size /= get_num_rooms();
AABB temp;
temp.size = total_size;
// longest axis of average room * fudge factor
_roaming_expansion_margin = temp.get_longest_axis_size() * 0.08;
}
// calculate PVS
if (p_generate_pvs) {
PVSBuilder pvs;
pvs.calculate_pvs(*this, p_pvs_filename);
_cull_using_pvs = p_cull_using_pvs; // hard code to on for test
} else {
_cull_using_pvs = false;
}
_loaded = true;
// all the roaming objects need to be sprawled into the rooms
// (they may have been created before the rooms)
_load_finalize_roaming();
// allow deleting any intermediate data
for (int n = 0; n < get_num_rooms(); n++) {
get_room(n).cleanup_after_conversion();
}
// this should probably have some thread protection, but I doubt it matters
// as this will worst case give wrong result for a frame
Engine::get_singleton()->set_portals_active(true);
print_line("Room conversion complete. " + itos(_room_pool_ids.size()) + " rooms, " + itos(_portal_pool_ids.size()) + " portals.");
}
void PortalRenderer::sprawl_static_geometry(int p_static_id, const VSStatic &p_static, int p_room_id, const Vector<Vector3> &p_object_pts) {
// set, and if room already done, ignore
if (!_bitfield_rooms.check_and_set(p_room_id))
return;
VSRoom &room = get_room(p_room_id);
room._static_ids.push_back(p_static_id);
// go through portals
for (int p = 0; p < room._portal_ids.size(); p++) {
const VSPortal &portal = get_portal(room._portal_ids[p]);
int room_to_id = portal.geometry_crosses_portal(p_room_id, p_static.aabb, p_object_pts);
if (room_to_id != -1) {
_log(String(Variant(p_static.aabb)) + " crosses portal");
sprawl_static_geometry(p_static_id, p_static, room_to_id, p_object_pts);
}
}
}
void PortalRenderer::sprawl_static_ghost(int p_ghost_id, const AABB &p_aabb, int p_room_id) {
// set, and if room already done, ignore
if (!_bitfield_rooms.check_and_set(p_room_id)) {
return;
}
VSRoom &room = get_room(p_room_id);
room._static_ghost_ids.push_back(p_ghost_id);
// go through portals
for (int p = 0; p < room._portal_ids.size(); p++) {
const VSPortal &portal = get_portal(room._portal_ids[p]);
int room_to_id = portal.crosses_portal(p_room_id, p_aabb, true);
if (room_to_id != -1) {
_log(String(Variant(p_aabb)) + " crosses portal");
sprawl_static_ghost(p_ghost_id, p_aabb, room_to_id);
}
}
}
void PortalRenderer::sprawl_static(int p_static_id, const VSStatic &p_static, int p_room_id) {
// set, and if room already done, ignore
if (!_bitfield_rooms.check_and_set(p_room_id)) {
return;
}
VSRoom &room = get_room(p_room_id);
room._static_ids.push_back(p_static_id);
// go through portals
for (int p = 0; p < room._portal_ids.size(); p++) {
const VSPortal &portal = get_portal(room._portal_ids[p]);
int room_to_id = portal.crosses_portal(p_room_id, p_static.aabb, true);
if (room_to_id != -1) {
_log(String(Variant(p_static.aabb)) + " crosses portal");
sprawl_static(p_static_id, p_static, room_to_id);
}
}
}
void PortalRenderer::_load_finalize_roaming() {
for (int n = 0; n < _moving_list_roaming.size(); n++) {
uint32_t pool_id = _moving_list_roaming[n];
Moving &moving = _moving_pool[pool_id];
const AABB &aabb = moving.exact_aabb;
OcclusionHandle handle = pool_id + 1;
instance_moving_update(handle, aabb, true);
}
for (int n = 0; n < _rghost_pool.active_size(); n++) {
RGhost &moving = _rghost_pool.get_active(n);
const AABB &aabb = moving.exact_aabb;
rghost_update(_rghost_pool.get_active_id(n) + 1, aabb, true);
}
}
void PortalRenderer::sprawl_roaming(uint32_t p_mover_pool_id, MovingBase &r_moving, int p_room_id, bool p_moving_or_ghost) {
// set, and if room already done, ignore
if (!_bitfield_rooms.check_and_set(p_room_id)) {
return;
}
// add to the room
VSRoom &room = get_room(p_room_id);
if (p_moving_or_ghost) {
room.add_roamer(p_mover_pool_id);
} else {
room.add_rghost(p_mover_pool_id);
}
// add the room to the mover
r_moving._rooms.push_back(p_room_id);
// go through portals
for (int p = 0; p < room._portal_ids.size(); p++) {
const VSPortal &portal = get_portal(room._portal_ids[p]);
int room_to_id = portal.crosses_portal(p_room_id, r_moving.expanded_aabb);
if (room_to_id != -1) {
// _log(String(Variant(p_static.aabb)) + " crosses portal");
sprawl_roaming(p_mover_pool_id, r_moving, room_to_id, p_moving_or_ghost);
}
}
}
// This gets called when you delete an instance the the room system depends on
void PortalRenderer::_ensure_unloaded() {
if (_loaded) {
_loaded = false;
_log("Portal system unloaded.", 1);
// this should probably have some thread protection, but I doubt it matters
// as this will worst case give wrong result for a frame
Engine::get_singleton()->set_portals_active(false);
}
}
void PortalRenderer::rooms_and_portals_clear() {
_loaded = false;
_statics.clear();
_static_ghosts.clear();
// the rooms and portals should remove their id when they delete themselves
// from the scene tree by calling room_destroy and portal_destroy ...
// therefore there should be no need to clear these here
// _room_pool_ids.clear();
// _portal_pool_ids.clear();
_rooms_lookup_bsp.clear();
// clear the portals out of each existing room
for (int n = 0; n < get_num_rooms(); n++) {
VSRoom &room = get_room(n);
room.rooms_and_portals_clear();
}
for (int n = 0; n < get_num_portals(); n++) {
VSPortal &portal = get_portal(n);
portal.rooms_and_portals_clear();
}
// when the rooms_and_portals_clear message is sent,
// we want to remove all references to old rooms in the moving
// objects, to prevent dangling references.
for (int n = 0; n < get_num_moving_globals(); n++) {
Moving &moving = get_pool_moving(_moving_list_global[n]);
moving.rooms_and_portals_clear();
}
for (int n = 0; n < _moving_list_roaming.size(); n++) {
Moving &moving = get_pool_moving(_moving_list_roaming[n]);
moving.rooms_and_portals_clear();
}
for (int n = 0; n < _rghost_pool.active_size(); n++) {
RGhost &moving = _rghost_pool.get_active(n);
moving.rooms_and_portals_clear();
}
_pvs.clear();
}
void PortalRenderer::rooms_override_camera(bool p_override, const Vector3 &p_point, const Vector<Plane> *p_convex) {
_override_camera = p_override;
_override_camera_pos = p_point;
if (p_convex) {
_override_camera_planes = *p_convex;
}
}
void PortalRenderer::rooms_update_gameplay_monitor(const Vector<Vector3> &p_camera_positions) {
// is the pvs loaded?
if (!_loaded || !_pvs.is_loaded()) {
if (!_pvs.is_loaded()) {
WARN_PRINT_ONCE("RoomManager PVS is required for this functionality");
}
return;
}
int *source_rooms = (int *)alloca(sizeof(int) * p_camera_positions.size());
int num_source_rooms = 0;
for (int n = 0; n < p_camera_positions.size(); n++) {
int source_room_id = find_room_within(p_camera_positions[n]);
if (source_room_id == -1) {
continue;
}
source_rooms[num_source_rooms++] = source_room_id;
}
_gameplay_monitor.update_gameplay(*this, source_rooms, num_source_rooms);
}
int PortalRenderer::cull_convex_implementation(const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_result_max, uint32_t p_mask, int32_t &r_previous_room_id_hint) {
// start room
int start_room_id = find_room_within(p_point, r_previous_room_id_hint);
// return the previous room hint
r_previous_room_id_hint = start_room_id;
if (start_room_id == -1) {
return -1;
}
// planes must be in CameraMatrix order
DEV_ASSERT(p_convex.size() == 6);
LocalVector<Plane> planes;
planes = p_convex;
_trace_results.clear();
if (!_debug_sprawl) {
_tracer.trace(*this, p_point, planes, start_room_id, _trace_results); //, near_and_far_planes);
} else {
_tracer.trace_debug_sprawl(*this, p_point, start_room_id, _trace_results);
}
int num_results = _trace_results.visible_static_ids.size();
int out_count = 0;
for (int n = 0; n < num_results; n++) {
uint32_t static_id = _trace_results.visible_static_ids[n];
RID static_rid = _statics[static_id].instance;
VSInstance *instance = VSG::scene->_instance_get_from_rid(static_rid);
if (VSG::scene->_instance_cull_check(instance, p_mask)) {
p_result_array[out_count++] = instance;
if (out_count >= p_result_max) {
break;
}
}
}
// results could be full up already
if (out_count >= p_result_max) {
return out_count;
}
// add the roaming results
// cap to the maximum results
int num_roam_hits = _trace_results.visible_roamer_pool_ids.size();
// translate
for (int n = 0; n < num_roam_hits; n++) {
const Moving &moving = get_pool_moving(_trace_results.visible_roamer_pool_ids[n]);
if (VSG::scene->_instance_cull_check(moving.instance, p_mask)) {
p_result_array[out_count++] = moving.instance;
if (out_count >= p_result_max) {
break;
}
}
}
// results could be full up already
if (out_count >= p_result_max) {
return out_count;
}
out_count = _tracer.trace_globals(planes, p_result_array, out_count, p_result_max, p_mask);
return out_count;
}
String PortalRenderer::_rid_to_string(RID p_rid) {
return _addr_to_string(p_rid.get_data());
}
String PortalRenderer::_addr_to_string(const void *p_addr) {
return String::num_uint64((uint64_t)p_addr, 16);
}

View File

@ -0,0 +1,292 @@
/*************************************************************************/
/* portal_renderer.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_RENDERER_H
#define PORTAL_RENDERER_H
#include "core/pooled_list.h"
#include "core/vector.h"
#include "portal_gameplay_monitor.h"
#include "portal_pvs.h"
#include "portal_rooms_bsp.h"
#include "portal_tracer.h"
#include "portal_types.h"
struct VSStatic {
// the lifetime of statics is not strictly monitored like moving objects
// therefore we store a RID which could return NULL if the object has been deleted
RID instance;
AABB aabb;
// statics are placed in a room, but they can optionally sprawl to other rooms
// if large (like lights)
uint32_t source_room_id;
// dynamics will request their AABB each frame
// from the visual server in case they have moved.
// But they will NOT update the rooms they are in...
// so this works well for e.g. moving platforms, but not for objects
// that will move between rooms.
uint32_t dynamic;
};
// static / dynamic visibility notifiers.
// ghost objects are not culled, but are present in rooms
// and expect to receive gameplay notifications
struct VSStaticGhost {
ObjectID object_id;
uint32_t last_tick_hit = 0;
uint32_t last_gameplay_tick_hit = 0;
};
class PortalRenderer {
public:
// use most significant bit to store whether an instance is being used in the room system
// in which case, deleting such an instance should deactivate the portal system to prevent
// crashes due to dangling references to instances.
static const uint32_t OCCLUSION_HANDLE_ROOM_BIT = 1 << 31;
struct MovingBase {
// when the rooms_and_portals_clear message is sent,
// we want to remove all references to old rooms in the moving
// objects, to prevent dangling references.
void rooms_and_portals_clear() { destroy(); }
void destroy() {
_rooms.clear();
room_id = -1;
}
// the expanded aabb allows objects to move on most frames
// without needing to determine a change of room
AABB expanded_aabb;
// exact aabb of the object should be used for culling
AABB exact_aabb;
// which is the primary room this moving object is in
// (it may sprawl into multiple rooms)
int32_t room_id;
// id in the allocation pool
uint32_t pool_id;
uint32_t last_tick_hit = 0;
uint32_t last_gameplay_tick_hit = 0;
// room ids of rooms this moving object is sprawled into
LocalVector<uint32_t, int32_t> _rooms;
};
struct Moving : public MovingBase {
// either roaming or global
bool global;
// in _moving_lists .. not the same as pool ID (handle)
uint32_t list_id;
// a void pointer, but this is ultimately a pointer to a VisualServerScene::Instance
// (can't have direct pointer because it is a nested class...)
VSInstance *instance;
#ifdef PORTAL_RENDERER_STORE_MOVING_RIDS
// primarily for testing
RID instance_rid;
#endif
};
// So far the only roaming ghosts are VisibilityNotifiers.
// this will always be roaming... statics and dynamics are handled separately,
// and global ghosts do not get created.
struct RGhost : public MovingBase {
ObjectID object_id;
};
PortalHandle portal_create();
void portal_destroy(PortalHandle p_portal);
void portal_set_geometry(PortalHandle p_portal, const Vector<Vector3> &p_points);
void portal_link(PortalHandle p_portal, RoomHandle p_room_from, RoomHandle p_room_to, bool p_two_way);
void portal_set_active(PortalHandle p_portal, bool p_active);
RoomGroupHandle roomgroup_create();
void roomgroup_prepare(RoomGroupHandle p_roomgroup, ObjectID p_roomgroup_object_id);
void roomgroup_destroy(RoomGroupHandle p_roomgroup);
void roomgroup_add_room(RoomGroupHandle p_roomgroup, RoomHandle p_room);
// Rooms
RoomHandle room_create();
void room_destroy(RoomHandle p_room);
OcclusionHandle room_add_instance(RoomHandle p_room, RID p_instance, const AABB &p_aabb, bool p_dynamic, const Vector<Vector3> &p_object_pts);
OcclusionHandle room_add_ghost(RoomHandle p_room, ObjectID p_object_id, const AABB &p_aabb);
void room_set_bound(RoomHandle p_room, ObjectID p_room_object_id, const Vector<Plane> &p_convex, const AABB &p_aabb, const Vector<Vector3> &p_verts);
void room_prepare(RoomHandle p_room, int32_t p_priority);
void rooms_and_portals_clear();
void rooms_finalize(bool p_generate_pvs, bool p_cull_using_pvs, bool p_use_secondary_pvs, bool p_use_signals, String p_pvs_filename);
void rooms_override_camera(bool p_override, const Vector3 &p_point, const Vector<Plane> *p_convex);
void rooms_set_active(bool p_active) { _active = p_active; }
void rooms_set_params(int p_portal_depth_limit) { _tracer.set_depth_limit(p_portal_depth_limit); }
void rooms_set_cull_using_pvs(bool p_enable) { _cull_using_pvs = p_enable; }
void rooms_update_gameplay_monitor(const Vector<Vector3> &p_camera_positions);
// for use in the editor only, to allow a cheap way of turning off portals
// if there has been a change, e.g. moving a room etc.
void rooms_unload() { _ensure_unloaded(); }
// debugging
void set_debug_sprawl(bool p_active) { _debug_sprawl = p_active; }
// this section handles moving objects - roaming (change rooms) and globals (not in any room)
OcclusionHandle instance_moving_create(VSInstance *p_instance, RID p_instance_rid, bool p_global, AABB p_aabb);
void instance_moving_update(OcclusionHandle p_handle, const AABB &p_aabb, bool p_force_reinsert = false);
void instance_moving_destroy(OcclusionHandle p_handle);
// spatial derived roamers (non VisualInstances that still need to be portal culled, especially VisibilityNotifiers)
RGhostHandle rghost_create(ObjectID p_object_id, const AABB &p_aabb);
void rghost_update(RGhostHandle p_handle, const AABB &p_aabb, bool p_force_reinsert = false);
void rghost_destroy(RGhostHandle p_handle);
// note that this relies on a 'frustum' type cull, from a point, and that the planes are specified as in
// CameraMatrix, i.e.
// order PLANE_NEAR,PLANE_FAR,PLANE_LEFT,PLANE_TOP,PLANE_RIGHT,PLANE_BOTTOM
int cull_convex(const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_result_max, uint32_t p_mask, int32_t &r_previous_room_id_hint) {
if (!_override_camera)
return cull_convex_implementation(p_point, p_convex, p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
return cull_convex_implementation(_override_camera_pos, _override_camera_planes, p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
}
int cull_convex_implementation(const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_result_max, uint32_t p_mask, int32_t &r_previous_room_id_hint);
bool is_active() const { return _active && _loaded; }
VSStatic &get_static(int p_id) { return _statics[p_id]; }
const VSStatic &get_static(int p_id) const { return _statics[p_id]; }
int32_t get_num_rooms() const { return _room_pool_ids.size(); }
VSRoom &get_room(int p_id) { return _room_pool[_room_pool_ids[p_id]]; }
const VSRoom &get_room(int p_id) const { return _room_pool[_room_pool_ids[p_id]]; }
int32_t get_num_portals() const { return _portal_pool_ids.size(); }
VSPortal &get_portal(int p_id) { return _portal_pool[_portal_pool_ids[p_id]]; }
const VSPortal &get_portal(int p_id) const { return _portal_pool[_portal_pool_ids[p_id]]; }
int32_t get_num_moving_globals() const { return _moving_list_global.size(); }
const Moving &get_moving_global(uint32_t p_id) const { return _moving_pool[_moving_list_global[p_id]]; }
Moving &get_pool_moving(uint32_t p_pool_id) { return _moving_pool[p_pool_id]; }
const Moving &get_pool_moving(uint32_t p_pool_id) const { return _moving_pool[p_pool_id]; }
RGhost &get_pool_rghost(uint32_t p_pool_id) { return _rghost_pool[p_pool_id]; }
const RGhost &get_pool_rghost(uint32_t p_pool_id) const { return _rghost_pool[p_pool_id]; }
VSStaticGhost &get_static_ghost(uint32_t p_id) { return _static_ghosts[p_id]; }
VSRoomGroup &get_roomgroup(uint32_t p_pool_id) { return _roomgroup_pool[p_pool_id]; }
PVS &get_pvs() { return _pvs; }
const PVS &get_pvs() const { return _pvs; }
bool get_cull_using_pvs() const { return _cull_using_pvs; }
private:
int find_room_within(const Vector3 &p_pos, int p_previous_room_id = -1) {
return _rooms_lookup_bsp.find_room_within(*this, p_pos, p_previous_room_id);
}
void sprawl_static(int p_static_id, const VSStatic &p_static, int p_room_id);
void sprawl_static_geometry(int p_static_id, const VSStatic &p_static, int p_room_id, const Vector<Vector3> &p_object_pts);
void sprawl_static_ghost(int p_ghost_id, const AABB &p_aabb, int p_room_id);
void _load_finalize_roaming();
void sprawl_roaming(uint32_t p_mover_pool_id, MovingBase &r_moving, int p_room_id, bool p_moving_or_ghost);
void _moving_remove_from_rooms(uint32_t p_moving_pool_id);
void _rghost_remove_from_rooms(uint32_t p_pool_id);
void _ensure_unloaded();
void _rooms_add_portals_to_convex_hulls();
void _add_portal_to_convex_hull(LocalVector<Plane, int32_t> &p_planes, const Plane &p);
void _debug_print_global_list();
bool _occlusion_handle_is_in_room(OcclusionHandle p_h) const {
return p_h == OCCLUSION_HANDLE_ROOM_BIT;
}
void _log(String p_string, int p_priority = 0);
// note this is vulnerable to crashes, we must monitor for deletion of rooms
LocalVector<uint32_t, int32_t> _room_pool_ids;
LocalVector<uint32_t, int32_t> _portal_pool_ids;
LocalVector<VSStatic, int32_t> _statics;
LocalVector<VSStaticGhost, int32_t> _static_ghosts;
// all rooms and portals are allocated from pools.
PooledList<VSPortal> _portal_pool;
PooledList<VSRoom> _room_pool;
PooledList<VSRoomGroup> _roomgroup_pool;
// moving objects, global and roaming
PooledList<Moving> _moving_pool;
TrackedPooledList<RGhost> _rghost_pool;
LocalVector<uint32_t, int32_t> _moving_list_global;
LocalVector<uint32_t, int32_t> _moving_list_roaming;
PVS _pvs;
bool _active = true;
bool _loaded = false;
bool _debug_sprawl = false;
// if the pvs is generated, we can either cull using dynamic portals or PVS
bool _cull_using_pvs = false;
PortalTracer _tracer;
PortalTracer::TraceResult _trace_results;
PortalRoomsBSP _rooms_lookup_bsp;
PortalGameplayMonitor _gameplay_monitor;
// when moving roaming objects, we expand their bound
// to prevent too many updates.
real_t _roaming_expansion_margin = 1.0;
// a bitfield to indicate which rooms have been
// visited already in sprawling, to prevent visiting rooms multiple times
BitFieldDynamic _bitfield_rooms;
bool _override_camera = false;
Vector3 _override_camera_pos;
LocalVector<Plane, int32_t> _override_camera_planes;
public:
static String _rid_to_string(RID p_rid);
static String _addr_to_string(const void *p_addr);
};
#endif

View File

@ -0,0 +1,641 @@
/*************************************************************************/
/* portal_rooms_bsp.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_rooms_bsp.h"
#include "core/math/geometry.h"
#include "core/math/plane.h"
#include "core/print_string.h"
#include "core/variant.h"
#include "portal_renderer.h"
// #define GODOT_VERBOSE_PORTAL_ROOMS_BSP
void PortalRoomsBSP::_log(String p_string) {
#ifdef GODOT_VERBOSE_PORTAL_ROOMS_BSP
print_line(p_string);
#endif
}
// rooms which contain internal rooms cannot use the optimization where it terminates the search for
// room within if inside the previous room. We can't use just use the rooms already marked as internal due
// to a portal leading to them, because the internal room network may spread into another room (e.g. terrain)
// which has internal room exit portal. So we need to detect manually all cases of overlap of internal rooms,
// and set the flag.
void PortalRoomsBSP::detect_internal_room_containment(PortalRenderer &r_portal_renderer) {
int num_rooms = r_portal_renderer.get_num_rooms();
for (int n = 0; n < num_rooms; n++) {
VSRoom &room = r_portal_renderer.get_room(n);
if (room._contains_internal_rooms) {
// already established it contains internal rooms, no need to test
continue;
}
// safety
if (!room._planes.size()) {
continue;
}
for (int i = 0; i < num_rooms; i++) {
// don't test against ourself
if (n == i) {
continue;
}
// only interested in rooms with a higher priority, these are potential internal rooms
const VSRoom &other = r_portal_renderer.get_room(i);
if (other._priority <= room._priority) {
continue;
}
// quick aabb check first
if (!room._aabb.intersects(other._aabb)) {
continue;
}
// safety
if (!other._planes.size()) {
continue;
}
if (Geometry::convex_hull_intersects_convex_hull(&room._planes[0], room._planes.size(), &other._planes[0], other._planes.size())) {
// it intersects an internal room
room._contains_internal_rooms = true;
break;
}
}
}
}
int PortalRoomsBSP::find_room_within(const PortalRenderer &p_portal_renderer, const Vector3 &p_pos, int p_previous_room_id) const {
real_t closest = FLT_MAX;
int closest_room_id = -1;
int closest_priority = -10000;
// first try previous room
if (p_previous_room_id != -1) {
const VSRoom &prev_room = p_portal_renderer.get_room(p_previous_room_id);
// we can only use this shortcut if the room doesn't include internal rooms.
// otherwise the point may be inside more than one room, and we need to find the room of highest priority.
if (!prev_room._contains_internal_rooms) {
closest = prev_room.is_point_within(p_pos);
closest_room_id = p_previous_room_id;
if (closest < 0.0) {
return p_previous_room_id;
}
} else {
// don't mark it as checked later, as we haven't done it because it contains internal rooms
p_previous_room_id = -1;
}
}
int num_bsp_rooms = 0;
const int32_t *bsp_rooms = find_shortlist(p_pos, num_bsp_rooms);
if (!num_bsp_rooms) {
return -1;
}
// special case, only 1 room in the shortlist, no need to check further
if (num_bsp_rooms == 1) {
return bsp_rooms[0];
}
for (int n = 0; n < num_bsp_rooms; n++) {
int room_id = bsp_rooms[n];
// the previous room has already been done above, and will be in closest + closest_room_id
if (room_id == p_previous_room_id) {
continue;
}
const VSRoom &room = p_portal_renderer.get_room(room_id);
real_t dist = room.is_point_within(p_pos);
// if we are actually inside a room, unless we are dealing with internal rooms,
// we can terminate early, no need to search more
if (dist < 0.0) {
if (!room._contains_internal_rooms) {
// this will happen in most cases
closest = dist;
closest_room_id = room_id;
break;
} else {
// if we are inside, and there are internal rooms involved we need to be a bit careful.
// higher priority always wins (i.e. the internal room)
// but with equal priority we just choose the regular best fit.
if ((room._priority > closest_priority) || ((room._priority == closest_priority) && (dist < closest))) {
closest = dist;
closest_room_id = room_id;
closest_priority = room._priority;
continue;
}
}
} else {
// if we are outside we just pick the closest room, irrespective of priority
if (dist < closest) {
closest = dist;
closest_room_id = room_id;
// do NOT store the priority, we don't want an room that isn't a true hit
// overriding a hit inside the room
}
}
}
return closest_room_id;
}
const int32_t *PortalRoomsBSP::find_shortlist(const Vector3 &p_pt, int &r_num_rooms) const {
if (!_nodes.size()) {
r_num_rooms = 0;
return nullptr;
}
const Node *node = &_nodes[0];
while (!node->leaf) {
if (node->plane.is_point_over(p_pt)) {
node = &_nodes[node->child[1]];
} else {
node = &_nodes[node->child[0]];
}
}
r_num_rooms = node->num_ids;
return &_room_ids[node->first_id];
}
void PortalRoomsBSP::create(PortalRenderer &r_portal_renderer) {
clear();
_portal_renderer = &r_portal_renderer;
detect_internal_room_containment(r_portal_renderer);
// noop
int num_rooms = r_portal_renderer.get_num_rooms();
if (!num_rooms) {
return;
}
LocalVector<int32_t, int32_t> room_ids;
room_ids.resize(num_rooms);
for (int n = 0; n < num_rooms; n++) {
room_ids[n] = n;
}
_nodes.push_back(Node());
_nodes[0].clear();
build(0, room_ids);
#ifdef GODOT_VERBOSE_PORTAL_ROOMS_BSP
debug_print_tree();
#endif
_log("PortalRoomsBSP " + itos(_nodes.size()) + " nodes.");
}
void PortalRoomsBSP::build(int p_start_node_id, LocalVector<int32_t, int32_t> p_orig_room_ids) {
struct Element {
void clear() { room_ids.clear(); }
int node_id;
LocalVector<int32_t, int32_t> room_ids;
};
Element first;
first.node_id = p_start_node_id;
first.room_ids = p_orig_room_ids;
LocalVector<Element, int32_t> stack;
stack.reserve(1024);
stack.push_back(first);
int stack_size = 1;
while (stack_size) {
stack_size--;
Element curr = stack[stack_size];
Node *node = &_nodes[curr.node_id];
int best_fit = 0;
int best_portal_id = -1;
int best_room_a = -1;
int best_room_b = -1;
// find a splitting plane
for (int n = 0; n < curr.room_ids.size(); n++) {
// go through the portals in this room
int rid = curr.room_ids[n];
const VSRoom &room = _portal_renderer->get_room(rid);
for (int p = 0; p < room._portal_ids.size(); p++) {
int pid = room._portal_ids[p];
// only outward portals
const VSPortal &portal = _portal_renderer->get_portal(pid);
if (portal._linkedroom_ID[1] == rid) {
continue;
}
int fit = evaluate_portal(pid, curr.room_ids);
if (fit > best_fit) {
best_fit = fit;
best_portal_id = pid;
}
}
}
bool split_found = false;
Plane split_plane;
// if a splitting portal was found, we are done
if (best_portal_id != -1) {
_log("found splitting portal : " + itos(best_portal_id));
const VSPortal &portal = _portal_renderer->get_portal(best_portal_id);
split_plane = portal._plane;
split_found = true;
} else {
// let's try and find an arbitrary splitting plane
for (int a = 0; a < curr.room_ids.size(); a++) {
for (int b = a + 1; b < curr.room_ids.size(); b++) {
Plane plane;
// note the actual room ids are not the same as a and b!!
int room_a_id = curr.room_ids[a];
int room_b_id = curr.room_ids[b];
int fit = evaluate_room_split_plane(room_a_id, room_b_id, curr.room_ids, plane);
if (fit > best_fit) {
best_fit = fit;
// the room ids, NOT a and b
best_room_a = room_a_id;
best_room_b = room_b_id;
split_plane = plane;
}
} // for b through rooms
} // for a through rooms
if (best_room_a != -1) {
split_found = true;
// print_line("found splitting plane between rooms : " + itos(best_room_a) + " and " + itos(best_room_b));
}
}
// found either a portal plane or arbitrary
if (split_found) {
node->plane = split_plane;
// add to stack
stack_size += 2;
if (stack_size > stack.size()) {
stack.resize(stack_size);
}
stack[stack_size - 2].clear();
stack[stack_size - 1].clear();
LocalVector<int32_t, int32_t> &room_ids_back = stack[stack_size - 2].room_ids;
LocalVector<int32_t, int32_t> &room_ids_front = stack[stack_size - 1].room_ids;
if (best_portal_id != -1) {
evaluate_portal(best_portal_id, curr.room_ids, &room_ids_back, &room_ids_front);
} else {
DEV_ASSERT(best_room_a != -1);
evaluate_room_split_plane(best_room_a, best_room_b, curr.room_ids, split_plane, &room_ids_back, &room_ids_front);
}
DEV_ASSERT(room_ids_back.size() <= curr.room_ids.size());
DEV_ASSERT(room_ids_front.size() <= curr.room_ids.size());
_log("\tback contains : " + itos(room_ids_back.size()) + " rooms");
_log("\tfront contains : " + itos(room_ids_front.size()) + " rooms");
// create child nodes
_nodes.push_back(Node());
_nodes.push_back(Node());
// need to reget the node pointer as we may have resized the vector
node = &_nodes[curr.node_id];
node->child[0] = _nodes.size() - 2;
node->child[1] = _nodes.size() - 1;
stack[stack_size - 2].node_id = node->child[0];
stack[stack_size - 1].node_id = node->child[1];
} else {
// couldn't split any further, is leaf
node->leaf = true;
node->first_id = _room_ids.size();
node->num_ids = curr.room_ids.size();
_log("leaf contains : " + itos(curr.room_ids.size()) + " rooms");
// add to the main list
int start = _room_ids.size();
_room_ids.resize(start + curr.room_ids.size());
for (int n = 0; n < curr.room_ids.size(); n++) {
_room_ids[start + n] = curr.room_ids[n];
}
}
} // while stack not empty
}
void PortalRoomsBSP::debug_print_tree(int p_node_id, int p_depth) {
String string = "";
for (int n = 0; n < p_depth; n++) {
string += "\t";
}
const Node &node = _nodes[p_node_id];
if (node.leaf) {
string += "L ";
for (int n = 0; n < node.num_ids; n++) {
int room_id = _room_ids[node.first_id + n];
string += itos(room_id) + ", ";
}
} else {
string += "N ";
}
print_line(string);
// children
if (!node.leaf) {
debug_print_tree(node.child[0], p_depth + 1);
debug_print_tree(node.child[1], p_depth + 1);
}
}
bool PortalRoomsBSP::find_1d_split_point(real_t p_min_a, real_t p_max_a, real_t p_min_b, real_t p_max_b, real_t &r_split_point) const {
if (p_max_a <= p_min_b) {
r_split_point = p_max_a + ((p_min_b - p_max_a) * 0.5);
return true;
}
if (p_max_b <= p_min_a) {
r_split_point = p_max_b + ((p_min_a - p_max_b) * 0.5);
return true;
}
return false;
}
bool PortalRoomsBSP::test_freeform_plane(const LocalVector<Vector3, int32_t> &p_verts_a, const LocalVector<Vector3, int32_t> &p_verts_b, const Plane &p_plane) const {
// print_line("test_freeform_plane " + String(Variant(p_plane)));
for (int n = 0; n < p_verts_a.size(); n++) {
real_t dist = p_plane.distance_to(p_verts_a[n]);
// print_line("\tdist_a " + String(Variant(dist)));
if (dist > _plane_epsilon) {
return false;
}
}
for (int n = 0; n < p_verts_b.size(); n++) {
real_t dist = p_plane.distance_to(p_verts_b[n]);
// print_line("\tdist_b " + String(Variant(dist)));
if (dist < -_plane_epsilon) {
return false;
}
}
return true;
}
// even if AABBs fail to have a splitting plane, there still may be another orientation that can split rooms (e.g. diagonal)
bool PortalRoomsBSP::calculate_freeform_splitting_plane(const VSRoom &p_room_a, const VSRoom &p_room_b, Plane &r_plane) const {
const LocalVector<Vector3, int32_t> &verts_a = p_room_a._verts;
const LocalVector<Vector3, int32_t> &verts_b = p_room_b._verts;
// test from room a to room b
for (int i = 0; i < verts_a.size(); i++) {
const Vector3 &pt_a = verts_a[i];
for (int j = 0; j < verts_b.size(); j++) {
const Vector3 &pt_b = verts_b[j];
for (int k = j + 1; k < verts_b.size(); k++) {
const Vector3 &pt_c = verts_b[k];
// make a plane
r_plane = Plane(pt_a, pt_b, pt_c);
// test the plane
if (test_freeform_plane(verts_a, verts_b, r_plane)) {
return true;
}
}
}
}
// test from room b to room a
for (int i = 0; i < verts_b.size(); i++) {
const Vector3 &pt_a = verts_b[i];
for (int j = 0; j < verts_a.size(); j++) {
const Vector3 &pt_b = verts_a[j];
for (int k = j + 1; k < verts_a.size(); k++) {
const Vector3 &pt_c = verts_a[k];
// make a plane
r_plane = Plane(pt_a, pt_b, pt_c);
// test the plane
if (test_freeform_plane(verts_b, verts_a, r_plane)) {
return true;
}
}
}
}
return false;
}
bool PortalRoomsBSP::calculate_aabb_splitting_plane(const AABB &p_a, const AABB &p_b, Plane &r_plane) const {
real_t split_point = 0.0;
const Vector3 &min_a = p_a.position;
const Vector3 &min_b = p_b.position;
Vector3 max_a = min_a + p_a.size;
Vector3 max_b = min_b + p_b.size;
if (find_1d_split_point(min_a.x, max_a.x, min_b.x, max_b.x, split_point)) {
r_plane = Plane(Vector3(1, 0, 0), split_point);
return true;
}
if (find_1d_split_point(min_a.y, max_a.y, min_b.y, max_b.y, split_point)) {
r_plane = Plane(Vector3(0, 1, 0), split_point);
return true;
}
if (find_1d_split_point(min_a.z, max_a.z, min_b.z, max_b.z, split_point)) {
r_plane = Plane(Vector3(0, 0, 1), split_point);
return true;
}
return false;
}
int PortalRoomsBSP::evaluate_room_split_plane(int p_room_a_id, int p_room_b_id, const LocalVector<int32_t, int32_t> &p_room_ids, Plane &r_plane, LocalVector<int32_t, int32_t> *r_room_ids_back, LocalVector<int32_t, int32_t> *r_room_ids_front) {
// try and create a splitting plane between room a and b, then evaluate it.
const VSRoom &room_a = _portal_renderer->get_room(p_room_a_id);
const VSRoom &room_b = _portal_renderer->get_room(p_room_b_id);
// easiest case, if the rooms don't overlap AABB, we can create an axis aligned plane between them
if (calculate_aabb_splitting_plane(room_a._aabb, room_b._aabb, r_plane)) {
return evaluate_plane(nullptr, r_plane, p_room_ids, r_room_ids_back, r_room_ids_front);
}
if (calculate_freeform_splitting_plane(room_a, room_b, r_plane)) {
return evaluate_plane(nullptr, r_plane, p_room_ids, r_room_ids_back, r_room_ids_front);
}
return 0;
}
int PortalRoomsBSP::evaluate_plane(const VSPortal *p_portal, const Plane &p_plane, const LocalVector<int32_t, int32_t> &p_room_ids, LocalVector<int32_t, int32_t> *r_room_ids_back, LocalVector<int32_t, int32_t> *r_room_ids_front) {
int rooms_front = 0;
int rooms_back = 0;
int rooms_split = 0;
if (r_room_ids_back) {
DEV_ASSERT(!r_room_ids_back->size());
}
if (r_room_ids_front) {
DEV_ASSERT(!r_room_ids_front->size());
}
#define GODOT_BSP_PUSH_FRONT \
rooms_front++; \
if (r_room_ids_front) { \
r_room_ids_front->push_back(rid); \
}
#define GODOT_BSP_PUSH_BACK \
rooms_back++; \
if (r_room_ids_back) { \
r_room_ids_back->push_back(rid); \
}
for (int n = 0; n < p_room_ids.size(); n++) {
int rid = p_room_ids[n];
const VSRoom &room = _portal_renderer->get_room(rid);
// easy cases first
real_t r_min, r_max;
room._aabb.project_range_in_plane(p_plane, r_min, r_max);
if ((r_min <= 0.0) && (r_max <= 0.0)) {
GODOT_BSP_PUSH_BACK
continue;
}
if ((r_min >= 0.0) && (r_max >= 0.0)) {
GODOT_BSP_PUSH_FRONT
continue;
}
// check if the room uses this portal
// internal portals can link to a room that is both in front and behind,
// so we can only deal with non internal portals here with this cheap test.
if (p_portal && !p_portal->_internal) {
if (p_portal->_linkedroom_ID[0] == rid) {
GODOT_BSP_PUSH_BACK
continue;
}
if (p_portal->_linkedroom_ID[1] == rid) {
GODOT_BSP_PUSH_FRONT
continue;
}
}
// most expensive test, test the individual points of the room
// This will catch some off axis rooms that aren't caught by the AABB alone
int points_front = 0;
int points_back = 0;
for (int p = 0; p < room._verts.size(); p++) {
const Vector3 &pt = room._verts[p];
real_t dist = p_plane.distance_to(pt);
// don't take account of points in the epsilon zone,
// these are within the margin of error and could be in front OR behind the plane
if (dist > _plane_epsilon) {
points_front++;
if (points_back) {
break;
}
} else if (dist < -_plane_epsilon) {
points_back++;
if (points_front) {
break;
}
}
}
// if all points are in front
if (!points_back) {
GODOT_BSP_PUSH_FRONT
continue;
}
// if all points are behind
if (!points_front) {
GODOT_BSP_PUSH_BACK
continue;
}
// if split, push to both children
if (r_room_ids_front) {
r_room_ids_front->push_back(rid);
}
if (r_room_ids_back) {
r_room_ids_back->push_back(rid);
}
rooms_split++;
}
#undef GODOT_BSP_PUSH_BACK
#undef GODOT_BSP_PUSH_FRONT
// we want the split that splits the most front and back rooms
return rooms_front * rooms_back;
}
int PortalRoomsBSP::evaluate_portal(int p_portal_id, const LocalVector<int32_t, int32_t> &p_room_ids, LocalVector<int32_t, int32_t> *r_room_ids_back, LocalVector<int32_t, int32_t> *r_room_ids_front) {
const VSPortal &portal = _portal_renderer->get_portal(p_portal_id);
const Plane &plane = portal._plane;
return evaluate_plane(&portal, plane, p_room_ids, r_room_ids_back, r_room_ids_front);
}

View File

@ -0,0 +1,106 @@
/*************************************************************************/
/* portal_rooms_bsp.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_ROOMS_BSP_H
#define PORTAL_ROOMS_BSP_H
#include "core/local_vector.h"
#include "core/math/aabb.h"
#include "core/math/plane.h"
class PortalRenderer;
struct VSPortal;
struct VSRoom;
class PortalRoomsBSP {
struct Node {
Node() { clear(); }
void clear() {
leaf = false;
child[0] = -1;
child[1] = -1;
}
bool leaf;
union {
int32_t child[2];
struct {
int32_t first_id;
int32_t num_ids;
};
};
Plane plane;
};
LocalVector<Node, int32_t> _nodes;
LocalVector<int32_t, int32_t> _room_ids;
PortalRenderer *_portal_renderer = nullptr;
const real_t _plane_epsilon = 0.001;
public:
// build the BSP on level start
void create(PortalRenderer &r_portal_renderer);
// clear data, and ready for a new level
void clear() {
_nodes.reset();
_room_ids.reset();
}
// the main function, returns a shortlist of rooms that are possible for a test point
const int32_t *find_shortlist(const Vector3 &p_pt, int &r_num_rooms) const;
// This is a 'sticky' function, it prefers to stay in the previous room where possible.
// This means there is a hysteresis for room choice that may occur if the user creates
// overlapping rooms...
int find_room_within(const PortalRenderer &p_portal_renderer, const Vector3 &p_pos, int p_previous_room_id) const;
private:
void build(int p_start_node_id, LocalVector<int32_t, int32_t> p_orig_room_ids);
void detect_internal_room_containment(PortalRenderer &r_portal_renderer);
int evaluate_portal(int p_portal_id, const LocalVector<int32_t, int32_t> &p_room_ids, LocalVector<int32_t, int32_t> *r_room_ids_back = nullptr, LocalVector<int32_t, int32_t> *r_room_ids_front = nullptr);
int evaluate_room_split_plane(int p_room_a_id, int p_room_b_id, const LocalVector<int32_t, int32_t> &p_room_ids, Plane &r_plane, LocalVector<int32_t, int32_t> *r_room_ids_back = nullptr, LocalVector<int32_t, int32_t> *r_room_ids_front = nullptr);
int evaluate_plane(const VSPortal *p_portal, const Plane &p_plane, const LocalVector<int32_t, int32_t> &p_room_ids, LocalVector<int32_t, int32_t> *r_room_ids_back = nullptr, LocalVector<int32_t, int32_t> *r_room_ids_front = nullptr);
bool calculate_aabb_splitting_plane(const AABB &p_a, const AABB &p_b, Plane &r_plane) const;
bool calculate_freeform_splitting_plane(const VSRoom &p_room_a, const VSRoom &p_room_b, Plane &r_plane) const;
bool find_1d_split_point(real_t p_min_a, real_t p_max_a, real_t p_min_b, real_t p_max_b, real_t &r_split_point) const;
bool test_freeform_plane(const LocalVector<Vector3, int32_t> &p_verts_a, const LocalVector<Vector3, int32_t> &p_verts_b, const Plane &p_plane) const;
void debug_print_tree(int p_node_id = 0, int p_depth = 0);
void _log(String p_string);
};
#endif

View File

@ -0,0 +1,475 @@
/*************************************************************************/
/* portal_tracer.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_tracer.h"
#include "portal_renderer.h"
#include "servers/visual/visual_server_globals.h"
#include "servers/visual/visual_server_scene.h"
PortalTracer::PlanesPool::PlanesPool() {
reset();
// preallocate the vectors to a reasonable size
for (int n = 0; n < POOL_MAX; n++) {
_planes[n].resize(32);
}
}
void PortalTracer::PlanesPool::reset() {
for (int n = 0; n < POOL_MAX; n++) {
_freelist[n] = POOL_MAX - n - 1;
}
_num_free = POOL_MAX;
}
unsigned int PortalTracer::PlanesPool::request() {
if (!_num_free) {
return -1;
}
_num_free--;
return _freelist[_num_free];
}
void PortalTracer::PlanesPool::free(unsigned int ui) {
DEV_ASSERT(ui < POOL_MAX);
DEV_ASSERT(_num_free < POOL_MAX);
_freelist[_num_free] = ui;
_num_free++;
}
void PortalTracer::trace_debug_sprawl(PortalRenderer &p_portal_renderer, const Vector3 &p_pos, int p_start_room_id, TraceResult &r_result) {
_portal_renderer = &p_portal_renderer;
_trace_start_point = p_pos;
_result = &r_result;
// all the statics should be not hit to start with
_result->clear();
// new test, new tick, to prevent hitting objects more than once
// on a test.
_tick++;
// if the camera is not in a room do nothing
if (p_start_room_id == -1) {
return;
}
trace_debug_sprawl_recursive(0, p_start_room_id);
}
void PortalTracer::trace(PortalRenderer &p_portal_renderer, const Vector3 &p_pos, const LocalVector<Plane> &p_planes, int p_start_room_id, TraceResult &r_result) {
// store local versions to prevent passing around recursive functions
_portal_renderer = &p_portal_renderer;
_trace_start_point = p_pos;
_result = &r_result;
// The near and far clipping planes needs special treatment. The problem is, if it is
// say a metre from the camera, it will clip out a portal immediately in front of the camera.
// as a result we want to use the near clipping plane for objects, but construct a fake
// near plane at exactly the position of the camera, to clip out portals that are behind us.
_near_and_far_planes[0] = p_planes[0];
_near_and_far_planes[1] = p_planes[1];
// all the statics should be not hit to start with
_result->clear();
// new test, new tick, to prevent hitting objects more than once
// on a test.
_tick++;
// if the camera is not in a room do nothing
// (this will return no hits, but is unlikely because the find_rooms lookup will return the nearest
// room even if not inside)
if (p_start_room_id == -1) {
return;
}
// start off the trace with the planes from the camera
LocalVector<Plane> cam_planes;
cam_planes = p_planes;
if (p_portal_renderer.get_cull_using_pvs()) {
trace_pvs(p_start_room_id, cam_planes);
} else {
// alternative : instead of copying straight, we create the first (near) clipping
// plane manually, at 0 distance from the camera. This ensures that portals will not be
// missed, while still culling portals and objects behind us. If we use the actual near clipping plane
// then a portal in front of the camera may not be seen through, giving glitches
cam_planes[0] = Plane(p_pos, cam_planes[0].normal);
TraceParams params;
params.use_pvs = p_portal_renderer.get_pvs().is_loaded();
// create bitfield
if (params.use_pvs) {
const PVS &pvs = _portal_renderer->get_pvs();
if (!pvs.get_pvs_size()) {
params.use_pvs = false;
} else {
// decompress a simple to read roomlist bitfield (could use bits maybe but bytes ok for now)
params.decompressed_room_pvs = nullptr;
params.decompressed_room_pvs = (uint8_t *)alloca(sizeof(uint8_t) * pvs.get_pvs_size());
memset(params.decompressed_room_pvs, 0, sizeof(uint8_t) * pvs.get_pvs_size());
const VSRoom &source_room = _portal_renderer->get_room(p_start_room_id);
for (int n = 0; n < source_room._pvs_size; n++) {
int room_id = pvs.get_pvs_room_id(source_room._pvs_first + n);
params.decompressed_room_pvs[room_id] = 255;
}
}
}
trace_recursive(params, 0, p_start_room_id, cam_planes);
}
}
void PortalTracer::cull_roamers(const VSRoom &p_room, const LocalVector<Plane> &p_planes) {
int num_roamers = p_room._roamer_pool_ids.size();
for (int n = 0; n < num_roamers; n++) {
uint32_t pool_id = p_room._roamer_pool_ids[n];
PortalRenderer::Moving &moving = _portal_renderer->get_pool_moving(pool_id);
// done already?
if (moving.last_tick_hit == _tick) {
continue;
}
// mark as done
moving.last_tick_hit = _tick;
if (test_cull_inside(moving.exact_aabb, p_planes)) {
_result->visible_roamer_pool_ids.push_back(pool_id);
}
}
}
void PortalTracer::cull_statics_debug_sprawl(const VSRoom &p_room) {
int num_statics = p_room._static_ids.size();
for (int n = 0; n < num_statics; n++) {
uint32_t static_id = p_room._static_ids[n];
// VSStatic &stat = _portal_renderer->get_static(static_id);
// deal with dynamic stats
// if (stat.dynamic) {
// VSG::scene->_instance_get_transformed_aabb(stat.instance, stat.aabb);
// }
// set the visible bit if not set
if (!_result->bf_visible_statics.check_and_set(static_id)) {
_result->visible_static_ids.push_back(static_id);
}
}
}
void PortalTracer::cull_statics(const VSRoom &p_room, const LocalVector<Plane> &p_planes) {
int num_statics = p_room._static_ids.size();
for (int n = 0; n < num_statics; n++) {
uint32_t static_id = p_room._static_ids[n];
VSStatic &stat = _portal_renderer->get_static(static_id);
// deal with dynamic stats
if (stat.dynamic) {
VSG::scene->_instance_get_transformed_aabb(stat.instance, stat.aabb);
}
// estimate the radius .. for now
const AABB &bb = stat.aabb;
// print("\t\t\tculling object " + pObj->get_name());
if (test_cull_inside(bb, p_planes)) {
// bypass the bitfield for now and just show / hide
//stat.show(bShow);
// set the visible bit if not set
if (_result->bf_visible_statics.check_and_set(static_id)) {
// if wasn't previously set, add to the visible list
_result->visible_static_ids.push_back(static_id);
}
}
} // for n through statics
}
int PortalTracer::trace_globals(const LocalVector<Plane> &p_planes, VSInstance **p_result_array, int first_result, int p_result_max, uint32_t p_mask) {
uint32_t num_globals = _portal_renderer->get_num_moving_globals();
int current_result = first_result;
for (uint32_t n = 0; n < num_globals; n++) {
const PortalRenderer::Moving &moving = _portal_renderer->get_moving_global(n);
#ifdef PORTAL_RENDERER_STORE_MOVING_RIDS
// debug check the instance is valid
void *vss_instance = VSG::scene->_instance_get_from_rid(moving.instance_rid);
if (vss_instance) {
#endif
if (test_cull_inside(moving.exact_aabb, p_planes, false)) {
if (VSG::scene->_instance_cull_check(moving.instance, p_mask)) {
p_result_array[current_result++] = moving.instance;
// full up?
if (current_result >= p_result_max) {
return current_result;
}
}
}
#ifdef PORTAL_RENDERER_STORE_MOVING_RIDS
} else {
WARN_PRINT("vss instance is null " + PortalRenderer::_addr_to_string(moving.instance));
}
#endif
}
return current_result;
}
void PortalTracer::trace_debug_sprawl_recursive(int p_depth, int p_room_id) {
if (p_depth > 1) {
return;
}
// prevent too much depth
ERR_FAIL_COND_MSG(p_depth > 8, "Portal Depth Limit reached");
// get the room
const VSRoom &room = _portal_renderer->get_room(p_room_id);
int num_portals = room._portal_ids.size();
for (int p = 0; p < num_portals; p++) {
const VSPortal &portal = _portal_renderer->get_portal(room._portal_ids[p]);
if (!portal._active) {
continue;
}
cull_statics_debug_sprawl(room);
// everything depends on whether the portal is incoming or outgoing.
int outgoing = 1;
int room_a_id = portal._linkedroom_ID[0];
if (room_a_id != p_room_id) {
outgoing = 0;
DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
}
// trace through this portal to the next room
int linked_room_id = portal._linkedroom_ID[outgoing];
if (linked_room_id != -1) {
trace_debug_sprawl_recursive(p_depth + 1, linked_room_id);
} // if a linked room exists
} // for p through portals
}
void PortalTracer::trace_pvs(int p_source_room_id, const LocalVector<Plane> &p_planes) {
const PVS &pvs = _portal_renderer->get_pvs();
const VSRoom &source_room = _portal_renderer->get_room(p_source_room_id);
for (int r = 0; r < source_room._pvs_size; r++) {
int room_id = pvs.get_pvs_room_id(source_room._pvs_first + r);
// get the room
const VSRoom &room = _portal_renderer->get_room(room_id);
cull_statics(room, p_planes);
cull_roamers(room, p_planes);
}
}
void PortalTracer::trace_recursive(const TraceParams &p_params, int p_depth, int p_room_id, const LocalVector<Plane> &p_planes) {
// prevent too much depth
if (p_depth > _depth_limit) {
WARN_PRINT_ONCE("Portal Depth Limit reached (seeing through too many portals)");
return;
}
// get the room
const VSRoom &room = _portal_renderer->get_room(p_room_id);
cull_statics(room, p_planes);
cull_roamers(room, p_planes);
int num_portals = room._portal_ids.size();
for (int p = 0; p < num_portals; p++) {
const VSPortal &portal = _portal_renderer->get_portal(room._portal_ids[p]);
// portals can be switched on and off at runtime, like opening and closing a door
if (!portal._active) {
continue;
}
// everything depends on whether the portal is incoming or outgoing.
// if incoming we reverse the logic.
int outgoing = 1;
int room_a_id = portal._linkedroom_ID[0];
if (room_a_id != p_room_id) {
outgoing = 0;
DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
}
// trace through this portal to the next room
int linked_room_id = portal._linkedroom_ID[outgoing];
// cull by PVS
if (p_params.use_pvs && (!p_params.decompressed_room_pvs[linked_room_id])) {
continue;
}
// cull by portal angle to camera.
// much better way of culling portals by direction to camera...
// instead of using dot product with a varying view direction, we simply find which side of the portal
// plane the camera is on! If it is behind, the portal can be seen through, if in front, it can't
real_t dist_cam = portal._plane.distance_to(_trace_start_point);
if (!outgoing) {
dist_cam = -dist_cam;
}
if (dist_cam >= 0.0) {
continue;
}
// is it culled by the planes?
VSPortal::ClipResult overall_res = VSPortal::ClipResult::CLIP_INSIDE;
// while clipping to the planes we maintain a list of partial planes, so we can add them to the
// recursive next iteration of planes to check
static LocalVector<int> partial_planes;
partial_planes.clear();
// for portals, we want to ignore the near clipping plane, as we might be right on the edge of a doorway
// and still want to look through the portal.
// so earlier we have set it that the first plane (ASSUMING that plane zero is the near clipping plane)
// starts from the camera position, and NOT the actual near clipping plane.
// if we need quite a distant near plane, we may need a different strategy.
for (uint32_t l = 0; l < p_planes.size(); l++) {
VSPortal::ClipResult res = portal.clip_with_plane(p_planes[l]);
switch (res) {
case VSPortal::ClipResult::CLIP_OUTSIDE: {
overall_res = res;
} break;
case VSPortal::ClipResult::CLIP_PARTIAL: {
// if the portal intersects one of the planes, we should take this plane into account
// in the next call of this recursive trace, because it can be used to cull out more objects
overall_res = res;
partial_planes.push_back(l);
} break;
default: // suppress warning
break;
}
// if the portal was totally outside the 'frustum' then we can ignore it
if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE)
break;
}
// this portal is culled
if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) {
continue;
}
// hopefully the portal actually leads somewhere...
if (linked_room_id != -1) {
// we need some new planes
unsigned int pool_mem = _planes_pool.request();
// if the planes pool is not empty, we got some planes, and can recurse
if (pool_mem != (unsigned int)-1) {
// get a new vector of planes from the pool
LocalVector<Plane> &new_planes = _planes_pool.get(pool_mem);
// makes sure there are none left over (as the pool may not clear them)
new_planes.clear();
// if portal is totally inside the planes, don't copy the old planes ..
// i.e. we can now cull using the portal and forget about the rest of the frustum (yay)
// note that this loses the far clipping plane .. but that shouldn't be important usually?
// (maybe we might need to account for this in future .. look for issues)
if (overall_res != VSPortal::ClipResult::CLIP_INSIDE) {
// if it WASNT totally inside the existing frustum, we also need to add any existing planes
// that cut the portal.
for (uint32_t n = 0; n < partial_planes.size(); n++) {
new_planes.push_back(p_planes[partial_planes[n]]);
}
}
// we will always add the portals planes. This could probably be optimized, as some
// portal planes may be culled out by partial planes... NYI
portal.add_planes(_trace_start_point, new_planes, outgoing != 0);
// always add the far plane. It is likely the portal is inside the far plane,
// but it is still needed in future for culling portals and objects.
// note that there is a small possibility of far plane being added twice here
// in some situations, but I don't think it should be a problem.
// The fake near plane BTW is almost never added (otherwise it would prematurely
// break traversal through the portals), so near clipping must be done
// explicitly on objects.
new_planes.push_back(_near_and_far_planes[1]);
// go and do the whole lot again in the next room
trace_recursive(p_params, p_depth + 1, linked_room_id, new_planes);
// no longer need these planes, return them to the pool
_planes_pool.free(pool_mem);
} // pool mem allocated
else {
// planes pool is empty!
// This will happen if the view goes through shedloads of portals
// The solution is either to increase the plane pool size, or not build levels
// with views through multiple portals. Looking through multiple portals is likely to be
// slow anyway because of the number of planes to test.
WARN_PRINT_ONCE("planes pool is empty");
// note we also have a depth check at the top of this function. Which will probably get hit
// before the pool gets empty.
}
} // if a linked room exists
} // for p through portals
}

View File

@ -0,0 +1,165 @@
/*************************************************************************/
/* portal_tracer.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_TRACER_H
#define PORTAL_TRACER_H
#include "core/bitfield_dynamic.h"
#include "core/local_vector.h"
#include "portal_types.h"
#ifdef TOOLS_ENABLED
// use this for checking for instance lifetime errors, disable normally
//#define PORTAL_RENDERER_STORE_MOVING_RIDS
#endif
class PortalRenderer;
struct VSRoom;
class PortalTracer {
public:
// a bitfield for which statics have been hit this time,
// and a list of showing statics
class TraceResult {
public:
void create(int p_num_statics) {
bf_visible_statics.create(p_num_statics);
}
void clear() {
bf_visible_statics.blank();
visible_static_ids.clear();
visible_roamer_pool_ids.clear();
}
BitFieldDynamic bf_visible_statics;
LocalVector<uint32_t> visible_static_ids;
LocalVector<uint32_t> visible_roamer_pool_ids;
};
struct TraceParams {
bool use_pvs;
uint8_t *decompressed_room_pvs;
};
// The recursive visibility function needs to allocate lists of planes each time a room is traversed.
// Instead of doing this allocation on the fly we will use a pool which should be much faster and nearer
// constant time.
// Note this simple pool isn't super optimal but should be fine for now.
class PlanesPool {
public:
// maximum number of vectors in the pool
const static int POOL_MAX = 32;
void reset();
// request a new vector of planes .. returns the pool id, or -1 if pool is empty
unsigned int request();
// return pool id to the pool
void free(unsigned int ui);
LocalVector<Plane> &get(unsigned int ui) { return _planes[ui]; }
PlanesPool();
private:
LocalVector<Plane> _planes[POOL_MAX];
// list of pool ids that are free and can be allocated
uint8_t _freelist[POOL_MAX];
uint32_t _num_free;
};
// for debugging, instead of doing a normal trace, show the objects that are sprawled from the current room
void trace_debug_sprawl(PortalRenderer &p_portal_renderer, const Vector3 &p_pos, int p_start_room_id, TraceResult &r_result);
// trace statics, dynamics and roaming
void trace(PortalRenderer &p_portal_renderer, const Vector3 &p_pos, const LocalVector<Plane> &p_planes, int p_start_room_id, TraceResult &r_result);
// globals are handled separately as they don't care about the rooms
int trace_globals(const LocalVector<Plane> &p_planes, VSInstance **p_result_array, int first_result, int p_result_max, uint32_t p_mask);
void set_depth_limit(int p_limit) { _depth_limit = p_limit; }
private:
// main tracing function is recursive
void trace_recursive(const TraceParams &p_params, int p_depth, int p_room_id, const LocalVector<Plane> &p_planes);
// use pvs to cull instead of dynamically using portals
// this is a faster trace but less accurate. Only possible if PVS has been generated.
void trace_pvs(int p_source_room_id, const LocalVector<Plane> &p_planes);
// debug version
void trace_debug_sprawl_recursive(int p_depth, int p_room_id);
void cull_statics(const VSRoom &p_room, const LocalVector<Plane> &p_planes);
void cull_statics_debug_sprawl(const VSRoom &p_room);
void cull_roamers(const VSRoom &p_room, const LocalVector<Plane> &p_planes);
// if an aabb is in front of any of the culling planes, it can't be seen so returns false
bool test_cull_inside(const AABB &p_aabb, const LocalVector<Plane> &p_planes, bool p_test_explicit_near_plane = true) const {
for (unsigned int p = 0; p < p_planes.size(); p++) {
real_t r_min, r_max;
p_aabb.project_range_in_plane(p_planes[p], r_min, r_max);
if (r_min > 0.0) {
return false;
}
}
if (p_test_explicit_near_plane) {
real_t r_min, r_max;
p_aabb.project_range_in_plane(_near_and_far_planes[0], r_min, r_max);
if (r_min > 0.0) {
return false;
}
}
return true;
}
// local versions to prevent passing around the recursive functions
PortalRenderer *_portal_renderer = nullptr;
Vector3 _trace_start_point;
TraceResult *_result = nullptr;
Plane _near_and_far_planes[2];
PlanesPool _planes_pool;
int _depth_limit = 16;
// keep a tick count for each trace, to avoid adding a visible
// object to the hit list more than once per tick
// (this makes more sense than bitfield for moving objects)
uint32_t _tick = 0;
};
#endif

View File

@ -0,0 +1,234 @@
/*************************************************************************/
/* portal_types.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_types.h"
VSPortal::ClipResult VSPortal::clip_with_plane(const Plane &p) const {
int nOutside = 0;
int nPoints = _pts_world.size();
for (int n = 0; n < nPoints; n++) {
real_t d = p.distance_to(_pts_world[n]);
if (d >= 0.0) {
nOutside++;
}
}
if (nOutside == nPoints) {
return CLIP_OUTSIDE;
}
if (nOutside == 0) {
return CLIP_INSIDE;
}
return CLIP_PARTIAL;
}
void VSPortal::add_pvs_planes(const VSPortal &p_first, bool p_first_outgoing, LocalVector<Plane, int32_t> &r_planes, bool p_outgoing) const {
int num_a = p_first._pts_world.size();
int num_b = _pts_world.size();
// get the world points of both in the correct order based on whether outgoing .. note this isn't very efficient...
Vector3 *pts_a = (Vector3 *)alloca(num_a * sizeof(Vector3));
Vector3 *pts_b = (Vector3 *)alloca(num_b * sizeof(Vector3));
if (p_first_outgoing) {
// straight copy
for (int n = 0; n < num_a; n++) {
pts_a[n] = p_first._pts_world[n];
}
} else {
for (int n = 0; n < num_a; n++) {
pts_a[n] = p_first._pts_world[num_a - 1 - n];
}
}
if (p_outgoing) {
// straight copy
for (int n = 0; n < num_b; n++) {
pts_b[n] = _pts_world[n];
}
} else {
for (int n = 0; n < num_b; n++) {
pts_b[n] = _pts_world[num_b - 1 - n];
}
}
// go through and try every combination of points to form a clipping plane
for (int pvA = 0; pvA < num_a; pvA++) {
for (int pvB = 0; pvB < num_b; pvB++) {
int pvC = (pvB + 1) % num_b;
// three verts
const Vector3 &va = pts_a[pvA];
const Vector3 &vb = pts_b[pvB];
const Vector3 &vc = pts_b[pvC];
// create plane
Plane plane = Plane(va, vc, vb);
// already exists similar plane, so ignore
if (_is_plane_duplicate(plane, r_planes)) {
continue;
}
if (_test_pvs_plane(-plane, pts_a, num_a, pts_b, num_b)) {
// add the plane
r_planes.push_back(plane);
}
} // for pvB
} // for pvA
}
// typically we will end up with a bunch of duplicate planes being trying to be added for a portal.
// we can remove any that are too similar
bool VSPortal::_is_plane_duplicate(const Plane &p_plane, const LocalVector<Plane, int32_t> &p_planes) const {
const real_t epsilon_d = 0.001;
const real_t epsilon_dot = 0.98;
for (int n = 0; n < p_planes.size(); n++) {
const Plane &p = p_planes[n];
if (Math::absf(p_plane.d - p.d) > epsilon_d) {
continue;
}
real_t dot = p_plane.normal.dot(p.normal);
if (dot < epsilon_dot) {
continue;
}
// match
return true;
}
return false;
}
bool VSPortal::_pvs_is_outside_planes(const LocalVector<Plane, int32_t> &p_planes) const {
// short version
const Vector<Vector3> &pts = _pts_world;
int nPoints = pts.size();
const real_t epsilon = 0.1;
for (int p = 0; p < p_planes.size(); p++) {
for (int n = 0; n < nPoints; n++) {
const Vector3 &pt = pts[n];
real_t dist = p_planes[p].distance_to(pt);
if (dist < -epsilon) {
return false;
}
}
}
return true;
}
bool VSPortal::_test_pvs_plane(const Plane &p_plane, const Vector3 *pts_a, int num_a, const Vector3 *pts_b, int num_b) const {
const real_t epsilon = 0.1;
for (int n = 0; n < num_a; n++) {
real_t dist = p_plane.distance_to(pts_a[n]);
if (dist > epsilon) {
return false;
}
}
for (int n = 0; n < num_b; n++) {
real_t dist = p_plane.distance_to(pts_b[n]);
if (dist < -epsilon) {
return false;
}
}
return true;
}
// add clipping planes to the vector formed by each portal edge and the camera
void VSPortal::add_planes(const Vector3 &p_cam, LocalVector<Plane> &r_planes, bool p_outgoing) const {
// short version
const Vector<Vector3> &pts = _pts_world;
int nPoints = pts.size();
ERR_FAIL_COND(nPoints < 3);
Plane p;
int offset_a, offset_b;
if (p_outgoing) {
offset_a = 0;
offset_b = -1;
} else {
offset_a = -1;
offset_b = 0;
}
for (int n = 1; n < nPoints; n++) {
p = Plane(p_cam, pts[n + offset_a], pts[n + offset_b]);
// detect null plane
// if (p.normal.length_squared() < 0.1)
// {
// print("NULL plane detected from points : ");
// print(ptCam + pts[n] + pts[n-1]);
// }
r_planes.push_back(p);
debug_check_plane_validity(p);
}
// first and last
if (p_outgoing) {
p = Plane(p_cam, pts[0], pts[nPoints - 1]);
} else {
p = Plane(p_cam, pts[nPoints - 1], pts[0]);
}
r_planes.push_back(p);
debug_check_plane_validity(p);
// debug
// if (!manager.m_bDebugPlanes)
// return;
// for (int n=0; n<nPoints; n++)
// {
// manager.m_DebugPlanes.push_back(pts[n]);
// }
}
void VSPortal::debug_check_plane_validity(const Plane &p) const {
// DEV_ASSERT(p.distance_to(center) < 0.0);
}

View File

@ -0,0 +1,354 @@
/*************************************************************************/
/* portal_types.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef PORTAL_TYPES_H
#define PORTAL_TYPES_H
#include "core/local_vector.h"
#include "core/math/aabb.h"
#include "core/math/plane.h"
#include "core/math/vector3.h"
#include "core/object_id.h"
#include "core/rid.h"
// visual server scene instance.
// we can't have a pointer to nested class outside of visual server scene...
// so rather than use a void * straight we can use this for some semblance
// of safety
typedef void VSInstance;
typedef void VSGhost;
// the handles are just IDs, but nicer to be specific on what they are in the code.
// Also - the handles are plus one based (i.e. 0 is unset, 1 is id 0, 2 is id 1 etc.
typedef uint32_t PortalHandle;
typedef uint32_t RoomHandle;
typedef uint32_t RoomGroupHandle;
typedef uint32_t OcclusionHandle;
typedef uint32_t RGhostHandle;
struct VSPortal {
enum ClipResult {
CLIP_OUTSIDE,
CLIP_PARTIAL,
CLIP_INSIDE,
};
// explicit create and destroy rather than constructors / destructors
// because we are using a pool so objects may be reused
void create() {
_linkedroom_ID[0] = -1;
_linkedroom_ID[1] = -1;
_active = true;
_plane = Plane();
_portal_id = -1;
_aabb = AABB();
}
void destroy() {
_pts_world.reset();
}
void rooms_and_portals_clear() {
_linkedroom_ID[0] = -1;
_linkedroom_ID[1] = -1;
_active = true;
_plane = Plane();
_aabb = AABB();
_pts_world.reset();
}
VSPortal::ClipResult clip_with_plane(const Plane &p) const;
void add_planes(const Vector3 &p_cam, LocalVector<Plane> &r_planes, bool p_outgoing) const;
void debug_check_plane_validity(const Plane &p) const;
void add_pvs_planes(const VSPortal &p_first, bool p_first_outgoing, LocalVector<Plane, int32_t> &r_planes, bool p_outgoing) const;
bool _pvs_is_outside_planes(const LocalVector<Plane, int32_t> &p_planes) const;
private:
bool _test_pvs_plane(const Plane &p_plane, const Vector3 *pts_a, int num_a, const Vector3 *pts_b, int num_b) const;
bool _is_plane_duplicate(const Plane &p_plane, const LocalVector<Plane, int32_t> &p_planes) const;
public:
// returns the room to if if crosses, or else returns -1
int geometry_crosses_portal(int p_room_from, const AABB &p_aabb, const Vector<Vector3> &p_pts) const {
// first aabb check
if (!p_aabb.intersects(_aabb)) {
return -1;
}
// disallow sprawling from outer to inner rooms.
// This is a convenience feature that stops e.g. terrain sprawling into
// a building. If you want geometry to feature in the inner room and the outer,
// simply place it in the inner room.
if (_internal && (_linkedroom_ID[0] != p_room_from)) {
return -1;
}
// accurate check use portal triangles
// NYI
const real_t epsilon = _margin;
if (p_room_from == _linkedroom_ID[0]) {
// outward
// how far do points project over the portal
for (int n = 0; n < p_pts.size(); n++) {
real_t dist = _plane.distance_to(p_pts[n]);
if (dist > epsilon) {
return _linkedroom_ID[1];
}
}
} else {
// inward
DEV_ASSERT(p_room_from == _linkedroom_ID[1]);
for (int n = 0; n < p_pts.size(); n++) {
real_t dist = _plane.distance_to(p_pts[n]);
if (dist < -epsilon) {
return _linkedroom_ID[0];
}
}
}
// no points crossed the portal
return -1;
}
// returns the room to if if crosses, or else returns -1
int crosses_portal(int p_room_from, const AABB &p_aabb, bool p_disallow_crossing_internal = false, bool p_accurate_check = false) const {
// first aabb check
if (!p_aabb.intersects(_aabb)) {
return -1;
}
// disallow sprawling from outer to inner rooms.
// This is a convenience feature that stops e.g. terrain sprawling into
// a building. If you want geometry to feature in the inner room and the outer,
// simply place it in the inner room.
if (p_disallow_crossing_internal && _internal && (_linkedroom_ID[0] != p_room_from)) {
return -1;
}
// accurate check use portal triangles
// NYI
real_t r_min, r_max;
p_aabb.project_range_in_plane(_plane, r_min, r_max);
const real_t epsilon = _margin; //10.0;
if (p_room_from == _linkedroom_ID[0]) {
if (r_max > epsilon) {
return _linkedroom_ID[1];
} else {
return -1;
}
}
DEV_ASSERT(p_room_from == _linkedroom_ID[1]);
if (r_min < -epsilon) {
return _linkedroom_ID[0];
}
return -1;
}
// the portal needs a list of unique world points (in order, clockwise?)
LocalVector<Vector3> _pts_world;
// portal plane
Plane _plane;
// aabb for quick bounds checks
AABB _aabb;
uint32_t _portal_id = -1;
// in order to detect objects crossing portals,
// an extension margin can be used to prevent objects
// that *just* cross the portal extending into the next room
real_t _margin = 1.0;
// these are room IDs, or -1 if unset
int _linkedroom_ID[2];
// can be turned on and off by the user
bool _active = true;
// internal portals have slightly different behaviour
bool _internal = false;
VSPortal() {
_linkedroom_ID[0] = -1;
_linkedroom_ID[1] = -1;
}
};
struct VSRoomGroup {
void create() {
}
void destroy() {
_room_ids.reset();
}
// used for calculating gameplay notifications
uint32_t last_gameplay_tick_hit = 0;
ObjectID _godot_instance_ID = 0;
LocalVector<uint32_t, int32_t> _room_ids;
};
struct VSRoom {
// explicit create and destroy rather than constructors / destructors
// because we are using a pool so objects may be reused
void create() {
_room_ID = -1;
_aabb = AABB();
}
void destroy() {
_static_ids.reset();
_static_ghost_ids.reset();
_planes.reset();
_verts.reset();
_portal_ids.reset();
_roamer_pool_ids.reset();
_rghost_pool_ids.reset();
_roomgroup_ids.reset();
_pvs_first = 0;
_pvs_size = 0;
_secondary_pvs_first = 0;
_secondary_pvs_size = 0;
_priority = 0;
_contains_internal_rooms = false;
}
void cleanup_after_conversion() {
_verts.reset();
}
void rooms_and_portals_clear() {
destroy();
_aabb = AABB();
// don't unset the room_ID here, because rooms may be accessed after this is called
}
// this isn't just useful for checking whether a point is within (i.e. returned value is 0 or less)
// it is useful for finding the CLOSEST room to a point (by plane distance, doesn't take into account corners etc)
real_t is_point_within(const Vector3 &p_pos) const {
// inside by default
real_t closest_dist = -FLT_MAX;
for (int n = 0; n < _planes.size(); n++) {
real_t dist = _planes[n].distance_to(p_pos);
if (dist > closest_dist) {
closest_dist = dist;
}
}
return closest_dist;
}
// not super fast, but there shouldn't be that many roamers per room
bool remove_roamer(uint32_t p_pool_id) {
for (int n = 0; n < _roamer_pool_ids.size(); n++) {
if (_roamer_pool_ids[n] == p_pool_id) {
_roamer_pool_ids.remove_unordered(n);
return true;
}
}
return false;
}
bool remove_rghost(uint32_t p_pool_id) {
for (int n = 0; n < _rghost_pool_ids.size(); n++) {
if (_rghost_pool_ids[n] == p_pool_id) {
_rghost_pool_ids.remove_unordered(n);
return true;
}
}
return false;
}
void add_roamer(uint32_t p_pool_id) {
_roamer_pool_ids.push_back(p_pool_id);
}
void add_rghost(uint32_t p_pool_id) {
_rghost_pool_ids.push_back(p_pool_id);
}
// keep a list of statics in the room .. statics may appear
// in more than one room due to sprawling!
LocalVector<uint32_t, int32_t> _static_ids;
LocalVector<uint32_t, int32_t> _static_ghost_ids;
// very rough
AABB _aabb;
int32_t _room_ID = -1;
ObjectID _godot_instance_ID = 0;
// rooms with a higher priority are internal rooms ..
// rooms within a room. These will be chosen in preference
// when finding the room within, when within more than one room.
// Example, house in a terrain room.
int32_t _priority = 0;
bool _contains_internal_rooms = false;
int32_t _pvs_first = 0;
int32_t _secondary_pvs_first = 0;
uint16_t _pvs_size = 0;
uint16_t _secondary_pvs_size = 0;
// used for calculating gameplay notifications
uint32_t last_gameplay_tick_hit = 0;
// convex hull of the room, either determined by geometry or manual bound
LocalVector<Plane, int32_t> _planes;
// vertices of the corners of the hull, passed from the scene tree
// (note these don't take account of any final portal planes adjusted by the portal renderer)
LocalVector<Vector3, int32_t> _verts;
// which portals are in the room (ingoing and outgoing)
LocalVector<uint32_t, int32_t> _portal_ids;
// roaming movers currently in the room
LocalVector<uint32_t, int32_t> _roamer_pool_ids;
LocalVector<uint32_t, int32_t> _rghost_pool_ids;
// keep track of which roomgroups the room is in, that
// way we can switch on and off roomgroups as they enter / exit view
LocalVector<uint32_t, int32_t> _roomgroup_ids;
};
#endif

View File

@ -558,6 +558,44 @@ public:
BIND2(instance_set_extra_visibility_margin, RID, real_t) BIND2(instance_set_extra_visibility_margin, RID, real_t)
// Portals
BIND2(instance_set_portal_mode, RID, InstancePortalMode)
BIND0R(RID, ghost_create)
BIND4(ghost_set_scenario, RID, RID, ObjectID, const AABB &)
BIND2(ghost_update, RID, const AABB &)
BIND0R(RID, portal_create)
BIND2(portal_set_scenario, RID, RID)
BIND3(portal_set_geometry, RID, const Vector<Vector3> &, float)
BIND4(portal_link, RID, RID, RID, bool)
BIND2(portal_set_active, RID, bool)
// Roomgroups
BIND0R(RID, roomgroup_create)
BIND2(roomgroup_prepare, RID, ObjectID)
BIND2(roomgroup_set_scenario, RID, RID)
BIND2(roomgroup_add_room, RID, RID)
// Rooms
BIND0R(RID, room_create)
BIND2(room_set_scenario, RID, RID)
BIND4(room_add_instance, RID, RID, const AABB &, const Vector<Vector3> &)
BIND3(room_add_ghost, RID, ObjectID, const AABB &)
BIND5(room_set_bound, RID, ObjectID, const Vector<Plane> &, const AABB &, const Vector<Vector3> &)
BIND2(room_prepare, RID, int32_t)
BIND1(rooms_and_portals_clear, RID)
BIND1(rooms_unload, RID)
BIND6(rooms_finalize, RID, bool, bool, bool, bool, String)
BIND4(rooms_override_camera, RID, bool, const Vector3 &, const Vector<Plane> *)
BIND2(rooms_set_active, RID, bool)
BIND2(rooms_set_params, RID, int)
BIND3(rooms_set_debug_feature, RID, RoomsDebugFeature, bool)
BIND2(rooms_update_gameplay_monitor, RID, const Vector<Vector3> &)
// Callbacks
BIND1(callbacks_register, VisualServerCallbacks *)
// don't use these in a game! // don't use these in a game!
BIND2RC(Vector<ObjectID>, instances_cull_aabb, const AABB &, RID) BIND2RC(Vector<ObjectID>, instances_cull_aabb, const AABB &, RID)
BIND3RC(Vector<ObjectID>, instances_cull_ray, const Vector3 &, const Vector3 &, RID) BIND3RC(Vector<ObjectID>, instances_cull_ray, const Vector3 &, const Vector3 &, RID)

View File

@ -101,7 +101,7 @@ VisualServerScene::SpatialPartitionID VisualServerScene::SpatialPartitioningScen
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) #if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
// we are relying on this instance to be valid in order to pass // we are relying on this instance to be valid in order to pass
// the visible flag to the bvh. // the visible flag to the bvh.
CRASH_COND(!p_userdata); DEV_ASSERT(p_userdata);
#endif #endif
return _bvh.create(p_userdata, p_userdata->visible, p_aabb, p_subindex, p_pairable, p_pairable_type, p_pairable_mask) + 1; return _bvh.create(p_userdata, p_userdata->visible, p_aabb, p_subindex, p_pairable, p_pairable_type, p_pairable_mask) + 1;
} }
@ -601,6 +601,11 @@ void VisualServerScene::instance_set_scenario(RID p_instance, RID p_scenario) {
instance->spatial_partition_id = 0; instance->spatial_partition_id = 0;
} }
// handle occlusion changes
if (instance->occlusion_handle) {
_instance_destroy_occlusion_rep(instance);
}
switch (instance->base_type) { switch (instance->base_type) {
case VS::INSTANCE_LIGHT: { case VS::INSTANCE_LIGHT: {
InstanceLightData *light = static_cast<InstanceLightData *>(instance->base_data); InstanceLightData *light = static_cast<InstanceLightData *>(instance->base_data);
@ -653,6 +658,9 @@ void VisualServerScene::instance_set_scenario(RID p_instance, RID p_scenario) {
} }
} }
// handle occlusion changes if necessary
_instance_create_occlusion_rep(instance);
_instance_queue_update(instance, true, true); _instance_queue_update(instance, true, true);
} }
} }
@ -889,6 +897,412 @@ void VisualServerScene::instance_set_extra_visibility_margin(RID p_instance, rea
_instance_queue_update(instance, true, false); _instance_queue_update(instance, true, false);
} }
// Portals
void VisualServerScene::instance_set_portal_mode(RID p_instance, VisualServer::InstancePortalMode p_mode) {
Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_COND(!instance);
// no change?
if (instance->portal_mode == p_mode) {
return;
}
// should this happen?
if (!instance->scenario) {
instance->portal_mode = p_mode;
return;
}
// destroy previous occlusion instance?
_instance_destroy_occlusion_rep(instance);
instance->portal_mode = p_mode;
_instance_create_occlusion_rep(instance);
}
void VisualServerScene::_instance_create_occlusion_rep(Instance *p_instance) {
ERR_FAIL_COND(!p_instance);
ERR_FAIL_COND(!p_instance->scenario);
switch (p_instance->portal_mode) {
default: {
p_instance->occlusion_handle = 0;
} break;
case VisualServer::InstancePortalMode::INSTANCE_PORTAL_MODE_ROAMING: {
p_instance->occlusion_handle = p_instance->scenario->_portal_renderer.instance_moving_create(p_instance, p_instance->self, false, p_instance->transformed_aabb);
} break;
case VisualServer::InstancePortalMode::INSTANCE_PORTAL_MODE_GLOBAL: {
p_instance->occlusion_handle = p_instance->scenario->_portal_renderer.instance_moving_create(p_instance, p_instance->self, true, p_instance->transformed_aabb);
} break;
}
}
void VisualServerScene::_instance_destroy_occlusion_rep(Instance *p_instance) {
ERR_FAIL_COND(!p_instance);
ERR_FAIL_COND(!p_instance->scenario);
// not an error, can occur
if (!p_instance->occlusion_handle) {
return;
}
p_instance->scenario->_portal_renderer.instance_moving_destroy(p_instance->occlusion_handle);
// unset
p_instance->occlusion_handle = 0;
}
void *VisualServerScene::_instance_get_from_rid(RID p_instance) {
Instance *instance = instance_owner.get(p_instance);
return instance;
}
bool VisualServerScene::_instance_get_transformed_aabb(RID p_instance, AABB &r_aabb) {
Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_NULL_V(instance, false);
r_aabb = instance->transformed_aabb;
return true;
}
// the portal has to be associated with a scenario, this is assumed to be
// the same scenario as the portal node
RID VisualServerScene::portal_create() {
Portal *portal = memnew(Portal);
ERR_FAIL_COND_V(!portal, RID());
RID portal_rid = portal_owner.make_rid(portal);
return portal_rid;
}
// should not be called multiple times, different scenarios etc, but just in case, we will support this
void VisualServerScene::portal_set_scenario(RID p_portal, RID p_scenario) {
Portal *portal = portal_owner.getornull(p_portal);
ERR_FAIL_COND(!portal);
Scenario *scenario = scenario_owner.getornull(p_scenario);
// noop?
if (portal->scenario == scenario) {
return;
}
// if the portal is in a scenario already, remove it
if (portal->scenario) {
portal->scenario->_portal_renderer.portal_destroy(portal->scenario_portal_id);
portal->scenario = nullptr;
portal->scenario_portal_id = 0;
}
// create when entering the world
if (scenario) {
portal->scenario = scenario;
// defer the actual creation to here
portal->scenario_portal_id = scenario->_portal_renderer.portal_create();
}
}
void VisualServerScene::portal_set_geometry(RID p_portal, const Vector<Vector3> &p_points, float p_margin) {
Portal *portal = portal_owner.getornull(p_portal);
ERR_FAIL_COND(!portal);
ERR_FAIL_COND(!portal->scenario);
portal->scenario->_portal_renderer.portal_set_geometry(portal->scenario_portal_id, p_points);
}
void VisualServerScene::portal_link(RID p_portal, RID p_room_from, RID p_room_to, bool p_two_way) {
Portal *portal = portal_owner.getornull(p_portal);
ERR_FAIL_COND(!portal);
ERR_FAIL_COND(!portal->scenario);
Room *room_from = room_owner.getornull(p_room_from);
ERR_FAIL_COND(!room_from);
Room *room_to = room_owner.getornull(p_room_to);
ERR_FAIL_COND(!room_to);
portal->scenario->_portal_renderer.portal_link(portal->scenario_portal_id, room_from->scenario_room_id, room_to->scenario_room_id, p_two_way);
}
void VisualServerScene::portal_set_active(RID p_portal, bool p_active) {
Portal *portal = portal_owner.getornull(p_portal);
ERR_FAIL_COND(!portal);
ERR_FAIL_COND(!portal->scenario);
portal->scenario->_portal_renderer.portal_set_active(portal->scenario_portal_id, p_active);
}
RID VisualServerScene::ghost_create() {
Ghost *ci = memnew(Ghost);
ERR_FAIL_COND_V(!ci, RID());
RID ci_rid = ghost_owner.make_rid(ci);
return ci_rid;
}
void VisualServerScene::ghost_set_scenario(RID p_ghost, RID p_scenario, ObjectID p_id, const AABB &p_aabb) {
Ghost *ci = ghost_owner.getornull(p_ghost);
ERR_FAIL_COND(!ci);
ci->aabb = p_aabb;
ci->object_id = p_id;
Scenario *scenario = scenario_owner.getornull(p_scenario);
// noop?
if (ci->scenario == scenario) {
return;
}
// if the portal is in a scenario already, remove it
if (ci->scenario) {
_ghost_destroy_occlusion_rep(ci);
ci->scenario = nullptr;
}
// create when entering the world
if (scenario) {
ci->scenario = scenario;
// defer the actual creation to here
_ghost_create_occlusion_rep(ci);
}
}
void VisualServerScene::ghost_update(RID p_ghost, const AABB &p_aabb) {
Ghost *ci = ghost_owner.getornull(p_ghost);
ERR_FAIL_COND(!ci);
ERR_FAIL_COND(!ci->scenario);
ci->aabb = p_aabb;
if (ci->rghost_handle) {
ci->scenario->_portal_renderer.rghost_update(ci->rghost_handle, p_aabb);
}
}
void VisualServerScene::_ghost_create_occlusion_rep(Ghost *p_ghost) {
ERR_FAIL_COND(!p_ghost);
ERR_FAIL_COND(!p_ghost->scenario);
if (!p_ghost->rghost_handle) {
p_ghost->rghost_handle = p_ghost->scenario->_portal_renderer.rghost_create(p_ghost->object_id, p_ghost->aabb);
}
}
void VisualServerScene::_ghost_destroy_occlusion_rep(Ghost *p_ghost) {
ERR_FAIL_COND(!p_ghost);
ERR_FAIL_COND(!p_ghost->scenario);
// not an error, can occur
if (!p_ghost->rghost_handle) {
return;
}
p_ghost->scenario->_portal_renderer.rghost_destroy(p_ghost->rghost_handle);
p_ghost->rghost_handle = 0;
}
RID VisualServerScene::roomgroup_create() {
RoomGroup *rg = memnew(RoomGroup);
ERR_FAIL_COND_V(!rg, RID());
RID roomgroup_rid = roomgroup_owner.make_rid(rg);
return roomgroup_rid;
}
void VisualServerScene::roomgroup_prepare(RID p_roomgroup, ObjectID p_roomgroup_object_id) {
RoomGroup *roomgroup = roomgroup_owner.getornull(p_roomgroup);
ERR_FAIL_COND(!roomgroup);
ERR_FAIL_COND(!roomgroup->scenario);
roomgroup->scenario->_portal_renderer.roomgroup_prepare(roomgroup->scenario_roomgroup_id, p_roomgroup_object_id);
}
void VisualServerScene::roomgroup_set_scenario(RID p_roomgroup, RID p_scenario) {
RoomGroup *rg = roomgroup_owner.getornull(p_roomgroup);
ERR_FAIL_COND(!rg);
Scenario *scenario = scenario_owner.getornull(p_scenario);
// noop?
if (rg->scenario == scenario) {
return;
}
// if the portal is in a scenario already, remove it
if (rg->scenario) {
rg->scenario->_portal_renderer.roomgroup_destroy(rg->scenario_roomgroup_id);
rg->scenario = nullptr;
rg->scenario_roomgroup_id = 0;
}
// create when entering the world
if (scenario) {
rg->scenario = scenario;
// defer the actual creation to here
rg->scenario_roomgroup_id = scenario->_portal_renderer.roomgroup_create();
}
}
void VisualServerScene::roomgroup_add_room(RID p_roomgroup, RID p_room) {
RoomGroup *roomgroup = roomgroup_owner.getornull(p_roomgroup);
ERR_FAIL_COND(!roomgroup);
ERR_FAIL_COND(!roomgroup->scenario);
Room *room = room_owner.getornull(p_room);
ERR_FAIL_COND(!room);
ERR_FAIL_COND(!room->scenario);
ERR_FAIL_COND(roomgroup->scenario != room->scenario);
roomgroup->scenario->_portal_renderer.roomgroup_add_room(roomgroup->scenario_roomgroup_id, room->scenario_room_id);
}
// Rooms
void VisualServerScene::callbacks_register(VisualServerCallbacks *p_callbacks) {
_visual_server_callbacks = p_callbacks;
}
// the room has to be associated with a scenario, this is assumed to be
// the same scenario as the room node
RID VisualServerScene::room_create() {
Room *room = memnew(Room);
ERR_FAIL_COND_V(!room, RID());
RID room_rid = room_owner.make_rid(room);
return room_rid;
}
// should not be called multiple times, different scenarios etc, but just in case, we will support this
void VisualServerScene::room_set_scenario(RID p_room, RID p_scenario) {
Room *room = room_owner.getornull(p_room);
ERR_FAIL_COND(!room);
Scenario *scenario = scenario_owner.getornull(p_scenario);
// no change?
if (room->scenario == scenario) {
return;
}
// if the room has an existing scenario, remove from it
if (room->scenario) {
room->scenario->_portal_renderer.room_destroy(room->scenario_room_id);
room->scenario = nullptr;
room->scenario_room_id = 0;
}
// create when entering the world
if (scenario) {
room->scenario = scenario;
// defer the actual creation to here
room->scenario_room_id = scenario->_portal_renderer.room_create();
}
}
void VisualServerScene::room_add_ghost(RID p_room, ObjectID p_object_id, const AABB &p_aabb) {
Room *room = room_owner.getornull(p_room);
ERR_FAIL_COND(!room);
ERR_FAIL_COND(!room->scenario);
room->scenario->_portal_renderer.room_add_ghost(room->scenario_room_id, p_object_id, p_aabb);
}
void VisualServerScene::room_add_instance(RID p_room, RID p_instance, const AABB &p_aabb, const Vector<Vector3> &p_object_pts) {
Room *room = room_owner.getornull(p_room);
ERR_FAIL_COND(!room);
ERR_FAIL_COND(!room->scenario);
Instance *instance = instance_owner.getornull(p_instance);
ERR_FAIL_COND(!instance);
AABB bb = p_aabb;
// the aabb passed from the client takes no account of the extra cull margin,
// so we need to add this manually.
// It is assumed it is in world space.
if (instance->extra_margin != 0.0) {
bb.grow_by(instance->extra_margin);
}
bool dynamic = false;
// don't add if portal mode is not static or dynamic
switch (instance->portal_mode) {
default: {
return; // this should be taken care of by the calling function, but just in case
} break;
case VisualServer::InstancePortalMode::INSTANCE_PORTAL_MODE_DYNAMIC: {
dynamic = true;
} break;
case VisualServer::InstancePortalMode::INSTANCE_PORTAL_MODE_STATIC: {
dynamic = false;
} break;
}
instance->occlusion_handle = room->scenario->_portal_renderer.room_add_instance(room->scenario_room_id, p_instance, bb, dynamic, p_object_pts);
}
void VisualServerScene::room_prepare(RID p_room, int32_t p_priority) {
Room *room = room_owner.getornull(p_room);
ERR_FAIL_COND(!room);
ERR_FAIL_COND(!room->scenario);
room->scenario->_portal_renderer.room_prepare(room->scenario_room_id, p_priority);
}
void VisualServerScene::room_set_bound(RID p_room, ObjectID p_room_object_id, const Vector<Plane> &p_convex, const AABB &p_aabb, const Vector<Vector3> &p_verts) {
Room *room = room_owner.getornull(p_room);
ERR_FAIL_COND(!room);
ERR_FAIL_COND(!room->scenario);
room->scenario->_portal_renderer.room_set_bound(room->scenario_room_id, p_room_object_id, p_convex, p_aabb, p_verts);
}
void VisualServerScene::rooms_unload(RID p_scenario) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_unload();
}
void VisualServerScene::rooms_and_portals_clear(RID p_scenario) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_and_portals_clear();
}
void VisualServerScene::rooms_finalize(RID p_scenario, bool p_generate_pvs, bool p_cull_using_pvs, bool p_use_secondary_pvs, bool p_use_signals, String p_pvs_filename) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_finalize(p_generate_pvs, p_cull_using_pvs, p_use_secondary_pvs, p_use_signals, p_pvs_filename);
}
void VisualServerScene::rooms_override_camera(RID p_scenario, bool p_override, const Vector3 &p_point, const Vector<Plane> *p_convex) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_override_camera(p_override, p_point, p_convex);
}
void VisualServerScene::rooms_set_active(RID p_scenario, bool p_active) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_set_active(p_active);
}
void VisualServerScene::rooms_set_params(RID p_scenario, int p_portal_depth_limit) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_set_params(p_portal_depth_limit);
}
void VisualServerScene::rooms_set_debug_feature(RID p_scenario, VisualServer::RoomsDebugFeature p_feature, bool p_active) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
switch (p_feature) {
default: {
} break;
case VisualServer::ROOMS_DEBUG_SPRAWL: {
scenario->_portal_renderer.set_debug_sprawl(p_active);
} break;
}
}
void VisualServerScene::rooms_update_gameplay_monitor(RID p_scenario, const Vector<Vector3> &p_camera_positions) {
Scenario *scenario = scenario_owner.getornull(p_scenario);
ERR_FAIL_COND(!scenario);
scenario->_portal_renderer.rooms_update_gameplay_monitor(p_camera_positions);
}
Vector<ObjectID> VisualServerScene::instances_cull_aabb(const AABB &p_aabb, RID p_scenario) const { Vector<ObjectID> VisualServerScene::instances_cull_aabb(const AABB &p_aabb, RID p_scenario) const {
Vector<ObjectID> instances; Vector<ObjectID> instances;
Scenario *scenario = scenario_owner.get(p_scenario); Scenario *scenario = scenario_owner.get(p_scenario);
@ -958,6 +1372,40 @@ Vector<ObjectID> VisualServerScene::instances_cull_convex(const Vector<Plane> &p
return instances; return instances;
} }
// thin wrapper to allow rooms / portals to take over culling if active
int VisualServerScene::_cull_convex_from_point(Scenario *p_scenario, const Vector3 &p_point, const Vector<Plane> &p_convex, Instance **p_result_array, int p_result_max, int32_t &r_previous_room_id_hint, uint32_t p_mask) {
int res = -1;
if (p_scenario->_portal_renderer.is_active()) {
// Note that the portal renderer ASSUMES that the planes exactly match the convention in
// CameraMatrix of enum Planes (6 planes, in order, near, far etc)
// If this is not the case, it should not be used.
res = p_scenario->_portal_renderer.cull_convex(p_point, p_convex, (VSInstance **)p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
}
// fallback to BVH / octree if portals not active
if (res == -1) {
res = p_scenario->sps->cull_convex(p_convex, p_result_array, p_result_max, p_mask);
}
return res;
}
void VisualServerScene::_rooms_instance_update(Instance *p_instance, const AABB &p_aabb) {
// magic number for instances in the room / portal system, but not requiring an update
// (due to being a STATIC or DYNAMIC object within a room)
// Must match the value in PortalRenderer in VisualServer
const uint32_t OCCLUSION_HANDLE_ROOM_BIT = 1 << 31;
// if the instance is a moving object in the room / portal system, update it
// Note that if rooms and portals is not in use, occlusion_handle should be zero in all cases unless the portal_mode
// has been set to global or roaming. (which is unlikely as the default is static).
// The exception is editor user interface elements.
// These are always set to global and will always keep their aabb up to date in the portal renderer unnecessarily.
// There is no easy way around this, but it should be very cheap, and have no impact outside the editor.
if (p_instance->occlusion_handle && (p_instance->occlusion_handle != OCCLUSION_HANDLE_ROOM_BIT)) {
p_instance->scenario->_portal_renderer.instance_moving_update(p_instance->occlusion_handle, p_aabb);
}
}
void VisualServerScene::instance_geometry_set_flag(RID p_instance, VS::InstanceFlags p_flags, bool p_enabled) { void VisualServerScene::instance_geometry_set_flag(RID p_instance, VS::InstanceFlags p_flags, bool p_enabled) {
Instance *instance = instance_owner.get(p_instance); Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_COND(!instance); ERR_FAIL_COND(!instance);
@ -1094,6 +1542,9 @@ void VisualServerScene::_update_instance(Instance *p_instance) {
p_instance->scenario->sps->move(p_instance->spatial_partition_id, new_aabb); p_instance->scenario->sps->move(p_instance->spatial_partition_id, new_aabb);
} }
// keep rooms and portals instance up to date if present
_rooms_instance_update(p_instance, new_aabb);
} }
void VisualServerScene::_update_instance_aabb(Instance *p_instance) { void VisualServerScene::_update_instance_aabb(Instance *p_instance) {
@ -1746,7 +2197,7 @@ bool VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
Vector<Plane> planes = cm.get_projection_planes(xform); Vector<Plane> planes = cm.get_projection_planes(xform);
int cull_count = p_scenario->sps->cull_convex(planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, VS::INSTANCE_GEOMETRY_MASK); int cull_count = _cull_convex_from_point(p_scenario, light_transform.origin, planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, light->previous_room_id_hint, VS::INSTANCE_GEOMETRY_MASK);
Plane near_plane(xform.origin, -xform.basis.get_axis(2)); Plane near_plane(xform.origin, -xform.basis.get_axis(2));
for (int j = 0; j < cull_count; j++) { for (int j = 0; j < cull_count; j++) {
@ -1781,7 +2232,7 @@ bool VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
cm.set_perspective(angle * 2.0, 1.0, 0.01, radius); cm.set_perspective(angle * 2.0, 1.0, 0.01, radius);
Vector<Plane> planes = cm.get_projection_planes(light_transform); Vector<Plane> planes = cm.get_projection_planes(light_transform);
int cull_count = p_scenario->sps->cull_convex(planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, VS::INSTANCE_GEOMETRY_MASK); int cull_count = _cull_convex_from_point(p_scenario, light_transform.origin, planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, light->previous_room_id_hint, VS::INSTANCE_GEOMETRY_MASK);
Plane near_plane(light_transform.origin, -light_transform.basis.get_axis(2)); Plane near_plane(light_transform.origin, -light_transform.basis.get_axis(2));
for (int j = 0; j < cull_count; j++) { for (int j = 0; j < cull_count; j++) {
@ -1851,7 +2302,7 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view
} break; } break;
} }
_prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); _prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
_render_scene(camera->transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); _render_scene(camera->transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1);
#endif #endif
} }
@ -1930,17 +2381,17 @@ void VisualServerScene::render_camera(Ref<ARVRInterface> &p_interface, ARVRInter
mono_transform *= apply_z_shift; mono_transform *= apply_z_shift;
// now prepare our scene with our adjusted transform projection matrix // now prepare our scene with our adjusted transform projection matrix
_prepare_scene(mono_transform, combined_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); _prepare_scene(mono_transform, combined_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
} else if (p_eye == ARVRInterface::EYE_MONO) { } else if (p_eye == ARVRInterface::EYE_MONO) {
// For mono render, prepare as per usual // For mono render, prepare as per usual
_prepare_scene(cam_transform, camera_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); _prepare_scene(cam_transform, camera_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
} }
// And render our scene... // And render our scene...
_render_scene(cam_transform, camera_matrix, p_eye, false, camera->env, p_scenario, p_shadow_atlas, RID(), -1); _render_scene(cam_transform, camera_matrix, p_eye, false, camera->env, p_scenario, p_shadow_atlas, RID(), -1);
}; };
void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe) { void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int32_t &r_previous_room_id_hint) {
// Note, in stereo rendering: // Note, in stereo rendering:
// - p_cam_transform will be a transform in the middle of our two eyes // - p_cam_transform will be a transform in the middle of our two eyes
// - p_cam_projection is a wider frustrum that encompasses both eyes // - p_cam_projection is a wider frustrum that encompasses both eyes
@ -1960,7 +2411,7 @@ void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const Ca
float z_far = p_cam_projection.get_z_far(); float z_far = p_cam_projection.get_z_far();
/* STEP 2 - CULL */ /* STEP 2 - CULL */
instance_cull_count = scenario->sps->cull_convex(planes, instance_cull_result, MAX_INSTANCE_CULL); instance_cull_count = _cull_convex_from_point(scenario, p_cam_transform.origin, planes, instance_cull_result, MAX_INSTANCE_CULL, r_previous_room_id_hint);
light_cull_count = 0; light_cull_count = 0;
reflection_probe_cull_count = 0; reflection_probe_cull_count = 0;
@ -2339,7 +2790,7 @@ bool VisualServerScene::_render_reflection_probe_step(Instance *p_instance, int
shadow_atlas = scenario->reflection_probe_shadow_atlas; shadow_atlas = scenario->reflection_probe_shadow_atlas;
} }
_prepare_scene(xform, cm, false, RID(), VSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance); _prepare_scene(xform, cm, false, RID(), VSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, reflection_probe->previous_room_id_hint);
_render_scene(xform, cm, 0, false, RID(), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, p_step); _render_scene(xform, cm, 0, false, RID(), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, p_step);
} else { } else {
@ -3524,6 +3975,22 @@ bool VisualServerScene::free(RID p_rid) {
instance_owner.free(p_rid); instance_owner.free(p_rid);
memdelete(instance); memdelete(instance);
} else if (room_owner.owns(p_rid)) {
Room *room = room_owner.get(p_rid);
room_owner.free(p_rid);
memdelete(room);
} else if (portal_owner.owns(p_rid)) {
Portal *portal = portal_owner.get(p_rid);
portal_owner.free(p_rid);
memdelete(portal);
} else if (ghost_owner.owns(p_rid)) {
Ghost *ghost = ghost_owner.get(p_rid);
ghost_owner.free(p_rid);
memdelete(ghost);
} else if (roomgroup_owner.owns(p_rid)) {
RoomGroup *roomgroup = roomgroup_owner.get(p_rid);
roomgroup_owner.free(p_rid);
memdelete(roomgroup);
} else { } else {
return false; return false;
} }
@ -3540,6 +4007,7 @@ VisualServerScene::VisualServerScene() {
render_pass = 1; render_pass = 1;
singleton = this; singleton = this;
_use_bvh = GLOBAL_DEF("rendering/quality/spatial_partitioning/use_bvh", true); _use_bvh = GLOBAL_DEF("rendering/quality/spatial_partitioning/use_bvh", true);
_visual_server_callbacks = nullptr;
} }
VisualServerScene::~VisualServerScene() { VisualServerScene::~VisualServerScene() {

View File

@ -40,6 +40,7 @@
#include "core/os/thread.h" #include "core/os/thread.h"
#include "core/safe_refcount.h" #include "core/safe_refcount.h"
#include "core/self_list.h" #include "core/self_list.h"
#include "portals/portal_renderer.h"
#include "servers/arvr/arvr_interface.h" #include "servers/arvr/arvr_interface.h"
class VisualServerScene { class VisualServerScene {
@ -54,8 +55,6 @@ public:
}; };
uint64_t render_pass; uint64_t render_pass;
bool _use_bvh;
static VisualServerScene *singleton; static VisualServerScene *singleton;
/* CAMERA API */ /* CAMERA API */
@ -76,6 +75,7 @@ public:
RID env; RID env;
Transform transform; Transform transform;
int32_t previous_room_id_hint;
Camera() { Camera() {
visible_layers = 0xFFFFFFFF; visible_layers = 0xFFFFFFFF;
@ -86,6 +86,7 @@ public:
size = 1.0; size = 1.0;
offset = Vector2(); offset = Vector2();
vaspect = false; vaspect = false;
previous_room_id_hint = -1;
} }
}; };
@ -187,6 +188,7 @@ public:
RID self; RID self;
SpatialPartitioningScene *sps; SpatialPartitioningScene *sps;
PortalRenderer _portal_renderer;
List<Instance *> directional_lights; List<Instance *> directional_lights;
RID environment; RID environment;
@ -222,6 +224,11 @@ public:
RID self; RID self;
//scenario stuff //scenario stuff
SpatialPartitionID spatial_partition_id; SpatialPartitionID spatial_partition_id;
// rooms & portals
OcclusionHandle occlusion_handle; // handle of instance in occlusion system (or 0)
VisualServer::InstancePortalMode portal_mode;
Scenario *scenario; Scenario *scenario;
SelfList<Instance> scenario_item; SelfList<Instance> scenario_item;
@ -272,6 +279,9 @@ public:
object_id = 0; object_id = 0;
visible = true; visible = true;
occlusion_handle = 0;
portal_mode = VisualServer::InstancePortalMode::INSTANCE_PORTAL_MODE_STATIC;
lod_begin = 0; lod_begin = 0;
lod_end = 0; lod_end = 0;
lod_begin_hysteresis = 0; lod_begin_hysteresis = 0;
@ -335,11 +345,13 @@ public:
SelfList<InstanceReflectionProbeData> update_list; SelfList<InstanceReflectionProbeData> update_list;
int render_step; int render_step;
int32_t previous_room_id_hint;
InstanceReflectionProbeData() : InstanceReflectionProbeData() :
update_list(this) { update_list(this) {
reflection_dirty = true; reflection_dirty = true;
render_step = -1; render_step = -1;
previous_room_id_hint = -1;
} }
}; };
@ -360,12 +372,14 @@ public:
List<PairInfo> geometries; List<PairInfo> geometries;
Instance *baked_light; Instance *baked_light;
int32_t previous_room_id_hint;
InstanceLightData() { InstanceLightData() {
shadow_dirty = true; shadow_dirty = true;
D = nullptr; D = nullptr;
last_version = 0; last_version = 0;
baked_light = nullptr; baked_light = nullptr;
previous_room_id_hint = -1;
} }
}; };
@ -515,11 +529,135 @@ public:
virtual void instance_set_extra_visibility_margin(RID p_instance, real_t p_margin); virtual void instance_set_extra_visibility_margin(RID p_instance, real_t p_margin);
// Portals
virtual void instance_set_portal_mode(RID p_instance, VisualServer::InstancePortalMode p_mode);
bool _instance_get_transformed_aabb(RID p_instance, AABB &r_aabb);
void *_instance_get_from_rid(RID p_instance);
bool _instance_cull_check(VSInstance *p_instance, uint32_t p_cull_mask) const {
uint32_t pairable_type = 1 << ((Instance *)p_instance)->base_type;
return pairable_type & p_cull_mask;
}
ObjectID _instance_get_object_ID(VSInstance *p_instance) const {
if (p_instance) {
return ((Instance *)p_instance)->object_id;
}
return 0;
}
private:
void _instance_create_occlusion_rep(Instance *p_instance);
void _instance_destroy_occlusion_rep(Instance *p_instance);
public:
struct Ghost : RID_Data {
// all interations with actual ghosts are indirect, as the ghost is part of the scenario
Scenario *scenario = nullptr;
uint32_t object_id = 0;
RGhostHandle rghost_handle = 0; // handle in occlusion system (or 0)
AABB aabb;
virtual ~Ghost() {
if (scenario) {
if (rghost_handle) {
scenario->_portal_renderer.rghost_destroy(rghost_handle);
rghost_handle = 0;
}
scenario = nullptr;
}
}
};
RID_Owner<Ghost> ghost_owner;
virtual RID ghost_create();
virtual void ghost_set_scenario(RID p_ghost, RID p_scenario, ObjectID p_id, const AABB &p_aabb);
virtual void ghost_update(RID p_ghost, const AABB &p_aabb);
private:
void _ghost_create_occlusion_rep(Ghost *p_ghost);
void _ghost_destroy_occlusion_rep(Ghost *p_ghost);
public:
struct Portal : RID_Data {
// all interations with actual portals are indirect, as the portal is part of the scenario
uint32_t scenario_portal_id = 0;
Scenario *scenario = nullptr;
virtual ~Portal() {
if (scenario) {
scenario->_portal_renderer.portal_destroy(scenario_portal_id);
scenario = nullptr;
scenario_portal_id = 0;
}
}
};
RID_Owner<Portal> portal_owner;
virtual RID portal_create();
virtual void portal_set_scenario(RID p_portal, RID p_scenario);
virtual void portal_set_geometry(RID p_portal, const Vector<Vector3> &p_points, float p_margin);
virtual void portal_link(RID p_portal, RID p_room_from, RID p_room_to, bool p_two_way);
virtual void portal_set_active(RID p_portal, bool p_active);
// RoomGroups
struct RoomGroup : RID_Data {
// all interations with actual roomgroups are indirect, as the roomgroup is part of the scenario
uint32_t scenario_roomgroup_id = 0;
Scenario *scenario = nullptr;
virtual ~RoomGroup() {
if (scenario) {
scenario->_portal_renderer.roomgroup_destroy(scenario_roomgroup_id);
scenario = nullptr;
scenario_roomgroup_id = 0;
}
}
};
RID_Owner<RoomGroup> roomgroup_owner;
virtual RID roomgroup_create();
virtual void roomgroup_prepare(RID p_roomgroup, ObjectID p_roomgroup_object_id);
virtual void roomgroup_set_scenario(RID p_roomgroup, RID p_scenario);
virtual void roomgroup_add_room(RID p_roomgroup, RID p_room);
// Rooms
struct Room : RID_Data {
// all interations with actual rooms are indirect, as the room is part of the scenario
uint32_t scenario_room_id = 0;
Scenario *scenario = nullptr;
virtual ~Room() {
if (scenario) {
scenario->_portal_renderer.room_destroy(scenario_room_id);
scenario = nullptr;
scenario_room_id = 0;
}
}
};
RID_Owner<Room> room_owner;
virtual RID room_create();
virtual void room_set_scenario(RID p_room, RID p_scenario);
virtual void room_add_instance(RID p_room, RID p_instance, const AABB &p_aabb, const Vector<Vector3> &p_object_pts);
virtual void room_add_ghost(RID p_room, ObjectID p_object_id, const AABB &p_aabb);
virtual void room_set_bound(RID p_room, ObjectID p_room_object_id, const Vector<Plane> &p_convex, const AABB &p_aabb, const Vector<Vector3> &p_verts);
virtual void room_prepare(RID p_room, int32_t p_priority);
virtual void rooms_and_portals_clear(RID p_scenario);
virtual void rooms_unload(RID p_scenario);
virtual void rooms_finalize(RID p_scenario, bool p_generate_pvs, bool p_cull_using_pvs, bool p_use_secondary_pvs, bool p_use_signals, String p_pvs_filename);
virtual void rooms_override_camera(RID p_scenario, bool p_override, const Vector3 &p_point, const Vector<Plane> *p_convex);
virtual void rooms_set_active(RID p_scenario, bool p_active);
virtual void rooms_set_params(RID p_scenario, int p_portal_depth_limit);
virtual void rooms_set_debug_feature(RID p_scenario, VisualServer::RoomsDebugFeature p_feature, bool p_active);
virtual void rooms_update_gameplay_monitor(RID p_scenario, const Vector<Vector3> &p_camera_positions);
virtual void callbacks_register(VisualServerCallbacks *p_callbacks);
VisualServerCallbacks *get_callbacks() const { return _visual_server_callbacks; }
// don't use these in a game! // don't use these in a game!
virtual Vector<ObjectID> instances_cull_aabb(const AABB &p_aabb, RID p_scenario = RID()) const; virtual Vector<ObjectID> instances_cull_aabb(const AABB &p_aabb, RID p_scenario = RID()) const;
virtual Vector<ObjectID> instances_cull_ray(const Vector3 &p_from, const Vector3 &p_to, RID p_scenario = RID()) const; virtual Vector<ObjectID> instances_cull_ray(const Vector3 &p_from, const Vector3 &p_to, RID p_scenario = RID()) const;
virtual Vector<ObjectID> instances_cull_convex(const Vector<Plane> &p_convex, RID p_scenario = RID()) const; virtual Vector<ObjectID> instances_cull_convex(const Vector<Plane> &p_convex, RID p_scenario = RID()) const;
// internal (uses portals when available)
int _cull_convex_from_point(Scenario *p_scenario, const Vector3 &p_point, const Vector<Plane> &p_convex, Instance **p_result_array, int p_result_max, int32_t &r_previous_room_id_hint, uint32_t p_mask = 0xFFFFFFFF);
void _rooms_instance_update(Instance *p_instance, const AABB &p_aabb);
virtual void instance_geometry_set_flag(RID p_instance, VS::InstanceFlags p_flags, bool p_enabled); virtual void instance_geometry_set_flag(RID p_instance, VS::InstanceFlags p_flags, bool p_enabled);
virtual void instance_geometry_set_cast_shadows_setting(RID p_instance, VS::ShadowCastingSetting p_shadow_casting_setting); virtual void instance_geometry_set_cast_shadows_setting(RID p_instance, VS::ShadowCastingSetting p_shadow_casting_setting);
virtual void instance_geometry_set_material_override(RID p_instance, RID p_material); virtual void instance_geometry_set_material_override(RID p_instance, RID p_material);
@ -534,7 +672,7 @@ public:
_FORCE_INLINE_ bool _light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario); _FORCE_INLINE_ bool _light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario);
void _prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe); void _prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int32_t &r_previous_room_id_hint);
void _render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, const int p_eye, bool p_cam_orthogonal, RID p_force_environment, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass); void _render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, const int p_eye, bool p_cam_orthogonal, RID p_force_environment, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass);
void render_empty_scene(RID p_scenario, RID p_shadow_atlas); void render_empty_scene(RID p_scenario, RID p_shadow_atlas);
@ -590,6 +728,11 @@ public:
bool free(RID p_rid); bool free(RID p_rid);
private:
bool _use_bvh;
VisualServerCallbacks *_visual_server_callbacks;
public:
VisualServerScene(); VisualServerScene();
virtual ~VisualServerScene(); virtual ~VisualServerScene();
}; };

View File

@ -140,6 +140,10 @@ void VisualServerWrapMT::finish() {
canvas_item_free_cached_ids(); canvas_item_free_cached_ids();
canvas_light_occluder_free_cached_ids(); canvas_light_occluder_free_cached_ids();
canvas_occluder_polygon_free_cached_ids(); canvas_occluder_polygon_free_cached_ids();
room_free_cached_ids();
roomgroup_free_cached_ids();
portal_free_cached_ids();
ghost_free_cached_ids();
} }
void VisualServerWrapMT::set_use_vsync_callback(bool p_enable) { void VisualServerWrapMT::set_use_vsync_callback(bool p_enable) {

View File

@ -481,6 +481,44 @@ public:
FUNC2(instance_set_extra_visibility_margin, RID, real_t) FUNC2(instance_set_extra_visibility_margin, RID, real_t)
// Portals
FUNC2(instance_set_portal_mode, RID, InstancePortalMode)
FUNCRID(ghost)
FUNC4(ghost_set_scenario, RID, RID, ObjectID, const AABB &)
FUNC2(ghost_update, RID, const AABB &)
FUNCRID(portal)
FUNC2(portal_set_scenario, RID, RID)
FUNC3(portal_set_geometry, RID, const Vector<Vector3> &, float)
FUNC4(portal_link, RID, RID, RID, bool)
FUNC2(portal_set_active, RID, bool)
// Roomgroups
FUNCRID(roomgroup)
FUNC2(roomgroup_prepare, RID, ObjectID)
FUNC2(roomgroup_set_scenario, RID, RID)
FUNC2(roomgroup_add_room, RID, RID)
// Rooms
FUNCRID(room)
FUNC2(room_set_scenario, RID, RID)
FUNC4(room_add_instance, RID, RID, const AABB &, const Vector<Vector3> &)
FUNC3(room_add_ghost, RID, ObjectID, const AABB &)
FUNC5(room_set_bound, RID, ObjectID, const Vector<Plane> &, const AABB &, const Vector<Vector3> &)
FUNC2(room_prepare, RID, int32_t)
FUNC1(rooms_and_portals_clear, RID)
FUNC1(rooms_unload, RID)
FUNC6(rooms_finalize, RID, bool, bool, bool, bool, String)
FUNC4(rooms_override_camera, RID, bool, const Vector3 &, const Vector<Plane> *)
FUNC2(rooms_set_active, RID, bool)
FUNC2(rooms_set_params, RID, int)
FUNC3(rooms_set_debug_feature, RID, RoomsDebugFeature, bool)
FUNC2(rooms_update_gameplay_monitor, RID, const Vector<Vector3> &)
// Callbacks
FUNC1(callbacks_register, VisualServerCallbacks *)
// don't use these in a game! // don't use these in a game!
FUNC2RC(Vector<ObjectID>, instances_cull_aabb, const AABB &, RID) FUNC2RC(Vector<ObjectID>, instances_cull_aabb, const AABB &, RID)
FUNC3RC(Vector<ObjectID>, instances_cull_ray, const Vector3 &, const Vector3 &, RID) FUNC3RC(Vector<ObjectID>, instances_cull_ray, const Vector3 &, const Vector3 &, RID)

View File

@ -2229,6 +2229,11 @@ RID VisualServer::instance_create2(RID p_base, RID p_scenario) {
RID instance = instance_create(); RID instance = instance_create();
instance_set_base(instance, p_base); instance_set_base(instance, p_base);
instance_set_scenario(instance, p_scenario); instance_set_scenario(instance, p_scenario);
// instance_create2 is used mainly by editor instances.
// These should not be culled by the portal system when it is active, so we set their mode to global,
// for frustum culling only.
instance_set_portal_mode(instance, VisualServer::INSTANCE_PORTAL_MODE_GLOBAL);
return instance; return instance;
} }

View File

@ -39,6 +39,8 @@
#include "core/rid.h" #include "core/rid.h"
#include "core/variant.h" #include "core/variant.h"
class VisualServerCallbacks;
class VisualServer : public Object { class VisualServer : public Object {
GDCLASS(VisualServer, Object); GDCLASS(VisualServer, Object);
@ -853,6 +855,57 @@ public:
virtual void instance_set_extra_visibility_margin(RID p_instance, real_t p_margin) = 0; virtual void instance_set_extra_visibility_margin(RID p_instance, real_t p_margin) = 0;
/* ROOMS AND PORTALS API */
// Portals
enum InstancePortalMode {
INSTANCE_PORTAL_MODE_STATIC, // not moving within a room
INSTANCE_PORTAL_MODE_DYNAMIC, // moving within room
INSTANCE_PORTAL_MODE_ROAMING, // moving between rooms
INSTANCE_PORTAL_MODE_GLOBAL, // frustum culled only
INSTANCE_PORTAL_MODE_IGNORE, // don't show at all - e.g. manual bounds, hidden portals
};
virtual void instance_set_portal_mode(RID p_instance, InstancePortalMode p_mode) = 0;
virtual RID ghost_create() = 0;
virtual void ghost_set_scenario(RID p_ghost, RID p_scenario, ObjectID p_id, const AABB &p_aabb) = 0;
virtual void ghost_update(RID p_ghost, const AABB &p_aabb) = 0;
virtual RID portal_create() = 0;
virtual void portal_set_scenario(RID p_portal, RID p_scenario) = 0;
virtual void portal_set_geometry(RID p_portal, const Vector<Vector3> &p_points, float p_margin) = 0;
virtual void portal_link(RID p_portal, RID p_room_from, RID p_room_to, bool p_two_way) = 0;
virtual void portal_set_active(RID p_portal, bool p_active) = 0;
// Roomgroups
virtual RID roomgroup_create() = 0;
virtual void roomgroup_prepare(RID p_roomgroup, ObjectID p_roomgroup_object_id) = 0;
virtual void roomgroup_set_scenario(RID p_roomgroup, RID p_scenario) = 0;
virtual void roomgroup_add_room(RID p_roomgroup, RID p_room) = 0;
// Rooms
enum RoomsDebugFeature {
ROOMS_DEBUG_SPRAWL,
};
virtual RID room_create() = 0;
virtual void room_set_scenario(RID p_room, RID p_scenario) = 0;
virtual void room_add_instance(RID p_room, RID p_instance, const AABB &p_aabb, const Vector<Vector3> &p_object_pts) = 0;
virtual void room_add_ghost(RID p_room, ObjectID p_object_id, const AABB &p_aabb) = 0;
virtual void room_set_bound(RID p_room, ObjectID p_room_object_id, const Vector<Plane> &p_convex, const AABB &p_aabb, const Vector<Vector3> &p_verts) = 0;
virtual void room_prepare(RID p_room, int32_t p_priority) = 0;
virtual void rooms_and_portals_clear(RID p_scenario) = 0;
virtual void rooms_unload(RID p_scenario) = 0;
virtual void rooms_finalize(RID p_scenario, bool p_generate_pvs, bool p_cull_using_pvs, bool p_use_secondary_pvs, bool p_use_signals, String p_pvs_filename) = 0;
virtual void rooms_override_camera(RID p_scenario, bool p_override, const Vector3 &p_point, const Vector<Plane> *p_convex) = 0;
virtual void rooms_set_active(RID p_scenario, bool p_active) = 0;
virtual void rooms_set_params(RID p_scenario, int p_portal_depth_limit) = 0;
virtual void rooms_set_debug_feature(RID p_scenario, RoomsDebugFeature p_feature, bool p_active) = 0;
virtual void rooms_update_gameplay_monitor(RID p_scenario, const Vector<Vector3> &p_camera_positions) = 0;
// callbacks are used to send messages back from the visual server to scene tree in thread friendly manner
virtual void callbacks_register(VisualServerCallbacks *p_callbacks) = 0;
// don't use these in a game! // don't use these in a game!
virtual Vector<ObjectID> instances_cull_aabb(const AABB &p_aabb, RID p_scenario = RID()) const = 0; virtual Vector<ObjectID> instances_cull_aabb(const AABB &p_aabb, RID p_scenario = RID()) const = 0;
virtual Vector<ObjectID> instances_cull_ray(const Vector3 &p_from, const Vector3 &p_to, RID p_scenario = RID()) const = 0; virtual Vector<ObjectID> instances_cull_ray(const Vector3 &p_from, const Vector3 &p_to, RID p_scenario = RID()) const = 0;
@ -1101,6 +1154,7 @@ VARIANT_ENUM_CAST(VisualServer::ViewportRenderInfo);
VARIANT_ENUM_CAST(VisualServer::ViewportDebugDraw); VARIANT_ENUM_CAST(VisualServer::ViewportDebugDraw);
VARIANT_ENUM_CAST(VisualServer::ScenarioDebugMode); VARIANT_ENUM_CAST(VisualServer::ScenarioDebugMode);
VARIANT_ENUM_CAST(VisualServer::InstanceType); VARIANT_ENUM_CAST(VisualServer::InstanceType);
VARIANT_ENUM_CAST(VisualServer::InstancePortalMode);
VARIANT_ENUM_CAST(VisualServer::NinePatchAxisMode); VARIANT_ENUM_CAST(VisualServer::NinePatchAxisMode);
VARIANT_ENUM_CAST(VisualServer::CanvasLightMode); VARIANT_ENUM_CAST(VisualServer::CanvasLightMode);
VARIANT_ENUM_CAST(VisualServer::CanvasLightShadowFilter); VARIANT_ENUM_CAST(VisualServer::CanvasLightShadowFilter);

View File

@ -0,0 +1,65 @@
/*************************************************************************/
/* visual_server_callbacks.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 "visual_server_callbacks.h"
#include "core/object.h"
void VisualServerCallbacks::lock() {
mutex.lock();
}
void VisualServerCallbacks::unlock() {
mutex.unlock();
}
void VisualServerCallbacks::flush() {
// should be ok without a lock ..
// is the most common case and should be quicker
if (!messages.size()) {
return;
}
lock();
for (int n = 0; n < messages.size(); n++) {
const Message &mess = messages[n];
Object *obj = ObjectDB::get_instance(mess.object_id);
if (!obj) {
continue;
}
obj->notification_callback(mess.type);
}
messages.clear();
unlock();
}

View File

@ -0,0 +1,66 @@
/*************************************************************************/
/* visual_server_callbacks.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef VISUAL_SERVER_CALLBACKS_H
#define VISUAL_SERVER_CALLBACKS_H
#include "core/local_vector.h"
#include "core/object_id.h"
#include "core/os/mutex.h"
class VisualServerCallbacks {
public:
enum CallbackType {
CALLBACK_NOTIFICATION_ENTER_GAMEPLAY,
CALLBACK_NOTIFICATION_EXIT_GAMEPLAY,
CALLBACK_SIGNAL_ENTER_GAMEPLAY,
CALLBACK_SIGNAL_EXIT_GAMEPLAY,
};
struct Message {
CallbackType type;
ObjectID object_id;
};
void lock();
void unlock();
void flush();
void push_message(const Message &p_message) { messages.push_back(p_message); }
int32_t get_num_messages() const { return messages.size(); }
const Message &get_message(int p_index) const { return messages[p_index]; }
void clear() { messages.clear(); }
private:
LocalVector<Message, int32_t> messages;
Mutex mutex;
};
#endif // VISUAL_SERVER_CALLBACKS_H