Merge pull request #57361 from lawnjelly/occ_poly_only
This commit is contained in:
commit
b6dbff7621
32 changed files with 2065 additions and 84 deletions
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
template <class T, class U = uint32_t, bool force_trivial = false>
|
template <class T, class U = uint32_t, bool force_trivial = false>
|
||||||
class LocalVector {
|
class LocalVector {
|
||||||
private:
|
protected:
|
||||||
U count = 0;
|
U count = 0;
|
||||||
U capacity = 0;
|
U capacity = 0;
|
||||||
T *data = nullptr;
|
T *data = nullptr;
|
||||||
|
@ -255,4 +255,9 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Integer default version
|
||||||
|
template <class T, class I = int32_t, bool force_trivial = false>
|
||||||
|
class LocalVectori : public LocalVector<T, I, force_trivial> {
|
||||||
|
};
|
||||||
|
|
||||||
#endif // LOCAL_VECTOR_H
|
#endif // LOCAL_VECTOR_H
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
#include "geometry.h"
|
#include "geometry.h"
|
||||||
|
|
||||||
|
#include "core/local_vector.h"
|
||||||
#include "core/print_string.h"
|
#include "core/print_string.h"
|
||||||
|
|
||||||
#include "thirdparty/misc/clipper.hpp"
|
#include "thirdparty/misc/clipper.hpp"
|
||||||
|
@ -53,6 +54,17 @@ bool Geometry::is_point_in_polygon(const Vector2 &p_point, const Vector<Vector2>
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
void Geometry::OccluderMeshData::clear() {
|
||||||
|
faces.clear();
|
||||||
|
vertices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Geometry::MeshData::clear() {
|
||||||
|
faces.clear();
|
||||||
|
edges.clear();
|
||||||
|
vertices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void Geometry::MeshData::optimize_vertices() {
|
void Geometry::MeshData::optimize_vertices() {
|
||||||
Map<int, int> vtx_remap;
|
Map<int, int> vtx_remap;
|
||||||
|
|
||||||
|
@ -1363,6 +1375,28 @@ Vector<Geometry::PackRectsResult> Geometry::partial_pack_rects(const Vector<Vect
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expects polygon as a triangle fan
|
||||||
|
real_t Geometry::find_polygon_area(const Vector3 *p_verts, int p_num_verts) {
|
||||||
|
if (!p_verts || (p_num_verts < 3)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Face3 f;
|
||||||
|
f.vertex[0] = p_verts[0];
|
||||||
|
f.vertex[1] = p_verts[1];
|
||||||
|
f.vertex[2] = p_verts[1];
|
||||||
|
|
||||||
|
real_t area = 0.0;
|
||||||
|
|
||||||
|
for (int n = 2; n < p_num_verts; n++) {
|
||||||
|
f.vertex[1] = f.vertex[2];
|
||||||
|
f.vertex[2] = p_verts[n];
|
||||||
|
area += Math::sqrt(f.get_twice_area_squared());
|
||||||
|
}
|
||||||
|
|
||||||
|
return area * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
// adapted from:
|
// adapted from:
|
||||||
// https://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order
|
// https://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order
|
||||||
void Geometry::sort_polygon_winding(Vector<Vector2> &r_verts, bool p_clockwise) {
|
void Geometry::sort_polygon_winding(Vector<Vector2> &r_verts, bool p_clockwise) {
|
||||||
|
|
|
@ -555,11 +555,17 @@ public:
|
||||||
double dot11 = v1.dot(v1);
|
double dot11 = v1.dot(v1);
|
||||||
double dot12 = v1.dot(v2);
|
double dot12 = v1.dot(v2);
|
||||||
|
|
||||||
|
// Check for divide by zero
|
||||||
|
double denom = dot00 * dot11 - dot01 * dot01;
|
||||||
|
if (denom == 0.0) {
|
||||||
|
return Vector3(0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
// Compute barycentric coordinates
|
// Compute barycentric coordinates
|
||||||
double invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
|
double invDenom = 1.0 / denom;
|
||||||
double b2 = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
double b2 = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
||||||
double b1 = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
double b1 = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
||||||
double b0 = 1.0f - b2 - b1;
|
double b0 = 1.0 - b2 - b1;
|
||||||
return Vector3(b0, b1, b2);
|
return Vector3(b0, b1, b2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -978,6 +984,24 @@ public:
|
||||||
Vector<Vector3> vertices;
|
Vector<Vector3> vertices;
|
||||||
|
|
||||||
void optimize_vertices();
|
void optimize_vertices();
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Occluder Meshes contain convex faces which may contain 0 to many convex holes.
|
||||||
|
// (holes are analogous to portals)
|
||||||
|
struct OccluderMeshData {
|
||||||
|
struct Hole {
|
||||||
|
LocalVectori<uint32_t> indices;
|
||||||
|
};
|
||||||
|
struct Face {
|
||||||
|
Plane plane;
|
||||||
|
bool two_way = false;
|
||||||
|
LocalVectori<uint32_t> indices;
|
||||||
|
LocalVectori<Hole> holes;
|
||||||
|
};
|
||||||
|
LocalVectori<Face> faces;
|
||||||
|
LocalVectori<Vector3> vertices;
|
||||||
|
void clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
_FORCE_INLINE_ static int get_uv84_normal_bit(const Vector3 &p_vector) {
|
_FORCE_INLINE_ static int get_uv84_normal_bit(const Vector3 &p_vector) {
|
||||||
|
@ -1070,6 +1094,7 @@ public:
|
||||||
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 sort_polygon_winding(Vector<Vector2> &r_verts, bool p_clockwise = true);
|
||||||
|
static real_t find_polygon_area(const Vector3 *p_verts, int p_num_verts);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,7 @@ public:
|
||||||
static _ALWAYS_INLINE_ double abs(double g) { return absd(g); }
|
static _ALWAYS_INLINE_ double abs(double g) { return absd(g); }
|
||||||
static _ALWAYS_INLINE_ float abs(float g) { return absf(g); }
|
static _ALWAYS_INLINE_ float abs(float g) { return absf(g); }
|
||||||
static _ALWAYS_INLINE_ int abs(int g) { return g > 0 ? g : -g; }
|
static _ALWAYS_INLINE_ int abs(int g) { return g > 0 ? g : -g; }
|
||||||
|
static _ALWAYS_INLINE_ int64_t abs(int64_t g) { return g > 0 ? g : -g; }
|
||||||
|
|
||||||
static _ALWAYS_INLINE_ double fposmod(double p_x, double p_y) {
|
static _ALWAYS_INLINE_ double fposmod(double p_x, double p_y) {
|
||||||
double value = Math::fmod(p_x, p_y);
|
double value = Math::fmod(p_x, p_y);
|
||||||
|
|
45
doc/classes/OccluderShapePolygon.xml
Normal file
45
doc/classes/OccluderShapePolygon.xml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<class name="OccluderShapePolygon" inherits="OccluderShape" version="3.5">
|
||||||
|
<brief_description>
|
||||||
|
Polygon occlusion primitive for use with the [Occluder] node.
|
||||||
|
</brief_description>
|
||||||
|
<description>
|
||||||
|
[OccluderShape]s are resources used by [Occluder] nodes, allowing geometric occlusion culling.
|
||||||
|
The polygon must be a convex polygon. The polygon points can be created and deleted either in the Editor inspector or by calling [code]set_polygon_points[/code]. The points of the edges can be set by dragging the handles in the Editor viewport.
|
||||||
|
Additionally each polygon occluder can optionally support a single hole. If you add at least three points in the Editor inspector to the hole, you can drag the edge points of the hole in the Editor viewport.
|
||||||
|
In general, the lower the number of edges in polygons and holes, the faster the system will operate at runtime, so in most cases you will want to use 4 points for each.
|
||||||
|
</description>
|
||||||
|
<tutorials>
|
||||||
|
</tutorials>
|
||||||
|
<methods>
|
||||||
|
<method name="set_hole_point">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="index" type="int" />
|
||||||
|
<argument index="1" name="position" type="Vector2" />
|
||||||
|
<description>
|
||||||
|
Sets an individual hole point position.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="set_polygon_point">
|
||||||
|
<return type="void" />
|
||||||
|
<argument index="0" name="index" type="int" />
|
||||||
|
<argument index="1" name="position" type="Vector2" />
|
||||||
|
<description>
|
||||||
|
Sets an individual polygon point position.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
</methods>
|
||||||
|
<members>
|
||||||
|
<member name="hole_points" type="PoolVector2Array" setter="set_hole_points" getter="get_hole_points" default="PoolVector2Array( )">
|
||||||
|
Allows changing the hole geometry from code.
|
||||||
|
</member>
|
||||||
|
<member name="polygon_points" type="PoolVector2Array" setter="set_polygon_points" getter="get_polygon_points" default="PoolVector2Array( 1, -1, 1, 1, -1, 1, -1, -1 )">
|
||||||
|
Allows changing the polygon geometry from code.
|
||||||
|
</member>
|
||||||
|
<member name="two_way" type="bool" setter="set_two_way" getter="is_two_way" default="true">
|
||||||
|
Specifies whether the occluder should operate one way only, or from both sides.
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
<constants>
|
||||||
|
</constants>
|
||||||
|
</class>
|
|
@ -1330,6 +1330,11 @@
|
||||||
<member name="rendering/misc/mesh_storage/split_stream" type="bool" setter="" getter="" default="false">
|
<member name="rendering/misc/mesh_storage/split_stream" type="bool" setter="" getter="" default="false">
|
||||||
On import, mesh vertex data will be split into two streams within a single vertex buffer, one for position data and the other for interleaved attributes data. Recommended to be enabled if targeting mobile devices. Requires manual reimport of meshes after toggling.
|
On import, mesh vertex data will be split into two streams within a single vertex buffer, one for position data and the other for interleaved attributes data. Recommended to be enabled if targeting mobile devices. Requires manual reimport of meshes after toggling.
|
||||||
</member>
|
</member>
|
||||||
|
<member name="rendering/misc/occlusion_culling/max_active_polygons" type="int" setter="" getter="" default="8">
|
||||||
|
Determines the maximum number of polygon occluders that will be used at any one time.
|
||||||
|
Although you can have many occluders in a scene, each frame the system will choose from these the most relevant based on a screen space metric, in order to give the best overall performance.
|
||||||
|
A greater number of polygons can potentially cull more objects, however the cost of culling calculations scales with the number of occluders.
|
||||||
|
</member>
|
||||||
<member name="rendering/misc/occlusion_culling/max_active_spheres" type="int" setter="" getter="" default="8">
|
<member name="rendering/misc/occlusion_culling/max_active_spheres" type="int" setter="" getter="" default="8">
|
||||||
Determines the maximum number of sphere occluders that will be used at any one time.
|
Determines the maximum number of sphere occluders that will be used at any one time.
|
||||||
Although you can have many occluders in a scene, each frame the system will choose from these the most relevant based on a screen space metric, in order to give the best overall performance.
|
Although you can have many occluders in a scene, each frame the system will choose from these the most relevant based on a screen space metric, in order to give the best overall performance.
|
||||||
|
|
|
@ -3674,7 +3674,11 @@ AABB SpatialEditorViewport::_calculate_spatial_bounds(const Spatial *p_parent, b
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bounds.size == Vector3() && p_parent->get_class_name() != StringName("Spatial")) {
|
if (bounds.size == Vector3() && p_parent->get_class_name() != StringName("Spatial")) {
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
bounds = p_parent->get_fallback_gizmo_aabb();
|
||||||
|
#else
|
||||||
bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
|
bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!p_exclude_toplevel_transform) {
|
if (!p_exclude_toplevel_transform) {
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
#include "scene/resources/cylinder_shape.h"
|
#include "scene/resources/cylinder_shape.h"
|
||||||
#include "scene/resources/height_map_shape.h"
|
#include "scene/resources/height_map_shape.h"
|
||||||
#include "scene/resources/occluder_shape.h"
|
#include "scene/resources/occluder_shape.h"
|
||||||
|
#include "scene/resources/occluder_shape_polygon.h"
|
||||||
#include "scene/resources/plane_shape.h"
|
#include "scene/resources/plane_shape.h"
|
||||||
#include "scene/resources/primitive_meshes.h"
|
#include "scene/resources/primitive_meshes.h"
|
||||||
#include "scene/resources/ray_shape.h"
|
#include "scene/resources/ray_shape.h"
|
||||||
|
@ -5000,6 +5001,8 @@ OccluderGizmoPlugin::OccluderGizmoPlugin() {
|
||||||
Color color_occluder = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(1.0, 0.0, 1.0));
|
Color color_occluder = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(1.0, 0.0, 1.0));
|
||||||
create_material("occluder", color_occluder, false, true, false);
|
create_material("occluder", color_occluder, false, true, false);
|
||||||
|
|
||||||
|
create_material("occluder_poly", Color(1, 1, 1, 1), false, false, true);
|
||||||
|
|
||||||
create_handle_material("occluder_handle");
|
create_handle_material("occluder_handle");
|
||||||
create_handle_material("extra_handle", false, SpatialEditor::get_singleton()->get_icon("EditorInternalHandle", "EditorIcons"));
|
create_handle_material("extra_handle", false, SpatialEditor::get_singleton()->get_icon("EditorInternalHandle", "EditorIcons"));
|
||||||
}
|
}
|
||||||
|
@ -5046,6 +5049,15 @@ String OccluderSpatialGizmo::get_handle_name(int p_idx) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OccluderShapePolygon *occ_poly = get_occluder_shape_poly();
|
||||||
|
if (occ_poly) {
|
||||||
|
if (p_idx < occ_poly->_poly_pts_local_raw.size()) {
|
||||||
|
return "Poly Point " + itos(p_idx);
|
||||||
|
} else {
|
||||||
|
return "Hole Point " + itos(p_idx - occ_poly->_poly_pts_local_raw.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5063,6 +5075,19 @@ Variant OccluderSpatialGizmo::get_handle_value(int p_idx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OccluderShapePolygon *occ_poly = get_occluder_shape_poly();
|
||||||
|
if (occ_poly) {
|
||||||
|
if (p_idx < occ_poly->_poly_pts_local_raw.size()) {
|
||||||
|
return occ_poly->_poly_pts_local_raw[p_idx];
|
||||||
|
} else {
|
||||||
|
p_idx -= occ_poly->_poly_pts_local_raw.size();
|
||||||
|
if (p_idx < occ_poly->_hole_pts_local_raw.size()) {
|
||||||
|
return occ_poly->_hole_pts_local_raw[p_idx];
|
||||||
|
}
|
||||||
|
return Vector2(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5145,16 +5170,63 @@ void OccluderSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OccluderShapePolygon *occ_poly = get_occluder_shape_poly();
|
||||||
|
if (occ_poly) {
|
||||||
|
Vector3 pt_local;
|
||||||
|
|
||||||
|
bool hole = p_idx >= occ_poly->_poly_pts_local_raw.size();
|
||||||
|
if (hole) {
|
||||||
|
p_idx -= occ_poly->_poly_pts_local_raw.size();
|
||||||
|
if (p_idx >= occ_poly->_hole_pts_local_raw.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pt_local = OccluderShapePolygon::_vec2to3(occ_poly->_hole_pts_local_raw[p_idx]);
|
||||||
|
} else {
|
||||||
|
pt_local = OccluderShapePolygon::_vec2to3(occ_poly->_poly_pts_local_raw[p_idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 pt_world = tr.xform(pt_local);
|
||||||
|
|
||||||
|
// get a normal from the global transform
|
||||||
|
Plane plane(Vector3(0, 0, 0), Vector3(0, 0, 1));
|
||||||
|
plane = tr.xform(plane);
|
||||||
|
|
||||||
|
// construct the plane that the 2d portal is defined in
|
||||||
|
plane = Plane(pt_world, plane.normal);
|
||||||
|
|
||||||
|
Vector3 inters;
|
||||||
|
|
||||||
|
if (plane.intersects_ray(ray_from, ray_dir, &inters)) {
|
||||||
|
// back calculate from the 3d intersection to the 2d portal plane
|
||||||
|
inters = tr_inv.xform(inters);
|
||||||
|
|
||||||
|
// snapping will be in 2d for portals, and the scale may make less sense,
|
||||||
|
// but better to offer at least some functionality
|
||||||
|
if (SpatialEditor::get_singleton()->is_snap_enabled()) {
|
||||||
|
float snap = SpatialEditor::get_singleton()->get_translate_snap();
|
||||||
|
inters.snap(Vector3(snap, snap, snap));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hole) {
|
||||||
|
occ_poly->set_hole_point(p_idx, Vector2(inters.x, inters.y));
|
||||||
|
} else {
|
||||||
|
occ_poly->set_polygon_point(p_idx, Vector2(inters.x, inters.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OccluderSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
|
void OccluderSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
|
||||||
|
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
|
||||||
|
|
||||||
OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
|
OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
|
||||||
if (occ_sphere) {
|
if (occ_sphere) {
|
||||||
Vector<Plane> spheres = occ_sphere->get_spheres();
|
Vector<Plane> spheres = occ_sphere->get_spheres();
|
||||||
int num_spheres = spheres.size();
|
int num_spheres = spheres.size();
|
||||||
|
|
||||||
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
|
|
||||||
|
|
||||||
if (p_idx >= num_spheres) {
|
if (p_idx >= num_spheres) {
|
||||||
p_idx -= num_spheres;
|
p_idx -= num_spheres;
|
||||||
|
|
||||||
|
@ -5170,24 +5242,49 @@ void OccluderSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bo
|
||||||
ur->commit_action();
|
ur->commit_action();
|
||||||
_occluder->property_list_changed_notify();
|
_occluder->property_list_changed_notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OccluderShapePolygon *occ_poly = get_occluder_shape_poly();
|
||||||
|
if (occ_poly) {
|
||||||
|
if (p_idx < occ_poly->_poly_pts_local_raw.size()) {
|
||||||
|
ur->create_action(TTR("Set Occluder Polygon Point Position"));
|
||||||
|
ur->add_do_method(occ_poly, "set_polygon_point", p_idx, occ_poly->_poly_pts_local_raw[p_idx]);
|
||||||
|
ur->add_undo_method(occ_poly, "set_polygon_point", p_idx, p_restore);
|
||||||
|
ur->commit_action();
|
||||||
|
_occluder->property_list_changed_notify();
|
||||||
|
} else {
|
||||||
|
p_idx -= occ_poly->_poly_pts_local_raw.size();
|
||||||
|
if (p_idx < occ_poly->_hole_pts_local_raw.size()) {
|
||||||
|
ur->create_action(TTR("Set Occluder Hole Point Position"));
|
||||||
|
ur->add_do_method(occ_poly, "set_hole_point", p_idx, occ_poly->_hole_pts_local_raw[p_idx]);
|
||||||
|
ur->add_undo_method(occ_poly, "set_hole_point", p_idx, p_restore);
|
||||||
|
ur->commit_action();
|
||||||
|
_occluder->property_list_changed_notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() {
|
OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() {
|
||||||
if (!_occluder) {
|
OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(get_occluder_shape());
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref<OccluderShape> rshape = _occluder->get_shape();
|
|
||||||
if (rshape.is_null() || !rshape.is_valid()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OccluderShape *shape = rshape.ptr();
|
|
||||||
OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(shape);
|
|
||||||
return occ_sphere;
|
return occ_sphere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OccluderShapePolygon *OccluderSpatialGizmo::get_occluder_shape_poly() const {
|
||||||
|
const OccluderShapePolygon *occ_poly = Object::cast_to<OccluderShapePolygon>(get_occluder_shape());
|
||||||
|
return occ_poly;
|
||||||
|
}
|
||||||
|
|
||||||
|
OccluderShapePolygon *OccluderSpatialGizmo::get_occluder_shape_poly() {
|
||||||
|
OccluderShapePolygon *occ_poly = Object::cast_to<OccluderShapePolygon>(get_occluder_shape());
|
||||||
|
return occ_poly;
|
||||||
|
}
|
||||||
|
|
||||||
const OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() const {
|
const OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() const {
|
||||||
|
const OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(get_occluder_shape());
|
||||||
|
return occ_sphere;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OccluderShape *OccluderSpatialGizmo::get_occluder_shape() const {
|
||||||
if (!_occluder) {
|
if (!_occluder) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -5197,9 +5294,20 @@ const OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() con
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OccluderShape *shape = rshape.ptr();
|
return rshape.ptr();
|
||||||
const OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(shape);
|
}
|
||||||
return occ_sphere;
|
|
||||||
|
OccluderShape *OccluderSpatialGizmo::get_occluder_shape() {
|
||||||
|
if (!_occluder) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<OccluderShape> rshape = _occluder->get_shape();
|
||||||
|
if (rshape.is_null() || !rshape.is_valid()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rshape.ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OccluderSpatialGizmo::redraw() {
|
void OccluderSpatialGizmo::redraw() {
|
||||||
|
@ -5258,9 +5366,90 @@ void OccluderSpatialGizmo::redraw() {
|
||||||
add_handles(handles, material_handle);
|
add_handles(handles, material_handle);
|
||||||
add_handles(radius_handles, material_extra_handle, false, true);
|
add_handles(radius_handles, material_extra_handle, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OccluderShapePolygon *occ_poly = get_occluder_shape_poly();
|
||||||
|
if (occ_poly) {
|
||||||
|
// main poly
|
||||||
|
_redraw_poly(false, occ_poly->_poly_pts_local, occ_poly->_poly_pts_local_raw);
|
||||||
|
|
||||||
|
// hole
|
||||||
|
_redraw_poly(true, occ_poly->_hole_pts_local, occ_poly->_hole_pts_local_raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderSpatialGizmo::_redraw_poly(bool p_hole, const Vector<Vector2> &p_pts, const PoolVector<Vector2> &p_pts_raw) {
|
||||||
|
PoolVector<Vector3> pts_edge;
|
||||||
|
PoolVector<Color> cols;
|
||||||
|
|
||||||
|
Color col_front = _color_poly_front;
|
||||||
|
Color col_back = _color_poly_back;
|
||||||
|
|
||||||
|
if (p_hole) {
|
||||||
|
col_front = _color_hole;
|
||||||
|
col_back = _color_hole;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_pts.size() > 2) {
|
||||||
|
Vector3 pt_first = OccluderShapePolygon::_vec2to3(p_pts[0]);
|
||||||
|
Vector3 pt_prev = OccluderShapePolygon::_vec2to3(p_pts[p_pts.size() - 1]);
|
||||||
|
for (int n = 0; n < p_pts.size(); n++) {
|
||||||
|
Vector3 pt_curr = OccluderShapePolygon::_vec2to3(p_pts[n]);
|
||||||
|
pts_edge.push_back(pt_first);
|
||||||
|
pts_edge.push_back(pt_prev);
|
||||||
|
pts_edge.push_back(pt_curr);
|
||||||
|
cols.push_back(col_front);
|
||||||
|
cols.push_back(col_front);
|
||||||
|
cols.push_back(col_front);
|
||||||
|
|
||||||
|
pts_edge.push_back(pt_first);
|
||||||
|
pts_edge.push_back(pt_curr);
|
||||||
|
pts_edge.push_back(pt_prev);
|
||||||
|
cols.push_back(col_back);
|
||||||
|
cols.push_back(col_back);
|
||||||
|
cols.push_back(col_back);
|
||||||
|
|
||||||
|
pt_prev = pt_curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the handles separately because these must correspond to the raw points
|
||||||
|
// for editing
|
||||||
|
Vector<Vector3> handles;
|
||||||
|
for (int n = 0; n < p_pts_raw.size(); n++) {
|
||||||
|
Vector3 pt = OccluderShapePolygon::_vec2to3(p_pts_raw[n]);
|
||||||
|
handles.push_back(pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// poly itself
|
||||||
|
{
|
||||||
|
if (pts_edge.size() > 2) {
|
||||||
|
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
|
||||||
|
Array array;
|
||||||
|
array.resize(Mesh::ARRAY_MAX);
|
||||||
|
array[Mesh::ARRAY_VERTEX] = pts_edge;
|
||||||
|
array[Mesh::ARRAY_COLOR] = cols;
|
||||||
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
|
||||||
|
|
||||||
|
Ref<Material> material_poly = gizmo_plugin->get_material("occluder_poly", this);
|
||||||
|
add_mesh(mesh, false, Ref<SkinReference>(), material_poly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handles
|
||||||
|
if (!p_hole) {
|
||||||
|
Ref<Material> material_handle = gizmo_plugin->get_material("occluder_handle", this);
|
||||||
|
add_handles(handles, material_handle);
|
||||||
|
} else {
|
||||||
|
Ref<Material> material_extra_handle = gizmo_plugin->get_material("extra_handle", this);
|
||||||
|
add_handles(handles, material_extra_handle, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OccluderSpatialGizmo::OccluderSpatialGizmo(Occluder *p_occluder) {
|
OccluderSpatialGizmo::OccluderSpatialGizmo(Occluder *p_occluder) {
|
||||||
_occluder = p_occluder;
|
_occluder = p_occluder;
|
||||||
set_spatial_node(p_occluder);
|
set_spatial_node(p_occluder);
|
||||||
|
|
||||||
|
_color_poly_front = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder_polygon_front", Color(1.0, 0.25, 0.8, 0.3));
|
||||||
|
_color_poly_back = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder_polygon_back", Color(0.85, 0.1, 1.0, 0.3));
|
||||||
|
_color_hole = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder_hole", Color(0.0, 1.0, 1.0, 0.3));
|
||||||
}
|
}
|
||||||
|
|
|
@ -505,15 +505,27 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
class Occluder;
|
class Occluder;
|
||||||
|
class OccluderShape;
|
||||||
class OccluderShapeSphere;
|
class OccluderShapeSphere;
|
||||||
|
class OccluderShapePolygon;
|
||||||
|
|
||||||
class OccluderSpatialGizmo : public EditorSpatialGizmo {
|
class OccluderSpatialGizmo : public EditorSpatialGizmo {
|
||||||
GDCLASS(OccluderSpatialGizmo, EditorSpatialGizmo);
|
GDCLASS(OccluderSpatialGizmo, EditorSpatialGizmo);
|
||||||
|
|
||||||
Occluder *_occluder = nullptr;
|
Occluder *_occluder = nullptr;
|
||||||
|
|
||||||
OccluderShapeSphere *get_occluder_shape_sphere();
|
const OccluderShape *get_occluder_shape() const;
|
||||||
const OccluderShapeSphere *get_occluder_shape_sphere() const;
|
const OccluderShapeSphere *get_occluder_shape_sphere() const;
|
||||||
|
const OccluderShapePolygon *get_occluder_shape_poly() const;
|
||||||
|
OccluderShape *get_occluder_shape();
|
||||||
|
OccluderShapeSphere *get_occluder_shape_sphere();
|
||||||
|
OccluderShapePolygon *get_occluder_shape_poly();
|
||||||
|
|
||||||
|
Color _color_poly_front;
|
||||||
|
Color _color_poly_back;
|
||||||
|
Color _color_hole;
|
||||||
|
|
||||||
|
void _redraw_poly(bool p_hole, const Vector<Vector2> &p_pts, const PoolVector<Vector2> &p_pts_raw);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual String get_handle_name(int p_idx) const;
|
virtual String get_handle_name(int p_idx) const;
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "occluder.h"
|
#include "occluder.h"
|
||||||
|
|
||||||
#include "core/engine.h"
|
#include "core/engine.h"
|
||||||
|
#include "servers/visual/portals/portal_occlusion_culler.h"
|
||||||
|
|
||||||
void Occluder::resource_changed(RES res) {
|
void Occluder::resource_changed(RES res) {
|
||||||
update_gizmo();
|
update_gizmo();
|
||||||
|
@ -72,6 +73,15 @@ Ref<OccluderShape> Occluder::get_shape() const {
|
||||||
return _shape;
|
return _shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
AABB Occluder::get_fallback_gizmo_aabb() const {
|
||||||
|
if (_shape.is_valid()) {
|
||||||
|
return _shape->get_fallback_gizmo_aabb();
|
||||||
|
}
|
||||||
|
return Spatial::get_fallback_gizmo_aabb();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
String Occluder::get_configuration_warning() const {
|
String Occluder::get_configuration_warning() const {
|
||||||
String warning = Spatial::get_configuration_warning();
|
String warning = Spatial::get_configuration_warning();
|
||||||
|
|
||||||
|
@ -80,8 +90,11 @@ String Occluder::get_configuration_warning() const {
|
||||||
warning += "\n\n";
|
warning += "\n\n";
|
||||||
}
|
}
|
||||||
warning += TTR("No shape is set.");
|
warning += TTR("No shape is set.");
|
||||||
|
return warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
if (_shape.ptr()->requires_uniform_scale()) {
|
||||||
Transform tr = get_global_transform();
|
Transform tr = get_global_transform();
|
||||||
Vector3 scale = tr.basis.get_scale();
|
Vector3 scale = tr.basis.get_scale();
|
||||||
|
|
||||||
|
@ -92,6 +105,8 @@ String Occluder::get_configuration_warning() const {
|
||||||
}
|
}
|
||||||
warning += TTR("Only uniform scales are supported.");
|
warning += TTR("Only uniform scales are supported.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return warning;
|
return warning;
|
||||||
}
|
}
|
||||||
|
@ -106,11 +121,21 @@ void Occluder::_notification(int p_what) {
|
||||||
_shape->update_shape_to_visual_server();
|
_shape->update_shape_to_visual_server();
|
||||||
_shape->update_transform_to_visual_server(get_global_transform());
|
_shape->update_transform_to_visual_server(get_global_transform());
|
||||||
}
|
}
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
if (Engine::get_singleton()->is_editor_hint()) {
|
||||||
|
set_process_internal(true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} break;
|
} break;
|
||||||
case NOTIFICATION_EXIT_WORLD: {
|
case NOTIFICATION_EXIT_WORLD: {
|
||||||
if (_shape.is_valid()) {
|
if (_shape.is_valid()) {
|
||||||
_shape->notification_exit_world();
|
_shape->notification_exit_world();
|
||||||
}
|
}
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
if (Engine::get_singleton()->is_editor_hint()) {
|
||||||
|
set_process_internal(false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} break;
|
} break;
|
||||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||||
if (_shape.is_valid() && is_inside_tree()) {
|
if (_shape.is_valid() && is_inside_tree()) {
|
||||||
|
@ -128,6 +153,12 @@ void Occluder::_notification(int p_what) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||||
|
if (PortalOcclusionCuller::_redraw_gizmo) {
|
||||||
|
PortalOcclusionCuller::_redraw_gizmo = false;
|
||||||
|
update_gizmo();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,11 @@ public:
|
||||||
|
|
||||||
String get_configuration_warning() const;
|
String get_configuration_warning() const;
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
// for editor gizmo
|
||||||
|
virtual AABB get_fallback_gizmo_aabb() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
Occluder();
|
Occluder();
|
||||||
~Occluder();
|
~Occluder();
|
||||||
};
|
};
|
||||||
|
|
|
@ -300,6 +300,12 @@ Transform Spatial::get_global_gizmo_transform() const {
|
||||||
Transform Spatial::get_local_gizmo_transform() const {
|
Transform Spatial::get_local_gizmo_transform() const {
|
||||||
return get_transform();
|
return get_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If not a VisualInstance, use this AABB for the orange box in the editor
|
||||||
|
AABB Spatial::get_fallback_gizmo_aabb() const {
|
||||||
|
return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Spatial *Spatial::get_parent_spatial() const {
|
Spatial *Spatial::get_parent_spatial() const {
|
||||||
|
|
|
@ -167,6 +167,7 @@ public:
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
virtual Transform get_global_gizmo_transform() const;
|
virtual Transform get_global_gizmo_transform() const;
|
||||||
virtual Transform get_local_gizmo_transform() const;
|
virtual Transform get_local_gizmo_transform() const;
|
||||||
|
virtual AABB get_fallback_gizmo_aabb() const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void set_as_toplevel(bool p_enabled);
|
void set_as_toplevel(bool p_enabled);
|
||||||
|
|
|
@ -220,6 +220,7 @@
|
||||||
#include "scene/resources/environment.h"
|
#include "scene/resources/environment.h"
|
||||||
#include "scene/resources/mesh_library.h"
|
#include "scene/resources/mesh_library.h"
|
||||||
#include "scene/resources/occluder_shape.h"
|
#include "scene/resources/occluder_shape.h"
|
||||||
|
#include "scene/resources/occluder_shape_polygon.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "modules/modules_enabled.gen.h" // For freetype.
|
#include "modules/modules_enabled.gen.h" // For freetype.
|
||||||
|
@ -668,6 +669,7 @@ void register_scene_types() {
|
||||||
ClassDB::register_class<ConcavePolygonShape>();
|
ClassDB::register_class<ConcavePolygonShape>();
|
||||||
ClassDB::register_virtual_class<OccluderShape>();
|
ClassDB::register_virtual_class<OccluderShape>();
|
||||||
ClassDB::register_class<OccluderShapeSphere>();
|
ClassDB::register_class<OccluderShapeSphere>();
|
||||||
|
ClassDB::register_class<OccluderShapePolygon>();
|
||||||
|
|
||||||
OS::get_singleton()->yield(); //may take time to init
|
OS::get_singleton()->yield(); //may take time to init
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,12 @@ void OccluderShape::notification_exit_world() {
|
||||||
VisualServer::get_singleton()->occluder_set_scenario(_shape, RID(), VisualServer::OCCLUDER_TYPE_UNDEFINED);
|
VisualServer::get_singleton()->occluder_set_scenario(_shape, RID(), VisualServer::OCCLUDER_TYPE_UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
AABB OccluderShape::get_fallback_gizmo_aabb() const {
|
||||||
|
return AABB(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
void OccluderShapeSphere::_bind_methods() {
|
void OccluderShapeSphere::_bind_methods() {
|
||||||
|
@ -75,6 +81,29 @@ void OccluderShapeSphere::_bind_methods() {
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "spheres", PROPERTY_HINT_NONE, itos(Variant::PLANE) + ":"), "set_spheres", "get_spheres");
|
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "spheres", PROPERTY_HINT_NONE, itos(Variant::PLANE) + ":"), "set_spheres", "get_spheres");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
void OccluderShapeSphere::_update_aabb() {
|
||||||
|
_aabb_local = AABB();
|
||||||
|
|
||||||
|
if (!_spheres.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_aabb_local.position = _spheres[0].normal;
|
||||||
|
|
||||||
|
for (int n = 0; n < _spheres.size(); n++) {
|
||||||
|
AABB bb(_spheres[n].normal, Vector3(0, 0, 0));
|
||||||
|
bb.grow_by(_spheres[n].d);
|
||||||
|
_aabb_local.merge_with(bb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AABB OccluderShapeSphere::get_fallback_gizmo_aabb() const {
|
||||||
|
return _aabb_local;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
void OccluderShapeSphere::update_shape_to_visual_server() {
|
void OccluderShapeSphere::update_shape_to_visual_server() {
|
||||||
VisualServer::get_singleton()->occluder_spheres_update(get_shape(), _spheres);
|
VisualServer::get_singleton()->occluder_spheres_update(get_shape(), _spheres);
|
||||||
}
|
}
|
||||||
|
@ -188,6 +217,8 @@ void OccluderShapeSphere::set_spheres(const Vector<Plane> &p_spheres) {
|
||||||
if (adding_in_editor) {
|
if (adding_in_editor) {
|
||||||
_spheres.set(_spheres.size() - 1, Plane(Vector3(), 1.0));
|
_spheres.set(_spheres.size() - 1, Plane(Vector3(), 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_update_aabb();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
notify_change_to_owners();
|
notify_change_to_owners();
|
||||||
|
@ -198,6 +229,9 @@ void OccluderShapeSphere::set_sphere_position(int p_idx, const Vector3 &p_positi
|
||||||
Plane p = _spheres[p_idx];
|
Plane p = _spheres[p_idx];
|
||||||
p.normal = p_position;
|
p.normal = p_position;
|
||||||
_spheres.set(p_idx, p);
|
_spheres.set(p_idx, p);
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
_update_aabb();
|
||||||
|
#endif
|
||||||
notify_change_to_owners();
|
notify_change_to_owners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,6 +241,9 @@ void OccluderShapeSphere::set_sphere_radius(int p_idx, real_t p_radius) {
|
||||||
Plane p = _spheres[p_idx];
|
Plane p = _spheres[p_idx];
|
||||||
p.d = MAX(p_radius, _min_radius);
|
p.d = MAX(p_radius, _min_radius);
|
||||||
_spheres.set(p_idx, p);
|
_spheres.set(p_idx, p);
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
_update_aabb();
|
||||||
|
#endif
|
||||||
notify_change_to_owners();
|
notify_change_to_owners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,12 @@ public:
|
||||||
void update_active_to_visual_server(bool p_active);
|
void update_active_to_visual_server(bool p_active);
|
||||||
void notification_exit_world();
|
void notification_exit_world();
|
||||||
virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap) = 0;
|
virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap) = 0;
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
// for editor gizmo
|
||||||
|
virtual AABB get_fallback_gizmo_aabb() const;
|
||||||
|
virtual bool requires_uniform_scale() const { return false; }
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class OccluderShapeSphere : public OccluderShape {
|
class OccluderShapeSphere : public OccluderShape {
|
||||||
|
@ -66,6 +72,11 @@ class OccluderShapeSphere : public OccluderShape {
|
||||||
Vector<Plane> _spheres;
|
Vector<Plane> _spheres;
|
||||||
const real_t _min_radius = 0.1;
|
const real_t _min_radius = 0.1;
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
AABB _aabb_local;
|
||||||
|
void _update_aabb();
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
|
@ -80,6 +91,11 @@ public:
|
||||||
virtual void update_shape_to_visual_server();
|
virtual void update_shape_to_visual_server();
|
||||||
virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap);
|
virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap);
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
virtual AABB get_fallback_gizmo_aabb() const;
|
||||||
|
virtual bool requires_uniform_scale() const { return false; }
|
||||||
|
#endif
|
||||||
|
|
||||||
OccluderShapeSphere();
|
OccluderShapeSphere();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
236
scene/resources/occluder_shape_polygon.cpp
Normal file
236
scene/resources/occluder_shape_polygon.cpp
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* occluder_shape_polygon.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2022 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 "occluder_shape_polygon.h"
|
||||||
|
|
||||||
|
#include "servers/visual_server.h"
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
void OccluderShapePolygon::_update_aabb() {
|
||||||
|
_aabb_local = AABB();
|
||||||
|
|
||||||
|
if (_poly_pts_local.size()) {
|
||||||
|
Vector3 begin = _vec2to3(_poly_pts_local[0]);
|
||||||
|
Vector3 end = begin;
|
||||||
|
|
||||||
|
for (int n = 1; n < _poly_pts_local.size(); n++) {
|
||||||
|
Vector3 pt = _vec2to3(_poly_pts_local[n]);
|
||||||
|
begin.x = MIN(begin.x, pt.x);
|
||||||
|
begin.y = MIN(begin.y, pt.y);
|
||||||
|
begin.z = MIN(begin.z, pt.z);
|
||||||
|
end.x = MAX(end.x, pt.x);
|
||||||
|
end.y = MAX(end.y, pt.y);
|
||||||
|
end.z = MAX(end.z, pt.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int n = 0; n < _hole_pts_local.size(); n++) {
|
||||||
|
Vector3 pt = _vec2to3(_hole_pts_local[n]);
|
||||||
|
begin.x = MIN(begin.x, pt.x);
|
||||||
|
begin.y = MIN(begin.y, pt.y);
|
||||||
|
begin.z = MIN(begin.z, pt.z);
|
||||||
|
end.x = MAX(end.x, pt.x);
|
||||||
|
end.y = MAX(end.y, pt.y);
|
||||||
|
end.z = MAX(end.z, pt.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
_aabb_local.position = begin;
|
||||||
|
_aabb_local.size = end - begin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AABB OccluderShapePolygon::get_fallback_gizmo_aabb() const {
|
||||||
|
return _aabb_local;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void OccluderShapePolygon::_sanitize_points_internal(const PoolVector<Vector2> &p_from, Vector<Vector2> &r_to) {
|
||||||
|
// remove duplicates? NYI maybe not necessary
|
||||||
|
Vector<Vector2> raw;
|
||||||
|
raw.resize(p_from.size());
|
||||||
|
for (int n = 0; n < p_from.size(); n++) {
|
||||||
|
raw.set(n, p_from[n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function may get rid of some concave points due to user editing ..
|
||||||
|
// may not be necessary, no idea how fast it is
|
||||||
|
r_to = Geometry::convex_hull_2d(raw);
|
||||||
|
|
||||||
|
// some peculiarity of convex_hull_2d function, it duplicates the last point for some reason
|
||||||
|
if (r_to.size() > 1) {
|
||||||
|
r_to.resize(r_to.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort winding, the system expects counter clockwise polys
|
||||||
|
Geometry::sort_polygon_winding(r_to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::_sanitize_points() {
|
||||||
|
_sanitize_points_internal(_poly_pts_local_raw, _poly_pts_local);
|
||||||
|
_sanitize_points_internal(_hole_pts_local_raw, _hole_pts_local);
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
_update_aabb();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::set_polygon_point(int p_idx, const Vector2 &p_point) {
|
||||||
|
if (p_idx >= _poly_pts_local_raw.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_poly_pts_local_raw.set(p_idx, p_point);
|
||||||
|
_sanitize_points();
|
||||||
|
notify_change_to_owners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::set_hole_point(int p_idx, const Vector2 &p_point) {
|
||||||
|
if (p_idx >= _hole_pts_local_raw.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hole_pts_local_raw.set(p_idx, p_point);
|
||||||
|
_sanitize_points();
|
||||||
|
notify_change_to_owners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::set_polygon_points(const PoolVector<Vector2> &p_points) {
|
||||||
|
_poly_pts_local_raw = p_points;
|
||||||
|
_sanitize_points();
|
||||||
|
notify_change_to_owners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::set_hole_points(const PoolVector<Vector2> &p_points) {
|
||||||
|
_hole_pts_local_raw = p_points;
|
||||||
|
_sanitize_points();
|
||||||
|
notify_change_to_owners();
|
||||||
|
}
|
||||||
|
|
||||||
|
PoolVector<Vector2> OccluderShapePolygon::get_polygon_points() const {
|
||||||
|
return _poly_pts_local_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
PoolVector<Vector2> OccluderShapePolygon::get_hole_points() const {
|
||||||
|
return _hole_pts_local_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::notification_enter_world(RID p_scenario) {
|
||||||
|
VisualServer::get_singleton()->occluder_set_scenario(get_shape(), p_scenario, VisualServer::OCCLUDER_TYPE_MESH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::update_shape_to_visual_server() {
|
||||||
|
if (_poly_pts_local.size() < 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Geometry::OccluderMeshData md;
|
||||||
|
md.faces.resize(1);
|
||||||
|
|
||||||
|
Geometry::OccluderMeshData::Face &face = md.faces[0];
|
||||||
|
face.two_way = is_two_way();
|
||||||
|
|
||||||
|
md.vertices.resize(_poly_pts_local.size() + _hole_pts_local.size());
|
||||||
|
face.indices.resize(_poly_pts_local.size());
|
||||||
|
|
||||||
|
for (int n = 0; n < _poly_pts_local.size(); n++) {
|
||||||
|
md.vertices[n] = _vec2to3(_poly_pts_local[n]);
|
||||||
|
face.indices[n] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hole points
|
||||||
|
if (_hole_pts_local.size()) {
|
||||||
|
face.holes.resize(1);
|
||||||
|
Geometry::OccluderMeshData::Hole &hole = face.holes[0];
|
||||||
|
hole.indices.resize(_hole_pts_local.size());
|
||||||
|
|
||||||
|
for (int n = 0; n < _hole_pts_local.size(); n++) {
|
||||||
|
int dest_idx = n + _poly_pts_local.size();
|
||||||
|
|
||||||
|
hole.indices[n] = dest_idx;
|
||||||
|
md.vertices[dest_idx] = _vec2to3(_hole_pts_local[n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
face.plane = Plane(Vector3(0, 0, 0), Vector3(0, 0, -1));
|
||||||
|
|
||||||
|
VisualServer::get_singleton()->occluder_mesh_update(get_shape(), md);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::set_two_way(bool p_two_way) {
|
||||||
|
_settings_two_way = p_two_way;
|
||||||
|
notify_change_to_owners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform OccluderShapePolygon::center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap) {
|
||||||
|
return Transform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::clear() {
|
||||||
|
_poly_pts_local.clear();
|
||||||
|
_poly_pts_local_raw.resize(0);
|
||||||
|
_hole_pts_local.clear();
|
||||||
|
_hole_pts_local_raw.resize(0);
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
_aabb_local = AABB();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OccluderShapePolygon::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("set_two_way", "two_way"), &OccluderShapePolygon::set_two_way);
|
||||||
|
ClassDB::bind_method(D_METHOD("is_two_way"), &OccluderShapePolygon::is_two_way);
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "two_way"), "set_two_way", "is_two_way");
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_polygon_points", "points"), &OccluderShapePolygon::set_polygon_points);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_polygon_points"), &OccluderShapePolygon::get_polygon_points);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_polygon_point", "index", "position"), &OccluderShapePolygon::set_polygon_point);
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon_points"), "set_polygon_points", "get_polygon_points");
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_hole_points", "points"), &OccluderShapePolygon::set_hole_points);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_hole_points"), &OccluderShapePolygon::get_hole_points);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_hole_point", "index", "position"), &OccluderShapePolygon::set_hole_point);
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "hole_points"), "set_hole_points", "get_hole_points");
|
||||||
|
}
|
||||||
|
|
||||||
|
OccluderShapePolygon::OccluderShapePolygon() :
|
||||||
|
OccluderShape(RID_PRIME(VisualServer::get_singleton()->occluder_create())) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
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_polygon_points(points); // default shape
|
||||||
|
}
|
95
scene/resources/occluder_shape_polygon.h
Normal file
95
scene/resources/occluder_shape_polygon.h
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* occluder_shape_polygon.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2022 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 OCCLUDER_SHAPE_POLYGON_H
|
||||||
|
#define OCCLUDER_SHAPE_POLYGON_H
|
||||||
|
|
||||||
|
#include "occluder_shape.h"
|
||||||
|
|
||||||
|
class OccluderShapePolygon : public OccluderShape {
|
||||||
|
GDCLASS(OccluderShapePolygon, OccluderShape);
|
||||||
|
OBJ_SAVE_TYPE(OccluderShapePolygon);
|
||||||
|
|
||||||
|
friend class OccluderSpatialGizmo;
|
||||||
|
|
||||||
|
// 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> _poly_pts_local_raw;
|
||||||
|
PoolVector<Vector2> _hole_pts_local_raw;
|
||||||
|
|
||||||
|
// sanitized
|
||||||
|
Vector<Vector2> _poly_pts_local;
|
||||||
|
Vector<Vector2> _hole_pts_local;
|
||||||
|
bool _settings_two_way = true;
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
AABB _aabb_local;
|
||||||
|
void _update_aabb();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// mem funcs
|
||||||
|
void _sanitize_points();
|
||||||
|
void _sanitize_points_internal(const PoolVector<Vector2> &p_from, Vector<Vector2> &r_to);
|
||||||
|
static Vector3 _vec2to3(const Vector2 &p_pt) { return Vector3(p_pt.x, p_pt.y, 0.0); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// the raw points are used for the IDE Inspector, and also to allow the user
|
||||||
|
// to edit the geometry of the poly at runtime (they can also just change the node transform)
|
||||||
|
void set_polygon_points(const PoolVector<Vector2> &p_points);
|
||||||
|
PoolVector<Vector2> get_polygon_points() const;
|
||||||
|
void set_hole_points(const PoolVector<Vector2> &p_points);
|
||||||
|
PoolVector<Vector2> get_hole_points() const;
|
||||||
|
|
||||||
|
// primarily for the gizmo
|
||||||
|
void set_polygon_point(int p_idx, const Vector2 &p_point);
|
||||||
|
void set_hole_point(int p_idx, const Vector2 &p_point);
|
||||||
|
|
||||||
|
void set_two_way(bool p_two_way);
|
||||||
|
bool is_two_way() const { return _settings_two_way; }
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
virtual void notification_enter_world(RID p_scenario);
|
||||||
|
virtual void update_shape_to_visual_server();
|
||||||
|
virtual Transform center_node(const Transform &p_global_xform, const Transform &p_parent_xform, real_t p_snap);
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
virtual AABB get_fallback_gizmo_aabb() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
OccluderShapePolygon();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // OCCLUDER_SHAPE_POLYGON_H
|
42
servers/visual/portals/portal_defines.h
Normal file
42
servers/visual/portals/portal_defines.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* portal_defines.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2022 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_DEFINES_H
|
||||||
|
#define PORTAL_DEFINES_H
|
||||||
|
|
||||||
|
// This file is to allow constants etc to be accessible from outside the visual server,
|
||||||
|
// while keeping the dependencies to an absolute minimum.
|
||||||
|
|
||||||
|
struct PortalDefines {
|
||||||
|
static const int OCCLUSION_POLY_MAX_VERTS = 8;
|
||||||
|
static const int OCCLUSION_POLY_MAX_HOLES = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PORTAL_DEFINES_H
|
|
@ -30,26 +30,252 @@
|
||||||
|
|
||||||
#include "portal_occlusion_culler.h"
|
#include "portal_occlusion_culler.h"
|
||||||
|
|
||||||
|
#include "core/engine.h"
|
||||||
|
#include "core/math/aabb.h"
|
||||||
#include "core/project_settings.h"
|
#include "core/project_settings.h"
|
||||||
#include "portal_renderer.h"
|
#include "portal_renderer.h"
|
||||||
|
|
||||||
|
#define _log(a, b) ;
|
||||||
|
//#define _log_prepare(a) log(a, 0)
|
||||||
|
#define _log_prepare(a) ;
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::_debug_log = true;
|
||||||
|
bool PortalOcclusionCuller::_redraw_gizmo = false;
|
||||||
|
|
||||||
|
void PortalOcclusionCuller::Clipper::debug_print_points(String p_string) {
|
||||||
|
print_line(p_string);
|
||||||
|
for (int n = 0; n < _pts_in.size(); n++) {
|
||||||
|
print_line("\t" + itos(n) + " : " + String(Variant(_pts_in[n])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plane PortalOcclusionCuller::Clipper::interpolate(const Plane &p_a, const Plane &p_b, real_t p_t) const {
|
||||||
|
Vector3 diff = p_b.normal - p_a.normal;
|
||||||
|
real_t d = p_b.d - p_a.d;
|
||||||
|
|
||||||
|
diff *= p_t;
|
||||||
|
d *= p_t;
|
||||||
|
|
||||||
|
return Plane(p_a.normal + diff, p_a.d + d);
|
||||||
|
}
|
||||||
|
|
||||||
|
real_t PortalOcclusionCuller::Clipper::clip_and_find_poly_area(const Plane *p_verts, int p_num_verts) {
|
||||||
|
_pts_in.clear();
|
||||||
|
_pts_out.clear();
|
||||||
|
|
||||||
|
// seed
|
||||||
|
for (int n = 0; n < p_num_verts; n++) {
|
||||||
|
_pts_in.push_back(p_verts[n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clip_to_plane(-1, 0, 0, 1)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (!clip_to_plane(1, 0, 0, 1)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (!clip_to_plane(0, -1, 0, 1)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (!clip_to_plane(0, 1, 0, 1)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (!clip_to_plane(0, 0, -1, 1)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (!clip_to_plane(0, 0, 1, 1)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// perspective divide
|
||||||
|
_pts_final.resize(_pts_in.size());
|
||||||
|
for (int n = 0; n < _pts_in.size(); n++) {
|
||||||
|
_pts_final[n] = _pts_in[n].normal / _pts_in[n].d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Geometry::find_polygon_area(&_pts_final[0], _pts_final.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::Clipper::is_inside(const Plane &p_pt, Boundary p_boundary) {
|
||||||
|
real_t w = p_pt.d;
|
||||||
|
|
||||||
|
switch (p_boundary) {
|
||||||
|
case B_LEFT: {
|
||||||
|
return p_pt.normal.x > -w;
|
||||||
|
} break;
|
||||||
|
case B_RIGHT: {
|
||||||
|
return p_pt.normal.x < w;
|
||||||
|
} break;
|
||||||
|
case B_TOP: {
|
||||||
|
return p_pt.normal.y < w;
|
||||||
|
} break;
|
||||||
|
case B_BOTTOM: {
|
||||||
|
return p_pt.normal.y > -w;
|
||||||
|
} break;
|
||||||
|
case B_NEAR: {
|
||||||
|
return p_pt.normal.z < w;
|
||||||
|
} break;
|
||||||
|
case B_FAR: {
|
||||||
|
return p_pt.normal.z > -w;
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a is out, b is in
|
||||||
|
Plane PortalOcclusionCuller::Clipper::intersect(const Plane &p_a, const Plane &p_b, Boundary p_boundary) {
|
||||||
|
Plane diff_plane(p_b.normal - p_a.normal, p_b.d - p_a.d);
|
||||||
|
const Vector3 &diff = diff_plane.normal;
|
||||||
|
|
||||||
|
real_t t = 0.0;
|
||||||
|
const real_t epsilon = 0.001f;
|
||||||
|
|
||||||
|
// prevent divide by zero
|
||||||
|
switch (p_boundary) {
|
||||||
|
case B_LEFT: {
|
||||||
|
if (diff.x > epsilon) {
|
||||||
|
t = (-1.0f - p_a.normal.x) / diff.x;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case B_RIGHT: {
|
||||||
|
if (-diff.x > epsilon) {
|
||||||
|
t = (p_a.normal.x - 1.0f) / -diff.x;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case B_TOP: {
|
||||||
|
if (-diff.y > epsilon) {
|
||||||
|
t = (p_a.normal.y - 1.0f) / -diff.y;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case B_BOTTOM: {
|
||||||
|
if (diff.y > epsilon) {
|
||||||
|
t = (-1.0f - p_a.normal.y) / diff.y;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case B_NEAR: {
|
||||||
|
if (-diff.z > epsilon) {
|
||||||
|
t = (p_a.normal.z - 1.0f) / -diff.z;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case B_FAR: {
|
||||||
|
if (diff.z > epsilon) {
|
||||||
|
t = (-1.0f - p_a.normal.z) / diff.z;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
diff_plane.normal *= t;
|
||||||
|
diff_plane.d *= t;
|
||||||
|
return Plane(p_a.normal + diff_plane.normal, p_a.d + diff_plane.d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip the poly to the plane given by the formula a * x + b * y + c * z + d * w.
|
||||||
|
bool PortalOcclusionCuller::Clipper::clip_to_plane(real_t a, real_t b, real_t c, real_t d) {
|
||||||
|
_pts_out.clear();
|
||||||
|
|
||||||
|
// repeat the first
|
||||||
|
_pts_in.push_back(_pts_in[0]);
|
||||||
|
|
||||||
|
Plane vPrev = _pts_in[0];
|
||||||
|
real_t dpPrev = a * vPrev.normal.x + b * vPrev.normal.y + c * vPrev.normal.z + d * vPrev.d;
|
||||||
|
|
||||||
|
for (int i = 1; i < _pts_in.size(); ++i) {
|
||||||
|
Plane v = _pts_in[i];
|
||||||
|
real_t dp = a * v.normal.x + b * v.normal.y + c * v.normal.z + d * v.d;
|
||||||
|
|
||||||
|
if (dpPrev >= 0) {
|
||||||
|
_pts_out.push_back(vPrev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sgn(dp) != sgn(dpPrev)) {
|
||||||
|
real_t t = dp < 0 ? dpPrev / (dpPrev - dp) : -dpPrev / (dp - dpPrev);
|
||||||
|
|
||||||
|
Plane vOut = interpolate(vPrev, v, t);
|
||||||
|
_pts_out.push_back(vOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
vPrev = v;
|
||||||
|
dpPrev = dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start again from the output points next time
|
||||||
|
_pts_in = _pts_out;
|
||||||
|
|
||||||
|
return _pts_in.size() > 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Geometry::MeshData PortalOcclusionCuller::debug_get_current_polys() const {
|
||||||
|
Geometry::MeshData md;
|
||||||
|
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
const Occlusion::PolyPlane &p = _polys[n].poly;
|
||||||
|
|
||||||
|
int first_index = md.vertices.size();
|
||||||
|
|
||||||
|
Vector3 normal_push = p.plane.normal * 0.001f;
|
||||||
|
|
||||||
|
// copy verts
|
||||||
|
for (int c = 0; c < p.num_verts; c++) {
|
||||||
|
md.vertices.push_back(p.verts[c] + normal_push);
|
||||||
|
}
|
||||||
|
|
||||||
|
// indices
|
||||||
|
Geometry::MeshData::Face face;
|
||||||
|
|
||||||
|
// triangle fan
|
||||||
|
face.indices.resize(p.num_verts);
|
||||||
|
|
||||||
|
for (int c = 0; c < p.num_verts; c++) {
|
||||||
|
face.indices.set(c, first_index + c);
|
||||||
|
}
|
||||||
|
|
||||||
|
md.faces.push_back(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
|
||||||
void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, const LocalVector<uint32_t, uint32_t> &p_occluder_pool_ids, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes) {
|
void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, const LocalVector<uint32_t, uint32_t> &p_occluder_pool_ids, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes) {
|
||||||
|
_portal_renderer = &p_portal_renderer;
|
||||||
|
|
||||||
|
// Bodge to keep settings up to date, until the project settings PR is merged
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
if (Engine::get_singleton()->is_editor_hint() && ((Engine::get_singleton()->get_frames_drawn() % 16) == 0)) {
|
||||||
|
_max_polys = GLOBAL_GET("rendering/misc/occlusion_culling/max_active_polygons");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
_num_spheres = 0;
|
_num_spheres = 0;
|
||||||
|
|
||||||
_pt_camera = pt_camera;
|
_pt_camera = pt_camera;
|
||||||
|
|
||||||
real_t goodness_of_fit[MAX_SPHERES];
|
// spheres
|
||||||
|
_num_spheres = 0;
|
||||||
|
real_t goodness_of_fit_sphere[MAX_SPHERES];
|
||||||
for (int n = 0; n < _max_spheres; n++) {
|
for (int n = 0; n < _max_spheres; n++) {
|
||||||
goodness_of_fit[n] = 0.0;
|
goodness_of_fit_sphere[n] = 0.0f;
|
||||||
}
|
}
|
||||||
real_t weakest_fit = FLT_MAX;
|
real_t weakest_fit_sphere = FLT_MAX;
|
||||||
int weakest_sphere = 0;
|
int weakest_sphere = 0;
|
||||||
_sphere_closest_dist = FLT_MAX;
|
_sphere_closest_dist = FLT_MAX;
|
||||||
|
|
||||||
// TODO : occlusion cull spheres AGAINST themselves.
|
// polys
|
||||||
// i.e. a sphere that is occluded by another occluder is no
|
_num_polys = 0;
|
||||||
// use as an occluder...
|
for (int n = 0; n < _max_polys; n++) {
|
||||||
|
_polys[n].goodness_of_fit = 0.0f;
|
||||||
|
}
|
||||||
|
real_t weakest_fit_poly = FLT_MAX;
|
||||||
|
int weakest_poly_id = 0;
|
||||||
|
|
||||||
// find sphere occluders
|
#ifdef TOOLS_ENABLED
|
||||||
|
uint32_t polycount = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// find occluders
|
||||||
for (unsigned int o = 0; o < p_occluder_pool_ids.size(); o++) {
|
for (unsigned int o = 0; o < p_occluder_pool_ids.size(); o++) {
|
||||||
int id = p_occluder_pool_ids[o];
|
int id = p_occluder_pool_ids[o];
|
||||||
VSOccluder &occ = p_portal_renderer.get_pool_occluder(id);
|
VSOccluder &occ = p_portal_renderer.get_pool_occluder(id);
|
||||||
|
@ -61,6 +287,9 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO : occlusion cull spheres AGAINST themselves.
|
||||||
|
// i.e. a sphere that is occluded by another occluder is no
|
||||||
|
// use as an occluder...
|
||||||
if (occ.type == VSOccluder::OT_SPHERE) {
|
if (occ.type == VSOccluder::OT_SPHERE) {
|
||||||
// make sure world space spheres are up to date
|
// make sure world space spheres are up to date
|
||||||
p_portal_renderer.occluder_ensure_up_to_date_sphere(occ);
|
p_portal_renderer.occluder_ensure_up_to_date_sphere(occ);
|
||||||
|
@ -83,7 +312,7 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
|
|
||||||
// calculate the goodness of fit .. smaller distance better, and larger radius
|
// calculate the goodness of fit .. smaller distance better, and larger radius
|
||||||
// calculate adjusted radius at 100.0
|
// calculate adjusted radius at 100.0
|
||||||
real_t fit = 100 / MAX(dist, 0.01);
|
real_t fit = 100 / MAX(dist, 0.01f);
|
||||||
fit *= occluder_sphere.radius;
|
fit *= occluder_sphere.radius;
|
||||||
|
|
||||||
// until we reach the max, just keep recording, and keep track
|
// until we reach the max, just keep recording, and keep track
|
||||||
|
@ -91,10 +320,10 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
if (_num_spheres < _max_spheres) {
|
if (_num_spheres < _max_spheres) {
|
||||||
_spheres[_num_spheres] = occluder_sphere;
|
_spheres[_num_spheres] = occluder_sphere;
|
||||||
_sphere_distances[_num_spheres] = dist;
|
_sphere_distances[_num_spheres] = dist;
|
||||||
goodness_of_fit[_num_spheres] = fit;
|
goodness_of_fit_sphere[_num_spheres] = fit;
|
||||||
|
|
||||||
if (fit < weakest_fit) {
|
if (fit < weakest_fit_sphere) {
|
||||||
weakest_fit = fit;
|
weakest_fit_sphere = fit;
|
||||||
weakest_sphere = _num_spheres;
|
weakest_sphere = _num_spheres;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,10 +335,10 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
_num_spheres++;
|
_num_spheres++;
|
||||||
} else {
|
} else {
|
||||||
// must beat the weakest
|
// must beat the weakest
|
||||||
if (fit > weakest_fit) {
|
if (fit > weakest_fit_sphere) {
|
||||||
_spheres[weakest_sphere] = occluder_sphere;
|
_spheres[weakest_sphere] = occluder_sphere;
|
||||||
_sphere_distances[weakest_sphere] = dist;
|
_sphere_distances[weakest_sphere] = dist;
|
||||||
goodness_of_fit[weakest_sphere] = fit;
|
goodness_of_fit_sphere[weakest_sphere] = fit;
|
||||||
|
|
||||||
// keep a record of the closest sphere for quick rejects
|
// keep a record of the closest sphere for quick rejects
|
||||||
if (dist < _sphere_closest_dist) {
|
if (dist < _sphere_closest_dist) {
|
||||||
|
@ -117,10 +346,10 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
}
|
}
|
||||||
|
|
||||||
// the weakest may have changed (this could be done more efficiently)
|
// the weakest may have changed (this could be done more efficiently)
|
||||||
weakest_fit = FLT_MAX;
|
weakest_fit_sphere = FLT_MAX;
|
||||||
for (int s = 0; s < _max_spheres; s++) {
|
for (int s = 0; s < _max_spheres; s++) {
|
||||||
if (goodness_of_fit[s] < weakest_fit) {
|
if (goodness_of_fit_sphere[s] < weakest_fit_sphere) {
|
||||||
weakest_fit = goodness_of_fit[s];
|
weakest_fit_sphere = goodness_of_fit_sphere[s];
|
||||||
weakest_sphere = s;
|
weakest_sphere = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,8 +357,109 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // sphere
|
} // sphere
|
||||||
|
|
||||||
|
if (occ.type == VSOccluder::OT_MESH) {
|
||||||
|
// make sure world space spheres are up to date
|
||||||
|
p_portal_renderer.occluder_ensure_up_to_date_polys(occ);
|
||||||
|
|
||||||
|
// multiple polys
|
||||||
|
for (int n = 0; n < occ.list_ids.size(); n++) {
|
||||||
|
const VSOccluder_Mesh &opoly = p_portal_renderer.get_pool_occluder_mesh(occ.list_ids[n]);
|
||||||
|
const Occlusion::PolyPlane &poly = opoly.poly_world;
|
||||||
|
|
||||||
|
// backface cull
|
||||||
|
bool faces_camera = poly.plane.is_point_over(pt_camera);
|
||||||
|
|
||||||
|
if (!faces_camera && !opoly.two_way) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
real_t fit;
|
||||||
|
if (!calculate_poly_goodness_of_fit(opoly, fit)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_num_polys < _max_polys) {
|
||||||
|
SortPoly &dest = _polys[_num_polys];
|
||||||
|
dest.poly = poly;
|
||||||
|
dest.flags = faces_camera ? SortPoly::SPF_FACES_CAMERA : 0;
|
||||||
|
if (opoly.num_holes) {
|
||||||
|
dest.flags |= SortPoly::SPF_HAS_HOLES;
|
||||||
|
}
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
dest.poly_source_id = polycount++;
|
||||||
|
#endif
|
||||||
|
dest.mesh_source_id = occ.list_ids[n];
|
||||||
|
dest.goodness_of_fit = fit;
|
||||||
|
|
||||||
|
if (fit < weakest_fit_poly) {
|
||||||
|
weakest_fit_poly = fit;
|
||||||
|
weakest_poly_id = _num_polys;
|
||||||
|
}
|
||||||
|
|
||||||
|
_num_polys++;
|
||||||
|
} else {
|
||||||
|
// must beat the weakest
|
||||||
|
if (fit > weakest_fit_poly) {
|
||||||
|
SortPoly &dest = _polys[weakest_poly_id];
|
||||||
|
dest.poly = poly;
|
||||||
|
//dest.faces_camera = faces_camera;
|
||||||
|
dest.flags = faces_camera ? SortPoly::SPF_FACES_CAMERA : 0;
|
||||||
|
if (opoly.num_holes) {
|
||||||
|
dest.flags |= SortPoly::SPF_HAS_HOLES;
|
||||||
|
}
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
dest.poly_source_id = polycount++;
|
||||||
|
#endif
|
||||||
|
dest.mesh_source_id = occ.list_ids[n];
|
||||||
|
dest.goodness_of_fit = fit;
|
||||||
|
|
||||||
|
// the weakest may have changed (this could be done more efficiently)
|
||||||
|
weakest_fit_poly = FLT_MAX;
|
||||||
|
for (int p = 0; p < _max_polys; p++) {
|
||||||
|
real_t goodness_of_fit = _polys[p].goodness_of_fit;
|
||||||
|
|
||||||
|
if (goodness_of_fit < weakest_fit_poly) {
|
||||||
|
weakest_fit_poly = goodness_of_fit;
|
||||||
|
weakest_poly_id = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // polys full up, replace
|
||||||
|
}
|
||||||
|
}
|
||||||
} // for o
|
} // for o
|
||||||
|
|
||||||
|
precalc_poly_edge_planes(pt_camera);
|
||||||
|
|
||||||
|
// flip polys so always facing camera
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
if (!(_polys[n].flags & SortPoly::SPF_FACES_CAMERA)) {
|
||||||
|
_polys[n].poly.flip();
|
||||||
|
|
||||||
|
// must flip holes and planes too
|
||||||
|
_precalced_poly[n].flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cull polys against each other.
|
||||||
|
whittle_polys();
|
||||||
|
|
||||||
|
// checksum is used only in the editor, to decide
|
||||||
|
// whether to redraw the gizmo of active polys
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
uint32_t last_checksum = _poly_checksum;
|
||||||
|
_poly_checksum = 0;
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
_poly_checksum += _polys[n].poly_source_id;
|
||||||
|
//_log_prepare("prepfinal : " + itos(_polys[n].poly_source_id) + " fit : " + rtos(_polys[n].goodness_of_fit));
|
||||||
|
}
|
||||||
|
if (_poly_checksum != last_checksum) {
|
||||||
|
_redraw_gizmo = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// force the sphere closest distance to above zero to prevent
|
// force the sphere closest distance to above zero to prevent
|
||||||
// divide by zero in the quick reject
|
// divide by zero in the quick reject
|
||||||
_sphere_closest_dist = MAX(_sphere_closest_dist, 0.001);
|
_sphere_closest_dist = MAX(_sphere_closest_dist, 0.001);
|
||||||
|
@ -150,41 +480,400 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
||||||
n--;
|
n--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record whether to do any occlusion culling at all..
|
||||||
|
_occluders_present = _num_spheres || _num_polys;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere) const {
|
void PortalOcclusionCuller::precalc_poly_edge_planes(const Vector3 &p_pt_camera) {
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
const SortPoly &sortpoly = _polys[n];
|
||||||
|
const Occlusion::PolyPlane &spoly = sortpoly.poly;
|
||||||
|
|
||||||
|
PreCalcedPoly &dpoly = _precalced_poly[n];
|
||||||
|
dpoly.edge_planes.num_planes = spoly.num_verts;
|
||||||
|
|
||||||
|
for (int e = 0; e < spoly.num_verts; e++) {
|
||||||
|
// point a and b of the edge
|
||||||
|
const Vector3 &pt_a = spoly.verts[e];
|
||||||
|
const Vector3 &pt_b = spoly.verts[(e + 1) % spoly.num_verts];
|
||||||
|
|
||||||
|
// edge plane to camera
|
||||||
|
dpoly.edge_planes.planes[e] = Plane(p_pt_camera, pt_a, pt_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
dpoly.num_holes = 0;
|
||||||
|
|
||||||
|
// holes
|
||||||
|
if (sortpoly.flags & SortPoly::SPF_HAS_HOLES) {
|
||||||
|
// get the mesh poly and the holes
|
||||||
|
const VSOccluder_Mesh &mesh = _portal_renderer->get_pool_occluder_mesh(sortpoly.mesh_source_id);
|
||||||
|
|
||||||
|
dpoly.num_holes = mesh.num_holes;
|
||||||
|
|
||||||
|
for (int h = 0; h < mesh.num_holes; h++) {
|
||||||
|
uint32_t hid = mesh.hole_pool_ids[h];
|
||||||
|
const VSOccluder_Hole &hole = _portal_renderer->get_pool_occluder_hole(hid);
|
||||||
|
|
||||||
|
// copy the verts to the precalced poly,
|
||||||
|
// we will need these later for whittling polys.
|
||||||
|
// We could alternatively link back to the original verts, but that gets messy.
|
||||||
|
dpoly.hole_polys[h] = hole.poly_world;
|
||||||
|
|
||||||
|
int hole_num_verts = hole.poly_world.num_verts;
|
||||||
|
const Vector3 *hverts = hole.poly_world.verts;
|
||||||
|
|
||||||
|
// number of planes equals number of verts forming edges
|
||||||
|
dpoly.hole_edge_planes[h].num_planes = hole_num_verts;
|
||||||
|
|
||||||
|
for (int e = 0; e < hole_num_verts; e++) {
|
||||||
|
const Vector3 &pt_a = hverts[e];
|
||||||
|
const Vector3 &pt_b = hverts[(e + 1) % hole_num_verts];
|
||||||
|
|
||||||
|
dpoly.hole_edge_planes[h].planes[e] = Plane(p_pt_camera, pt_a, pt_b);
|
||||||
|
} // for e
|
||||||
|
|
||||||
|
} // for h
|
||||||
|
} // if has holes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortalOcclusionCuller::whittle_polys() {
|
||||||
|
//#define GODOT_OCCLUSION_FLASH_POLYS
|
||||||
|
#ifdef GODOT_OCCLUSION_FLASH_POLYS
|
||||||
|
if (((Engine::get_singleton()->get_frames_drawn() / 4) % 2) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool repeat = true;
|
||||||
|
|
||||||
|
while (repeat) {
|
||||||
|
repeat = false;
|
||||||
|
// Check for complete occlusion of polys by a closer poly.
|
||||||
|
// Such polys can be completely removed from checks.
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
// ensure we test each occluder once and only once
|
||||||
|
// (as this routine will repeat each time an occluded poly is found)
|
||||||
|
SortPoly &sort_poly = _polys[n];
|
||||||
|
if (!(sort_poly.flags & SortPoly::SPF_TESTED_AS_OCCLUDER)) {
|
||||||
|
sort_poly.flags |= SortPoly::SPF_TESTED_AS_OCCLUDER;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Occlusion::PolyPlane &poly = _polys[n].poly;
|
||||||
|
const Plane &occluder_plane = poly.plane;
|
||||||
|
const PreCalcedPoly &pcp = _precalced_poly[n];
|
||||||
|
|
||||||
|
// the goodness of fit is the screen space area at the moment,
|
||||||
|
// so we can use it as a quick reject .. polys behind occluders will always
|
||||||
|
// be smaller area than the occluder.
|
||||||
|
real_t occluder_area = _polys[n].goodness_of_fit;
|
||||||
|
|
||||||
|
// check each other poly as an occludee
|
||||||
|
for (int t = 0; t < _num_polys; t++) {
|
||||||
|
if (n == t) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// quick reject based on screen space area.
|
||||||
|
// if the area of the test poly is larger, it can't be completely behind
|
||||||
|
// the occluder.
|
||||||
|
bool quick_reject_entire_occludee = _polys[t].goodness_of_fit > occluder_area;
|
||||||
|
|
||||||
|
const Occlusion::PolyPlane &test_poly = _polys[t].poly;
|
||||||
|
PreCalcedPoly &pcp_test = _precalced_poly[t];
|
||||||
|
|
||||||
|
// We have two considerations:
|
||||||
|
// (1) Entire poly is occluded
|
||||||
|
// (2) If not (1), then maybe a hole is occluded
|
||||||
|
|
||||||
|
bool completely_reject = false;
|
||||||
|
|
||||||
|
if (!quick_reject_entire_occludee && is_poly_inside_occlusion_volume(test_poly, occluder_plane, pcp.edge_planes)) {
|
||||||
|
completely_reject = true;
|
||||||
|
|
||||||
|
// we must also test against all holes if some are present
|
||||||
|
for (int h = 0; h < pcp.num_holes; h++) {
|
||||||
|
if (is_poly_touching_hole(test_poly, pcp.hole_edge_planes[h])) {
|
||||||
|
completely_reject = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completely_reject) {
|
||||||
|
// yes .. we can remove this poly .. but do not muck up the iteration of the list
|
||||||
|
//print_line("poly is occluded " + itos(t));
|
||||||
|
|
||||||
|
// this condition should never happen, we should never be checking occludee against itself
|
||||||
|
DEV_ASSERT(_polys[t].poly_source_id != _polys[n].poly_source_id);
|
||||||
|
|
||||||
|
// unordered remove
|
||||||
|
_polys[t] = _polys[_num_polys - 1];
|
||||||
|
_precalced_poly[t] = _precalced_poly[_num_polys - 1];
|
||||||
|
_num_polys--;
|
||||||
|
|
||||||
|
// no NOT repeat the test poly if it was copied from n, i.e. the occludee would
|
||||||
|
// be the same as the occluder
|
||||||
|
if (_num_polys != n) {
|
||||||
|
// repeat this test poly as it will be the next
|
||||||
|
t--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we end up removing a poly BEFORE n, the replacement poly (from the unordered remove)
|
||||||
|
// will never get tested as an occluder. So we have to account for this by rerunning the routine.
|
||||||
|
repeat = true;
|
||||||
|
} // allow due to holes
|
||||||
|
} // if poly inside occlusion volume
|
||||||
|
|
||||||
|
// if we did not completely reject, there could be holes that could be rejected
|
||||||
|
if (!completely_reject) {
|
||||||
|
if (pcp_test.num_holes) {
|
||||||
|
for (int h = 0; h < pcp_test.num_holes; h++) {
|
||||||
|
const Occlusion::Poly &hole_poly = pcp_test.hole_polys[h];
|
||||||
|
|
||||||
|
// is the hole within the occluder?
|
||||||
|
if (is_poly_inside_occlusion_volume(hole_poly, occluder_plane, pcp.edge_planes)) {
|
||||||
|
// if the hole touching a hole in the occluder? if so we can't eliminate it
|
||||||
|
bool allow = true;
|
||||||
|
|
||||||
|
for (int oh = 0; oh < pcp.num_holes; oh++) {
|
||||||
|
if (is_poly_touching_hole(hole_poly, pcp.hole_edge_planes[oh])) {
|
||||||
|
allow = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allow) {
|
||||||
|
// Unordered remove the hole. No need to repeat the whole while loop I don't think?
|
||||||
|
// As this just makes it more efficient at runtime, it doesn't make the further whittling more accurate.
|
||||||
|
pcp_test.num_holes--;
|
||||||
|
pcp_test.hole_edge_planes[h] = pcp_test.hole_edge_planes[pcp_test.num_holes];
|
||||||
|
pcp_test.hole_polys[h] = pcp_test.hole_polys[pcp_test.num_holes];
|
||||||
|
|
||||||
|
h--; // repeat this as the unordered remove has placed a new member into h slot
|
||||||
|
} // allow
|
||||||
|
|
||||||
|
} // hole is within
|
||||||
|
}
|
||||||
|
} // has holes
|
||||||
|
} // did not completely reject
|
||||||
|
|
||||||
|
} // for t through occludees
|
||||||
|
|
||||||
|
} // for n through occluders
|
||||||
|
|
||||||
|
} // while repeat
|
||||||
|
|
||||||
|
// order polys by distance to camera / area? NYI
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::calculate_poly_goodness_of_fit(const VSOccluder_Mesh &p_opoly, real_t &r_fit) {
|
||||||
|
// transform each of the poly points, find the area in screen space
|
||||||
|
|
||||||
|
// The points must be homogeneous coordinates, i.e. BEFORE
|
||||||
|
// the perspective divide, in clip space. They will have the perspective
|
||||||
|
// divide applied after clipping, to calculate the area.
|
||||||
|
// We therefore store them as planes to store the w coordinate as d.
|
||||||
|
Plane xpoints[Occlusion::PolyPlane::MAX_POLY_VERTS];
|
||||||
|
int num_verts = p_opoly.poly_world.num_verts;
|
||||||
|
|
||||||
|
for (int n = 0; n < num_verts; n++) {
|
||||||
|
// source and dest in homogeneous coords
|
||||||
|
Plane source(p_opoly.poly_world.verts[n], 1.0f);
|
||||||
|
Plane &dest = xpoints[n];
|
||||||
|
|
||||||
|
dest = _matrix_camera.xform4(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find screen space area
|
||||||
|
real_t area = _clipper.clip_and_find_poly_area(xpoints, num_verts);
|
||||||
|
if (area <= 0.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_fit = area;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::_is_poly_of_interest_to_split_plane(const Plane *p_poly_split_plane, int p_poly_id) const {
|
||||||
|
const Occlusion::PolyPlane &poly = _polys[p_poly_id].poly;
|
||||||
|
|
||||||
|
int over = 0;
|
||||||
|
int under = 0;
|
||||||
|
|
||||||
|
// we need an epsilon because adjacent polys that just
|
||||||
|
// join with a wall may have small floating point error ahead
|
||||||
|
// of the splitting plane.
|
||||||
|
const real_t epsilon = 0.005f;
|
||||||
|
|
||||||
|
for (int n = 0; n < poly.num_verts; n++) {
|
||||||
|
// point a and b of the edge
|
||||||
|
const Vector3 &pt = poly.verts[n];
|
||||||
|
|
||||||
|
real_t dist = p_poly_split_plane->distance_to(pt);
|
||||||
|
if (dist > epsilon) {
|
||||||
|
over++;
|
||||||
|
} else {
|
||||||
|
under++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return whether straddles the plane
|
||||||
|
return over && under;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::cull_aabb_to_polys_ex(const AABB &p_aabb) const {
|
||||||
|
_log("\n", 0);
|
||||||
|
_log("* cull_aabb_to_polys_ex " + String(Variant(p_aabb)), 0);
|
||||||
|
|
||||||
|
Plane plane;
|
||||||
|
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
_log("\tchecking poly " + itos(n), 0);
|
||||||
|
|
||||||
|
const SortPoly &sortpoly = _polys[n];
|
||||||
|
const Occlusion::PolyPlane &poly = sortpoly.poly;
|
||||||
|
|
||||||
|
// occludee must be on opposite side to camera
|
||||||
|
real_t omin, omax;
|
||||||
|
p_aabb.project_range_in_plane(poly.plane, omin, omax);
|
||||||
|
|
||||||
|
if (omax > -0.2f) {
|
||||||
|
_log("\t\tAABB is in front of occluder, ignoring", 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// test against each edge of the poly, and expand the edge
|
||||||
|
bool hit = true;
|
||||||
|
|
||||||
|
const PreCalcedPoly &pcp = _precalced_poly[n];
|
||||||
|
|
||||||
|
for (int e = 0; e < pcp.edge_planes.num_planes; e++) {
|
||||||
|
// edge plane to camera
|
||||||
|
plane = pcp.edge_planes.planes[e];
|
||||||
|
p_aabb.project_range_in_plane(plane, omin, omax);
|
||||||
|
|
||||||
|
if (omax > 0.0f) {
|
||||||
|
hit = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it hit, check against holes
|
||||||
|
if (hit && pcp.num_holes) {
|
||||||
|
for (int h = 0; h < pcp.num_holes; h++) {
|
||||||
|
const PlaneSet &hole = pcp.hole_edge_planes[h];
|
||||||
|
|
||||||
|
// if the AABB is totally outside any edge, it is safe for a hit
|
||||||
|
bool safe = false;
|
||||||
|
for (int e = 0; e < hole.num_planes; e++) {
|
||||||
|
// edge plane to camera
|
||||||
|
plane = hole.planes[e];
|
||||||
|
p_aabb.project_range_in_plane(plane, omin, omax);
|
||||||
|
|
||||||
|
// if inside the hole, no longer a hit on this poly
|
||||||
|
if (omin > 0.0f) {
|
||||||
|
safe = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // for e
|
||||||
|
|
||||||
|
if (!safe) {
|
||||||
|
hit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // for h
|
||||||
|
} // if has holes
|
||||||
|
|
||||||
|
// hit?
|
||||||
|
|
||||||
|
if (hit) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_log("\tno hit", 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::cull_aabb_to_polys(const AABB &p_aabb) const {
|
||||||
|
if (!_num_polys) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cull_aabb_to_polys_ex(p_aabb);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::cull_sphere_to_polys(const Vector3 &p_occludee_center, real_t p_occludee_radius) const {
|
||||||
|
if (!_num_polys) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plane plane;
|
||||||
|
|
||||||
|
for (int n = 0; n < _num_polys; n++) {
|
||||||
|
const Occlusion::PolyPlane &poly = _polys[n].poly;
|
||||||
|
|
||||||
|
// test against each edge of the poly, and expand the edge
|
||||||
|
bool hit = true;
|
||||||
|
|
||||||
|
// occludee must be on opposite side to camera
|
||||||
|
real_t dist = poly.plane.distance_to(p_occludee_center);
|
||||||
|
|
||||||
|
if (dist > -p_occludee_radius) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int e = 0; e < poly.num_verts; e++) {
|
||||||
|
plane = Plane(_pt_camera, poly.verts[e], poly.verts[(e + 1) % poly.num_verts]);
|
||||||
|
|
||||||
|
// de-expand
|
||||||
|
plane.d -= p_occludee_radius;
|
||||||
|
|
||||||
|
if (plane.is_point_over(p_occludee_center)) {
|
||||||
|
hit = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hit?
|
||||||
|
if (hit) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::cull_sphere_to_spheres(const Vector3 &p_occludee_center, real_t p_occludee_radius, const Vector3 &p_ray_dir, real_t p_dist_to_occludee, int p_ignore_sphere) const {
|
||||||
|
// maybe not required
|
||||||
if (!_num_spheres) {
|
if (!_num_spheres) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ray from origin to the occludee
|
|
||||||
Vector3 ray_dir = p_occludee_center - _pt_camera;
|
|
||||||
real_t dist_to_occludee_raw = ray_dir.length();
|
|
||||||
|
|
||||||
// account for occludee radius
|
|
||||||
real_t dist_to_occludee = dist_to_occludee_raw - p_occludee_radius;
|
|
||||||
|
|
||||||
// prevent divide by zero, and the occludee cannot be occluded if we are WITHIN
|
// prevent divide by zero, and the occludee cannot be occluded if we are WITHIN
|
||||||
// its bounding sphere... so no need to check
|
// its bounding sphere... so no need to check
|
||||||
if (dist_to_occludee < _sphere_closest_dist) {
|
if (p_dist_to_occludee < _sphere_closest_dist) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize ray
|
|
||||||
// hopefully by this point, dist_to_occludee_raw cannot possibly be zero due to above check
|
|
||||||
ray_dir *= 1.0 / dist_to_occludee_raw;
|
|
||||||
|
|
||||||
// this can probably be done cheaper with dot products but the math might be a bit fiddly to get right
|
// this can probably be done cheaper with dot products but the math might be a bit fiddly to get right
|
||||||
for (int s = 0; s < _num_spheres; s++) {
|
for (int s = 0; s < _num_spheres; s++) {
|
||||||
// first get the sphere distance
|
// first get the sphere distance
|
||||||
real_t occluder_dist_to_cam = _sphere_distances[s];
|
real_t occluder_dist_to_cam = _sphere_distances[s];
|
||||||
if (dist_to_occludee < occluder_dist_to_cam) {
|
if (p_dist_to_occludee < occluder_dist_to_cam) {
|
||||||
// can't occlude
|
// can't occlude
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the perspective adjusted occludee radius
|
// the perspective adjusted occludee radius
|
||||||
real_t adjusted_occludee_radius = p_occludee_radius * (occluder_dist_to_cam / dist_to_occludee);
|
real_t adjusted_occludee_radius = p_occludee_radius * (occluder_dist_to_cam / p_dist_to_occludee);
|
||||||
|
|
||||||
const Occlusion::Sphere &occluder_sphere = _spheres[s];
|
const Occlusion::Sphere &occluder_sphere = _spheres[s];
|
||||||
real_t occluder_radius = occluder_sphere.radius - adjusted_occludee_radius;
|
real_t occluder_radius = occluder_sphere.radius - adjusted_occludee_radius;
|
||||||
|
@ -195,8 +884,8 @@ bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t
|
||||||
// distance to hit
|
// distance to hit
|
||||||
real_t dist;
|
real_t dist;
|
||||||
|
|
||||||
if (occluder_sphere.intersect_ray(_pt_camera, ray_dir, dist, occluder_radius)) {
|
if (occluder_sphere.intersect_ray(_pt_camera, p_ray_dir, dist, occluder_radius)) {
|
||||||
if ((dist < dist_to_occludee) && (s != p_ignore_sphere)) {
|
if ((dist < p_dist_to_occludee) && (s != p_ignore_sphere)) {
|
||||||
// occluded
|
// occluded
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -207,6 +896,51 @@ bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere, bool p_cull_to_polys) const {
|
||||||
|
if (!_occluders_present) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ray from origin to the occludee
|
||||||
|
Vector3 ray_dir = p_occludee_center - _pt_camera;
|
||||||
|
real_t dist_to_occludee_raw = ray_dir.length();
|
||||||
|
|
||||||
|
// account for occludee radius
|
||||||
|
real_t dist_to_occludee = dist_to_occludee_raw - p_occludee_radius;
|
||||||
|
|
||||||
|
// ignore occlusion for closeup, and avoid divide by zero
|
||||||
|
if (dist_to_occludee_raw < 0.1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize ray
|
||||||
|
// hopefully by this point, dist_to_occludee_raw cannot possibly be zero due to above check
|
||||||
|
ray_dir *= 1.0 / dist_to_occludee_raw;
|
||||||
|
|
||||||
|
if (cull_sphere_to_spheres(p_occludee_center, p_occludee_radius, ray_dir, dist_to_occludee, p_ignore_sphere)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_cull_to_polys && cull_sphere_to_polys(p_occludee_center, p_occludee_radius)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
PortalOcclusionCuller::PortalOcclusionCuller() {
|
PortalOcclusionCuller::PortalOcclusionCuller() {
|
||||||
_max_spheres = GLOBAL_GET("rendering/misc/occlusion_culling/max_active_spheres");
|
_max_spheres = GLOBAL_GET("rendering/misc/occlusion_culling/max_active_spheres");
|
||||||
|
_max_polys = GLOBAL_GET("rendering/misc/occlusion_culling/max_active_polygons");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PortalOcclusionCuller::log(String p_string, int p_depth) const {
|
||||||
|
if (_debug_log) {
|
||||||
|
for (int n = 0; n < p_depth; n++) {
|
||||||
|
p_string = "\t\t\t" + p_string;
|
||||||
|
}
|
||||||
|
print_line(p_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef _log
|
||||||
|
#undef _log_prepare
|
||||||
|
|
|
@ -32,15 +32,57 @@
|
||||||
#define PORTAL_OCCLUSION_CULLER_H
|
#define PORTAL_OCCLUSION_CULLER_H
|
||||||
|
|
||||||
class PortalRenderer;
|
class PortalRenderer;
|
||||||
|
#include "core/math/camera_matrix.h"
|
||||||
|
#include "core/math/geometry.h"
|
||||||
#include "portal_types.h"
|
#include "portal_types.h"
|
||||||
|
|
||||||
class PortalOcclusionCuller {
|
class PortalOcclusionCuller {
|
||||||
enum {
|
enum {
|
||||||
MAX_SPHERES = 64,
|
MAX_SPHERES = 64,
|
||||||
|
MAX_POLYS = 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Clipper {
|
||||||
|
public:
|
||||||
|
real_t clip_and_find_poly_area(const Plane *p_verts, int p_num_verts);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum Boundary {
|
||||||
|
B_LEFT,
|
||||||
|
B_RIGHT,
|
||||||
|
B_TOP,
|
||||||
|
B_BOTTOM,
|
||||||
|
B_NEAR,
|
||||||
|
B_FAR,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_inside(const Plane &p_pt, Boundary p_boundary);
|
||||||
|
Plane intersect(const Plane &p_a, const Plane &p_b, Boundary p_boundary);
|
||||||
|
void debug_print_points(String p_string);
|
||||||
|
|
||||||
|
Plane interpolate(const Plane &p_a, const Plane &p_b, real_t p_t) const;
|
||||||
|
bool clip_to_plane(real_t a, real_t b, real_t c, real_t d);
|
||||||
|
|
||||||
|
LocalVectori<Plane> _pts_in;
|
||||||
|
LocalVectori<Plane> _pts_out;
|
||||||
|
|
||||||
|
// after perspective divide
|
||||||
|
LocalVectori<Vector3> _pts_final;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
int sgn(T val) {
|
||||||
|
return (T(0) < val) - (val < T(0));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PortalOcclusionCuller();
|
PortalOcclusionCuller();
|
||||||
|
|
||||||
|
void prepare_camera(const CameraMatrix &p_cam_matrix, const Vector3 &p_cam_dir) {
|
||||||
|
_matrix_camera = p_cam_matrix;
|
||||||
|
_pt_cam_dir = p_cam_dir;
|
||||||
|
}
|
||||||
|
|
||||||
void prepare(PortalRenderer &p_portal_renderer, const VSRoom &p_room, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes, const Plane *p_near_plane) {
|
void prepare(PortalRenderer &p_portal_renderer, const VSRoom &p_room, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes, const Plane *p_near_plane) {
|
||||||
if (p_near_plane) {
|
if (p_near_plane) {
|
||||||
static LocalVector<Plane> local_planes;
|
static LocalVector<Plane> local_planes;
|
||||||
|
@ -61,16 +103,33 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepare_generic(PortalRenderer &p_portal_renderer, const LocalVector<uint32_t, uint32_t> &p_occluder_pool_ids, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes);
|
void prepare_generic(PortalRenderer &p_portal_renderer, const LocalVector<uint32_t, uint32_t> &p_occluder_pool_ids, const Vector3 &pt_camera, const LocalVector<Plane> &p_planes);
|
||||||
|
|
||||||
bool cull_aabb(const AABB &p_aabb) const {
|
bool cull_aabb(const AABB &p_aabb) const {
|
||||||
if (!_num_spheres) {
|
if (!_occluders_present) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (cull_aabb_to_polys(p_aabb)) {
|
||||||
return cull_sphere(p_aabb.get_center(), p_aabb.size.length() * 0.5);
|
return true;
|
||||||
}
|
}
|
||||||
bool cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere = -1) const;
|
|
||||||
|
return cull_sphere(p_aabb.get_center(), p_aabb.size.length() * 0.5, -1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cull_sphere(const Vector3 &p_occludee_center, real_t p_occludee_radius, int p_ignore_sphere = -1, bool p_cull_to_polys = true) const;
|
||||||
|
|
||||||
|
Geometry::MeshData debug_get_current_polys() const;
|
||||||
|
|
||||||
|
static bool _redraw_gizmo;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool cull_sphere_to_spheres(const Vector3 &p_occludee_center, real_t p_occludee_radius, const Vector3 &p_ray_dir, real_t p_dist_to_occludee, int p_ignore_sphere) const;
|
||||||
|
bool cull_sphere_to_polys(const Vector3 &p_occludee_center, real_t p_occludee_radius) const;
|
||||||
|
bool cull_aabb_to_polys(const AABB &p_aabb) const;
|
||||||
|
|
||||||
|
// experimental
|
||||||
|
bool cull_aabb_to_polys_ex(const AABB &p_aabb) const;
|
||||||
|
bool _is_poly_of_interest_to_split_plane(const Plane *p_poly_split_plane, int p_poly_id) const;
|
||||||
|
|
||||||
// if a sphere is entirely in front of any of the culling planes, it can't be seen so returns false
|
// if a sphere is entirely in front of any of the culling planes, it can't be seen so returns false
|
||||||
bool is_sphere_culled(const Vector3 &p_pos, real_t p_radius, const LocalVector<Plane> &p_planes) const {
|
bool is_sphere_culled(const Vector3 &p_pos, real_t p_radius, const LocalVector<Plane> &p_planes) const {
|
||||||
for (unsigned int p = 0; p < p_planes.size(); p++) {
|
for (unsigned int p = 0; p < p_planes.size(); p++) {
|
||||||
|
@ -99,10 +158,94 @@ private:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool calculate_poly_goodness_of_fit(const VSOccluder_Mesh &p_opoly, real_t &r_fit);
|
||||||
|
void whittle_polys();
|
||||||
|
void precalc_poly_edge_planes(const Vector3 &p_pt_camera);
|
||||||
|
|
||||||
|
bool is_vso_poly_culled(const VSOccluder_Mesh &p_opoly, const LocalVector<Plane> &p_planes) const {
|
||||||
|
return is_poly_culled(p_opoly.poly_world, p_planes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all the points of the poly are beyond one of the planes (e.g. frustum), it is completely culled.
|
||||||
|
bool is_poly_culled(const Occlusion::PolyPlane &p_opoly, const LocalVector<Plane> &p_planes) const {
|
||||||
|
for (unsigned int p = 0; p < p_planes.size(); p++) {
|
||||||
|
const Plane &plane = p_planes[p];
|
||||||
|
|
||||||
|
int points_outside = 0;
|
||||||
|
for (int n = 0; n < p_opoly.num_verts; n++) {
|
||||||
|
const Vector3 &pt = p_opoly.verts[n];
|
||||||
|
if (!plane.is_point_over(pt)) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
points_outside++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (points_outside == p_opoly.num_verts) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the points of the poly must be within ALL the planes to return true.
|
||||||
|
struct PlaneSet;
|
||||||
|
bool is_poly_inside_occlusion_volume(const Occlusion::Poly &p_test_poly, const Plane &p_occluder_plane, const PlaneSet &p_planeset) const {
|
||||||
|
// first test against the occluder poly plane
|
||||||
|
for (int n = 0; n < p_test_poly.num_verts; n++) {
|
||||||
|
const Vector3 &pt = p_test_poly.verts[n];
|
||||||
|
if (p_occluder_plane.is_point_over(pt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int p = 0; p < p_planeset.num_planes; p++) {
|
||||||
|
const Plane &plane = p_planeset.planes[p];
|
||||||
|
|
||||||
|
for (int n = 0; n < p_test_poly.num_verts; n++) {
|
||||||
|
const Vector3 &pt = p_test_poly.verts[n];
|
||||||
|
if (plane.is_point_over(pt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_poly_touching_hole(const Occlusion::Poly &p_opoly, const PlaneSet &p_planeset) const {
|
||||||
|
if (!p_opoly.num_verts) {
|
||||||
|
// should not happen?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// find aabb
|
||||||
|
AABB bb;
|
||||||
|
bb.position = p_opoly.verts[0];
|
||||||
|
for (int n = 1; n < p_opoly.num_verts; n++) {
|
||||||
|
bb.expand_to(p_opoly.verts[n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the AABB is totally outside any edge, it is safe for a hit
|
||||||
|
real_t omin, omax;
|
||||||
|
|
||||||
|
for (int e = 0; e < p_planeset.num_planes; e++) {
|
||||||
|
// edge plane to camera
|
||||||
|
const Plane &plane = p_planeset.planes[e];
|
||||||
|
bb.project_range_in_plane(plane, omin, omax);
|
||||||
|
|
||||||
|
// if inside the hole, no longer a hit on this poly
|
||||||
|
if (omin > 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // for e
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(String p_string, int p_depth = 0) const;
|
||||||
|
|
||||||
// only a number of the spheres in the scene will be chosen to be
|
// only a number of the spheres in the scene will be chosen to be
|
||||||
// active based on their distance to the camera, screen space etc.
|
// active based on their distance to the camera, screen space etc.
|
||||||
Occlusion::Sphere _spheres[MAX_SPHERES];
|
Occlusion::Sphere _spheres[MAX_SPHERES];
|
||||||
|
@ -111,7 +254,67 @@ private:
|
||||||
int _num_spheres = 0;
|
int _num_spheres = 0;
|
||||||
int _max_spheres = 8;
|
int _max_spheres = 8;
|
||||||
|
|
||||||
|
struct SortPoly {
|
||||||
|
enum SortPolyFlags {
|
||||||
|
SPF_FACES_CAMERA = 1,
|
||||||
|
SPF_DONE = 2,
|
||||||
|
SPF_TESTED_AS_OCCLUDER = 4,
|
||||||
|
SPF_HAS_HOLES = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
Occlusion::PolyPlane poly;
|
||||||
|
uint32_t flags;
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
uint32_t poly_source_id;
|
||||||
|
#endif
|
||||||
|
uint32_t mesh_source_id;
|
||||||
|
real_t goodness_of_fit;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlaneSet {
|
||||||
|
void flip() {
|
||||||
|
for (int n = 0; n < num_planes; n++) {
|
||||||
|
planes[n] = -planes[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pre-calculated edge planes to the camera
|
||||||
|
int num_planes = 0;
|
||||||
|
Plane planes[PortalDefines::OCCLUSION_POLY_MAX_VERTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreCalcedPoly {
|
||||||
|
void flip() {
|
||||||
|
edge_planes.flip();
|
||||||
|
for (int n = 0; n < num_holes; n++) {
|
||||||
|
hole_edge_planes[n].flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int num_holes = 0;
|
||||||
|
PlaneSet edge_planes;
|
||||||
|
PlaneSet hole_edge_planes[PortalDefines::OCCLUSION_POLY_MAX_HOLES];
|
||||||
|
Occlusion::Poly hole_polys[PortalDefines::OCCLUSION_POLY_MAX_HOLES];
|
||||||
|
};
|
||||||
|
|
||||||
|
SortPoly _polys[MAX_POLYS];
|
||||||
|
PreCalcedPoly _precalced_poly[MAX_POLYS];
|
||||||
|
int _num_polys = 0;
|
||||||
|
int _max_polys = 8;
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
uint32_t _poly_checksum = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
Vector3 _pt_camera;
|
Vector3 _pt_camera;
|
||||||
|
Vector3 _pt_cam_dir;
|
||||||
|
|
||||||
|
CameraMatrix _matrix_camera;
|
||||||
|
PortalRenderer *_portal_renderer = nullptr;
|
||||||
|
|
||||||
|
Clipper _clipper;
|
||||||
|
|
||||||
|
bool _occluders_present = false;
|
||||||
|
|
||||||
|
static bool _debug_log;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PORTAL_OCCLUSION_CULLER_H
|
#endif // PORTAL_OCCLUSION_CULLER_H
|
||||||
|
|
|
@ -547,6 +547,103 @@ void PortalRenderer::occluder_refresh_room_within(uint32_t p_occluder_pool_id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PortalRenderer::occluder_update_mesh(OccluderHandle p_handle, const Geometry::OccluderMeshData &p_mesh_data) {
|
||||||
|
p_handle--;
|
||||||
|
VSOccluder &occ = _occluder_pool[p_handle];
|
||||||
|
ERR_FAIL_COND(occ.type != VSOccluder::OT_MESH);
|
||||||
|
|
||||||
|
// needs world points updating next time
|
||||||
|
occ.dirty = true;
|
||||||
|
|
||||||
|
const LocalVectori<Geometry::OccluderMeshData::Face> &faces = p_mesh_data.faces;
|
||||||
|
const LocalVectori<Vector3> &vertices = p_mesh_data.vertices;
|
||||||
|
|
||||||
|
// first deal with the situation where the number of polys has changed (rare)
|
||||||
|
if (occ.list_ids.size() != faces.size()) {
|
||||||
|
// not the most efficient, but works...
|
||||||
|
// remove existing
|
||||||
|
for (int n = 0; n < occ.list_ids.size(); n++) {
|
||||||
|
uint32_t id = occ.list_ids[n];
|
||||||
|
_occluder_mesh_pool.free(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
occ.list_ids.clear();
|
||||||
|
// create new
|
||||||
|
for (int n = 0; n < faces.size(); n++) {
|
||||||
|
uint32_t id;
|
||||||
|
VSOccluder_Mesh *poly = _occluder_mesh_pool.request(id);
|
||||||
|
poly->create();
|
||||||
|
occ.list_ids.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// new data
|
||||||
|
for (int n = 0; n < occ.list_ids.size(); n++) {
|
||||||
|
uint32_t id = occ.list_ids[n];
|
||||||
|
|
||||||
|
VSOccluder_Mesh &opoly = _occluder_mesh_pool[id];
|
||||||
|
Occlusion::PolyPlane &poly = opoly.poly_local;
|
||||||
|
|
||||||
|
// source face
|
||||||
|
const Geometry::OccluderMeshData::Face &face = faces[n];
|
||||||
|
opoly.two_way = face.two_way;
|
||||||
|
|
||||||
|
// make sure the number of holes is correct
|
||||||
|
if (face.holes.size() != opoly.num_holes) {
|
||||||
|
// slow but hey ho
|
||||||
|
// delete existing holes
|
||||||
|
for (int i = 0; i < opoly.num_holes; i++) {
|
||||||
|
_occluder_hole_pool.free(opoly.hole_pool_ids[i]);
|
||||||
|
opoly.hole_pool_ids[i] = UINT32_MAX;
|
||||||
|
}
|
||||||
|
// create any new holes
|
||||||
|
opoly.num_holes = face.holes.size();
|
||||||
|
for (int i = 0; i < opoly.num_holes; i++) {
|
||||||
|
uint32_t hole_id;
|
||||||
|
VSOccluder_Hole *hole = _occluder_hole_pool.request(hole_id);
|
||||||
|
opoly.hole_pool_ids[i] = hole_id;
|
||||||
|
hole->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
poly.plane = face.plane;
|
||||||
|
|
||||||
|
poly.num_verts = MIN(face.indices.size(), Occlusion::PolyPlane::MAX_POLY_VERTS);
|
||||||
|
|
||||||
|
// make sure the world poly also has the correct num verts
|
||||||
|
opoly.poly_world.num_verts = poly.num_verts;
|
||||||
|
|
||||||
|
for (int c = 0; c < poly.num_verts; c++) {
|
||||||
|
int vert_index = face.indices[c];
|
||||||
|
|
||||||
|
if (vert_index < vertices.size()) {
|
||||||
|
poly.verts[c] = vertices[vert_index];
|
||||||
|
} else {
|
||||||
|
WARN_PRINT_ONCE("occluder_update_mesh : poly index out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// holes
|
||||||
|
for (int h = 0; h < opoly.num_holes; h++) {
|
||||||
|
VSOccluder_Hole &dhole = get_pool_occluder_hole(opoly.hole_pool_ids[h]);
|
||||||
|
const Geometry::OccluderMeshData::Hole &shole = face.holes[h];
|
||||||
|
|
||||||
|
dhole.poly_local.num_verts = shole.indices.size();
|
||||||
|
dhole.poly_local.num_verts = MIN(dhole.poly_local.num_verts, Occlusion::Poly::MAX_POLY_VERTS);
|
||||||
|
dhole.poly_world.num_verts = dhole.poly_local.num_verts;
|
||||||
|
|
||||||
|
for (int c = 0; c < dhole.poly_local.num_verts; c++) {
|
||||||
|
int vert_index = shole.indices[c];
|
||||||
|
if (vert_index < vertices.size()) {
|
||||||
|
dhole.poly_local.verts[c] = vertices[vert_index];
|
||||||
|
} else {
|
||||||
|
WARN_PRINT_ONCE("occluder_update_mesh : hole index out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PortalRenderer::occluder_update_spheres(OccluderHandle p_handle, const Vector<Plane> &p_spheres) {
|
void PortalRenderer::occluder_update_spheres(OccluderHandle p_handle, const Vector<Plane> &p_spheres) {
|
||||||
p_handle--;
|
p_handle--;
|
||||||
VSOccluder &occ = _occluder_pool[p_handle];
|
VSOccluder &occ = _occluder_pool[p_handle];
|
||||||
|
@ -591,6 +688,9 @@ void PortalRenderer::occluder_destroy(OccluderHandle p_handle) {
|
||||||
case VSOccluder::OT_SPHERE: {
|
case VSOccluder::OT_SPHERE: {
|
||||||
occluder_update_spheres(p_handle + 1, Vector<Plane>());
|
occluder_update_spheres(p_handle + 1, Vector<Plane>());
|
||||||
} break;
|
} break;
|
||||||
|
case VSOccluder::OT_MESH: {
|
||||||
|
occluder_update_mesh(p_handle + 1, Geometry::OccluderMeshData());
|
||||||
|
} break;
|
||||||
default: {
|
default: {
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
@ -1100,7 +1200,7 @@ void PortalRenderer::rooms_update_gameplay_monitor(const Vector<Vector3> &p_came
|
||||||
_gameplay_monitor.update_gameplay(*this, source_rooms, num_source_rooms);
|
_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) {
|
int PortalRenderer::cull_convex_implementation(const Vector3 &p_point, const Vector3 &p_cam_dir, const CameraMatrix &p_cam_matrix, 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
|
// start room
|
||||||
int start_room_id = find_room_within(p_point, r_previous_room_id_hint);
|
int start_room_id = find_room_within(p_point, r_previous_room_id_hint);
|
||||||
|
|
||||||
|
@ -1111,6 +1211,9 @@ int PortalRenderer::cull_convex_implementation(const Vector3 &p_point, const Vec
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set up the occlusion culler once off .. this is a prepare before the prepare is done PER room
|
||||||
|
_tracer.get_occlusion_culler().prepare_camera(p_cam_matrix, p_cam_dir);
|
||||||
|
|
||||||
// planes must be in CameraMatrix order
|
// planes must be in CameraMatrix order
|
||||||
DEV_ASSERT(p_convex.size() == 6);
|
DEV_ASSERT(p_convex.size() == 6);
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#ifndef PORTAL_RENDERER_H
|
#ifndef PORTAL_RENDERER_H
|
||||||
#define PORTAL_RENDERER_H
|
#define PORTAL_RENDERER_H
|
||||||
|
|
||||||
|
#include "core/math/camera_matrix.h"
|
||||||
|
#include "core/math/geometry.h"
|
||||||
#include "core/math/plane.h"
|
#include "core/math/plane.h"
|
||||||
#include "core/pooled_list.h"
|
#include "core/pooled_list.h"
|
||||||
#include "core/vector.h"
|
#include "core/vector.h"
|
||||||
|
@ -187,30 +189,50 @@ public:
|
||||||
// occluders
|
// occluders
|
||||||
OccluderHandle occluder_create(VSOccluder::Type p_type);
|
OccluderHandle occluder_create(VSOccluder::Type p_type);
|
||||||
void occluder_update_spheres(OccluderHandle p_handle, const Vector<Plane> &p_spheres);
|
void occluder_update_spheres(OccluderHandle p_handle, const Vector<Plane> &p_spheres);
|
||||||
|
void occluder_update_mesh(OccluderHandle p_handle, const Geometry::OccluderMeshData &p_mesh_data);
|
||||||
void occluder_set_transform(OccluderHandle p_handle, const Transform &p_xform);
|
void occluder_set_transform(OccluderHandle p_handle, const Transform &p_xform);
|
||||||
void occluder_set_active(OccluderHandle p_handle, bool p_active);
|
void occluder_set_active(OccluderHandle p_handle, bool p_active);
|
||||||
void occluder_destroy(OccluderHandle p_handle);
|
void occluder_destroy(OccluderHandle p_handle);
|
||||||
|
|
||||||
|
// editor only .. slow
|
||||||
|
Geometry::MeshData occlusion_debug_get_current_polys() const { return _tracer.get_occlusion_culler().debug_get_current_polys(); }
|
||||||
|
|
||||||
// note that this relies on a 'frustum' type cull, from a point, and that the planes are specified as in
|
// note that this relies on a 'frustum' type cull, from a point, and that the planes are specified as in
|
||||||
// CameraMatrix, i.e.
|
// CameraMatrix, i.e.
|
||||||
// order PLANE_NEAR,PLANE_FAR,PLANE_LEFT,PLANE_TOP,PLANE_RIGHT,PLANE_BOTTOM
|
// 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) {
|
int cull_convex(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, 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)
|
// combined camera matrix
|
||||||
return cull_convex_implementation(p_point, p_convex, p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
|
CameraMatrix cm = CameraMatrix(p_cam_transform.affine_inverse());
|
||||||
|
cm = p_cam_projection * cm;
|
||||||
|
Vector3 point = p_cam_transform.origin;
|
||||||
|
Vector3 cam_dir = -p_cam_transform.basis.get_axis(2).normalized();
|
||||||
|
|
||||||
return cull_convex_implementation(_override_camera_pos, _override_camera_planes, p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
|
if (!_override_camera)
|
||||||
|
return cull_convex_implementation(point, cam_dir, cm, p_convex, p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
|
||||||
|
|
||||||
|
// override camera matrix NYI
|
||||||
|
return cull_convex_implementation(_override_camera_pos, cam_dir, cm, _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);
|
int cull_convex_implementation(const Vector3 &p_point, const Vector3 &p_cam_dir, const CameraMatrix &p_cam_matrix, 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 occlusion_is_active() const { return _occluder_pool.active_size() && use_occlusion_culling; }
|
||||||
|
|
||||||
// special function for occlusion culling only that does not use portals / rooms,
|
// special function for occlusion culling only that does not use portals / rooms,
|
||||||
// but allows using occluders with the main scene
|
// but allows using occluders with the main scene
|
||||||
int occlusion_cull(const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results) {
|
int occlusion_cull(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results) {
|
||||||
// inactive?
|
// inactive?
|
||||||
if (!_occluder_pool.active_size() || !use_occlusion_culling) {
|
if (!_occluder_pool.active_size() || !use_occlusion_culling) {
|
||||||
return p_num_results;
|
return p_num_results;
|
||||||
}
|
}
|
||||||
return _tracer.occlusion_cull(*this, p_point, p_convex, p_result_array, p_num_results);
|
|
||||||
|
// combined camera matrix
|
||||||
|
CameraMatrix cm = CameraMatrix(p_cam_transform.affine_inverse());
|
||||||
|
cm = p_cam_projection * cm;
|
||||||
|
Vector3 point = p_cam_transform.origin;
|
||||||
|
Vector3 cam_dir = -p_cam_transform.basis.get_axis(2).normalized();
|
||||||
|
|
||||||
|
return _tracer.occlusion_cull(*this, point, cam_dir, cm, p_convex, p_result_array, p_num_results);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_active() const { return _active && _loaded; }
|
bool is_active() const { return _active && _loaded; }
|
||||||
|
@ -235,10 +257,13 @@ public:
|
||||||
RGhost &get_pool_rghost(uint32_t p_pool_id) { return _rghost_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]; }
|
const RGhost &get_pool_rghost(uint32_t p_pool_id) const { return _rghost_pool[p_pool_id]; }
|
||||||
|
|
||||||
|
const LocalVector<uint32_t, uint32_t> &get_occluders_active_list() const { return _occluder_pool.get_active_list(); }
|
||||||
const VSOccluder &get_pool_occluder(uint32_t p_pool_id) const { return _occluder_pool[p_pool_id]; }
|
const VSOccluder &get_pool_occluder(uint32_t p_pool_id) const { return _occluder_pool[p_pool_id]; }
|
||||||
VSOccluder &get_pool_occluder(uint32_t p_pool_id) { return _occluder_pool[p_pool_id]; }
|
VSOccluder &get_pool_occluder(uint32_t p_pool_id) { return _occluder_pool[p_pool_id]; }
|
||||||
const VSOccluder_Sphere &get_pool_occluder_sphere(uint32_t p_pool_id) const { return _occluder_sphere_pool[p_pool_id]; }
|
const VSOccluder_Sphere &get_pool_occluder_sphere(uint32_t p_pool_id) const { return _occluder_sphere_pool[p_pool_id]; }
|
||||||
const LocalVector<uint32_t, uint32_t> &get_occluders_active_list() const { return _occluder_pool.get_active_list(); }
|
const VSOccluder_Mesh &get_pool_occluder_mesh(uint32_t p_pool_id) const { return _occluder_mesh_pool[p_pool_id]; }
|
||||||
|
const VSOccluder_Hole &get_pool_occluder_hole(uint32_t p_pool_id) const { return _occluder_hole_pool[p_pool_id]; }
|
||||||
|
VSOccluder_Hole &get_pool_occluder_hole(uint32_t p_pool_id) { return _occluder_hole_pool[p_pool_id]; }
|
||||||
|
|
||||||
VSStaticGhost &get_static_ghost(uint32_t p_id) { return _static_ghosts[p_id]; }
|
VSStaticGhost &get_static_ghost(uint32_t p_id) { return _static_ghosts[p_id]; }
|
||||||
|
|
||||||
|
@ -295,6 +320,8 @@ private:
|
||||||
// occluders
|
// occluders
|
||||||
TrackedPooledList<VSOccluder> _occluder_pool;
|
TrackedPooledList<VSOccluder> _occluder_pool;
|
||||||
TrackedPooledList<VSOccluder_Sphere, uint32_t, true> _occluder_sphere_pool;
|
TrackedPooledList<VSOccluder_Sphere, uint32_t, true> _occluder_sphere_pool;
|
||||||
|
TrackedPooledList<VSOccluder_Mesh, uint32_t, true> _occluder_mesh_pool;
|
||||||
|
TrackedPooledList<VSOccluder_Hole, uint32_t, true> _occluder_hole_pool;
|
||||||
|
|
||||||
PVS _pvs;
|
PVS _pvs;
|
||||||
|
|
||||||
|
@ -327,6 +354,7 @@ public:
|
||||||
static String _addr_to_string(const void *p_addr);
|
static String _addr_to_string(const void *p_addr);
|
||||||
|
|
||||||
void occluder_ensure_up_to_date_sphere(VSOccluder &r_occluder);
|
void occluder_ensure_up_to_date_sphere(VSOccluder &r_occluder);
|
||||||
|
void occluder_ensure_up_to_date_polys(VSOccluder &r_occluder);
|
||||||
void occluder_refresh_room_within(uint32_t p_occluder_pool_id);
|
void occluder_refresh_room_within(uint32_t p_occluder_pool_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -350,7 +378,6 @@ inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occl
|
||||||
uint32_t pool_id = r_occluder.list_ids[n];
|
uint32_t pool_id = r_occluder.list_ids[n];
|
||||||
VSOccluder_Sphere &osphere = _occluder_sphere_pool[pool_id];
|
VSOccluder_Sphere &osphere = _occluder_sphere_pool[pool_id];
|
||||||
|
|
||||||
// transform position and radius
|
|
||||||
osphere.world.pos = tr.xform(osphere.local.pos);
|
osphere.world.pos = tr.xform(osphere.local.pos);
|
||||||
osphere.world.radius = osphere.local.radius * scale;
|
osphere.world.radius = osphere.local.radius * scale;
|
||||||
|
|
||||||
|
@ -370,4 +397,36 @@ inline void PortalRenderer::occluder_ensure_up_to_date_sphere(VSOccluder &r_occl
|
||||||
r_occluder.aabb.size = bb_max - bb_min;
|
r_occluder.aabb.size = bb_max - bb_min;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
inline void PortalRenderer::occluder_ensure_up_to_date_polys(VSOccluder &r_occluder) {
|
||||||
|
if (!r_occluder.dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r_occluder.dirty = false;
|
||||||
|
|
||||||
|
const Transform &tr = r_occluder.xform;
|
||||||
|
|
||||||
|
for (int n = 0; n < r_occluder.list_ids.size(); n++) {
|
||||||
|
uint32_t pool_id = r_occluder.list_ids[n];
|
||||||
|
|
||||||
|
VSOccluder_Mesh &opoly = _occluder_mesh_pool[pool_id];
|
||||||
|
|
||||||
|
for (int i = 0; i < opoly.poly_local.num_verts; i++) {
|
||||||
|
opoly.poly_world.verts[i] = tr.xform(opoly.poly_local.verts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
opoly.poly_world.plane = tr.xform(opoly.poly_local.plane);
|
||||||
|
|
||||||
|
// holes
|
||||||
|
for (int h = 0; h < opoly.num_holes; h++) {
|
||||||
|
uint32_t hid = opoly.hole_pool_ids[h];
|
||||||
|
|
||||||
|
VSOccluder_Hole &hole = _occluder_hole_pool[hid];
|
||||||
|
|
||||||
|
for (int i = 0; i < hole.poly_local.num_verts; i++) {
|
||||||
|
hole.poly_world.verts[i] = tr.xform(hole.poly_local.verts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PORTAL_RENDERER_H
|
||||||
|
|
|
@ -532,7 +532,9 @@ void PortalTracer::trace_recursive(const TraceParams &p_params, int p_depth, int
|
||||||
} // for p through portals
|
} // for p through portals
|
||||||
}
|
}
|
||||||
|
|
||||||
int PortalTracer::occlusion_cull(PortalRenderer &p_portal_renderer, const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results) {
|
int PortalTracer::occlusion_cull(PortalRenderer &p_portal_renderer, const Vector3 &p_point, const Vector3 &p_cam_dir, const CameraMatrix &p_cam_matrix, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results) {
|
||||||
|
_occlusion_culler.prepare_camera(p_cam_matrix, p_cam_dir);
|
||||||
|
|
||||||
// silly conversion of vector to local vector
|
// silly conversion of vector to local vector
|
||||||
// can this be avoided? NYI
|
// can this be avoided? NYI
|
||||||
// pretty cheap anyway as it will just copy 6 planes, max a few times per frame...
|
// pretty cheap anyway as it will just copy 6 planes, max a few times per frame...
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
//#define PORTAL_RENDERER_STORE_MOVING_RIDS
|
//#define PORTAL_RENDERER_STORE_MOVING_RIDS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct CameraMatrix;
|
||||||
class PortalRenderer;
|
class PortalRenderer;
|
||||||
struct VSRoom;
|
struct VSRoom;
|
||||||
|
|
||||||
|
@ -113,7 +114,10 @@ public:
|
||||||
|
|
||||||
// special function for occlusion culling only that does not use portals / rooms,
|
// special function for occlusion culling only that does not use portals / rooms,
|
||||||
// but allows using occluders with the main scene
|
// but allows using occluders with the main scene
|
||||||
int occlusion_cull(PortalRenderer &p_portal_renderer, const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results);
|
int occlusion_cull(PortalRenderer &p_portal_renderer, const Vector3 &p_point, const Vector3 &p_cam_dir, const CameraMatrix &p_cam_matrix, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results);
|
||||||
|
|
||||||
|
PortalOcclusionCuller &get_occlusion_culler() { return _occlusion_culler; }
|
||||||
|
const PortalOcclusionCuller &get_occlusion_culler() const { return _occlusion_culler; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// main tracing function is recursive
|
// main tracing function is recursive
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "core/math/vector3.h"
|
#include "core/math/vector3.h"
|
||||||
#include "core/object_id.h"
|
#include "core/object_id.h"
|
||||||
#include "core/rid.h"
|
#include "core/rid.h"
|
||||||
|
#include "portal_defines.h"
|
||||||
|
|
||||||
// visual server scene instance.
|
// visual server scene instance.
|
||||||
// we can't have a pointer to nested class outside of visual server scene...
|
// we can't have a pointer to nested class outside of visual server scene...
|
||||||
|
@ -391,6 +392,7 @@ struct VSOccluder {
|
||||||
enum Type : uint32_t {
|
enum Type : uint32_t {
|
||||||
OT_UNDEFINED,
|
OT_UNDEFINED,
|
||||||
OT_SPHERE,
|
OT_SPHERE,
|
||||||
|
OT_MESH,
|
||||||
OT_NUM_TYPES,
|
OT_NUM_TYPES,
|
||||||
} type;
|
} type;
|
||||||
|
|
||||||
|
@ -444,6 +446,30 @@ struct Sphere {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Poly {
|
||||||
|
static const int MAX_POLY_VERTS = PortalDefines::OCCLUSION_POLY_MAX_VERTS;
|
||||||
|
void create() {
|
||||||
|
num_verts = 0;
|
||||||
|
}
|
||||||
|
void flip() {
|
||||||
|
for (int n = 0; n < num_verts / 2; n++) {
|
||||||
|
SWAP(verts[n], verts[num_verts - n - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_verts;
|
||||||
|
Vector3 verts[MAX_POLY_VERTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PolyPlane : public Poly {
|
||||||
|
void flip() {
|
||||||
|
plane = -plane;
|
||||||
|
Poly::flip();
|
||||||
|
}
|
||||||
|
Plane plane;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Occlusion
|
} // namespace Occlusion
|
||||||
|
|
||||||
struct VSOccluder_Sphere {
|
struct VSOccluder_Sphere {
|
||||||
|
@ -456,4 +482,32 @@ struct VSOccluder_Sphere {
|
||||||
Occlusion::Sphere world;
|
Occlusion::Sphere world;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VSOccluder_Mesh {
|
||||||
|
static const int MAX_POLY_HOLES = PortalDefines::OCCLUSION_POLY_MAX_HOLES;
|
||||||
|
void create() {
|
||||||
|
poly_local.create();
|
||||||
|
poly_world.create();
|
||||||
|
num_holes = 0;
|
||||||
|
two_way = false;
|
||||||
|
for (int n = 0; n < MAX_POLY_HOLES; n++) {
|
||||||
|
hole_pool_ids[n] = UINT32_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Occlusion::PolyPlane poly_local;
|
||||||
|
Occlusion::PolyPlane poly_world;
|
||||||
|
bool two_way;
|
||||||
|
|
||||||
|
int num_holes;
|
||||||
|
uint32_t hole_pool_ids[MAX_POLY_HOLES];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VSOccluder_Hole {
|
||||||
|
void create() {
|
||||||
|
poly_local.create();
|
||||||
|
poly_world.create();
|
||||||
|
}
|
||||||
|
Occlusion::Poly poly_local;
|
||||||
|
Occlusion::Poly poly_world;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -585,9 +585,11 @@ public:
|
||||||
BIND0R(RID, occluder_create)
|
BIND0R(RID, occluder_create)
|
||||||
BIND3(occluder_set_scenario, RID, RID, OccluderType)
|
BIND3(occluder_set_scenario, RID, RID, OccluderType)
|
||||||
BIND2(occluder_spheres_update, RID, const Vector<Plane> &)
|
BIND2(occluder_spheres_update, RID, const Vector<Plane> &)
|
||||||
|
BIND2(occluder_mesh_update, RID, const Geometry::OccluderMeshData &)
|
||||||
BIND2(occluder_set_transform, RID, const Transform &)
|
BIND2(occluder_set_transform, RID, const Transform &)
|
||||||
BIND2(occluder_set_active, RID, bool)
|
BIND2(occluder_set_active, RID, bool)
|
||||||
BIND1(set_use_occlusion_culling, bool)
|
BIND1(set_use_occlusion_culling, bool)
|
||||||
|
BIND1RC(Geometry::MeshData, occlusion_debug_get_current_polys, RID)
|
||||||
|
|
||||||
// Rooms
|
// Rooms
|
||||||
BIND0R(RID, room_create)
|
BIND0R(RID, room_create)
|
||||||
|
|
|
@ -1246,12 +1246,28 @@ void VisualServerScene::occluder_spheres_update(RID p_occluder, const Vector<Pla
|
||||||
ro->scenario->_portal_renderer.occluder_update_spheres(ro->scenario_occluder_id, p_spheres);
|
ro->scenario->_portal_renderer.occluder_update_spheres(ro->scenario_occluder_id, p_spheres);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VisualServerScene::occluder_mesh_update(RID p_occluder, const Geometry::OccluderMeshData &p_mesh_data) {
|
||||||
|
Occluder *ro = occluder_owner.getornull(p_occluder);
|
||||||
|
ERR_FAIL_COND(!ro);
|
||||||
|
ERR_FAIL_COND(!ro->scenario);
|
||||||
|
ro->scenario->_portal_renderer.occluder_update_mesh(ro->scenario_occluder_id, p_mesh_data);
|
||||||
|
}
|
||||||
|
|
||||||
void VisualServerScene::set_use_occlusion_culling(bool p_enable) {
|
void VisualServerScene::set_use_occlusion_culling(bool p_enable) {
|
||||||
// this is not scenario specific, and is global
|
// this is not scenario specific, and is global
|
||||||
// (mainly for debugging)
|
// (mainly for debugging)
|
||||||
PortalRenderer::use_occlusion_culling = p_enable;
|
PortalRenderer::use_occlusion_culling = p_enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Geometry::MeshData VisualServerScene::occlusion_debug_get_current_polys(RID p_scenario) const {
|
||||||
|
Scenario *scenario = scenario_owner.getornull(p_scenario);
|
||||||
|
if (!scenario) {
|
||||||
|
return Geometry::MeshData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return scenario->_portal_renderer.occlusion_debug_get_current_polys();
|
||||||
|
}
|
||||||
|
|
||||||
// Rooms
|
// Rooms
|
||||||
void VisualServerScene::callbacks_register(VisualServerCallbacks *p_callbacks) {
|
void VisualServerScene::callbacks_register(VisualServerCallbacks *p_callbacks) {
|
||||||
_visual_server_callbacks = p_callbacks;
|
_visual_server_callbacks = p_callbacks;
|
||||||
|
@ -1480,13 +1496,13 @@ Vector<ObjectID> VisualServerScene::instances_cull_convex(const Vector<Plane> &p
|
||||||
}
|
}
|
||||||
|
|
||||||
// thin wrapper to allow rooms / portals to take over culling if active
|
// 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 VisualServerScene::_cull_convex_from_point(Scenario *p_scenario, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, 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;
|
int res = -1;
|
||||||
if (p_scenario->_portal_renderer.is_active()) {
|
if (p_scenario->_portal_renderer.is_active()) {
|
||||||
// Note that the portal renderer ASSUMES that the planes exactly match the convention in
|
// 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)
|
// CameraMatrix of enum Planes (6 planes, in order, near, far etc)
|
||||||
// If this is not the case, it should not be used.
|
// 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);
|
res = p_scenario->_portal_renderer.cull_convex(p_cam_transform, p_cam_projection, p_convex, (VSInstance **)p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback to BVH / octree if portals not active
|
// fallback to BVH / octree if portals not active
|
||||||
|
@ -1494,7 +1510,9 @@ int VisualServerScene::_cull_convex_from_point(Scenario *p_scenario, const Vecto
|
||||||
res = p_scenario->sps->cull_convex(p_convex, p_result_array, p_result_max, p_mask);
|
res = p_scenario->sps->cull_convex(p_convex, p_result_array, p_result_max, p_mask);
|
||||||
|
|
||||||
// Opportunity for occlusion culling on the main scene. This will be a noop if no occluders.
|
// Opportunity for occlusion culling on the main scene. This will be a noop if no occluders.
|
||||||
res = p_scenario->_portal_renderer.occlusion_cull(p_point, p_convex, (VSInstance **)p_result_array, res);
|
if (p_scenario->_portal_renderer.occlusion_is_active()) {
|
||||||
|
res = p_scenario->_portal_renderer.occlusion_cull(p_cam_transform, p_cam_projection, p_convex, (VSInstance **)p_result_array, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -2321,7 +2339,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 = _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);
|
int cull_count = _cull_convex_from_point(p_scenario, light_transform, cm, 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++) {
|
||||||
|
@ -2356,7 +2374,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 = _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);
|
int cull_count = _cull_convex_from_point(p_scenario, light_transform, cm, 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++) {
|
||||||
|
@ -2535,7 +2553,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 = _cull_convex_from_point(scenario, p_cam_transform.origin, planes, instance_cull_result, MAX_INSTANCE_CULL, r_previous_room_id_hint);
|
instance_cull_count = _cull_convex_from_point(scenario, p_cam_transform, p_cam_projection, 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;
|
||||||
|
|
|
@ -694,10 +694,14 @@ public:
|
||||||
virtual RID occluder_create();
|
virtual RID occluder_create();
|
||||||
virtual void occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type);
|
virtual void occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type);
|
||||||
virtual void occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres);
|
virtual void occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres);
|
||||||
|
virtual void occluder_mesh_update(RID p_occluder, const Geometry::OccluderMeshData &p_mesh_data);
|
||||||
virtual void occluder_set_transform(RID p_occluder, const Transform &p_xform);
|
virtual void occluder_set_transform(RID p_occluder, const Transform &p_xform);
|
||||||
virtual void occluder_set_active(RID p_occluder, bool p_active);
|
virtual void occluder_set_active(RID p_occluder, bool p_active);
|
||||||
virtual void set_use_occlusion_culling(bool p_enable);
|
virtual void set_use_occlusion_culling(bool p_enable);
|
||||||
|
|
||||||
|
// editor only .. slow
|
||||||
|
virtual Geometry::MeshData occlusion_debug_get_current_polys(RID p_scenario) const;
|
||||||
|
|
||||||
// Rooms
|
// Rooms
|
||||||
struct Room : RID_Data {
|
struct Room : RID_Data {
|
||||||
// all interations with actual rooms are indirect, as the room is part of the scenario
|
// all interations with actual rooms are indirect, as the room is part of the scenario
|
||||||
|
@ -740,7 +744,7 @@ public:
|
||||||
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)
|
// 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);
|
int _cull_convex_from_point(Scenario *p_scenario, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, 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);
|
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);
|
||||||
|
|
|
@ -508,9 +508,11 @@ public:
|
||||||
FUNCRID(occluder)
|
FUNCRID(occluder)
|
||||||
FUNC3(occluder_set_scenario, RID, RID, OccluderType)
|
FUNC3(occluder_set_scenario, RID, RID, OccluderType)
|
||||||
FUNC2(occluder_spheres_update, RID, const Vector<Plane> &)
|
FUNC2(occluder_spheres_update, RID, const Vector<Plane> &)
|
||||||
|
FUNC2(occluder_mesh_update, RID, const Geometry::OccluderMeshData &)
|
||||||
FUNC2(occluder_set_transform, RID, const Transform &)
|
FUNC2(occluder_set_transform, RID, const Transform &)
|
||||||
FUNC2(occluder_set_active, RID, bool)
|
FUNC2(occluder_set_active, RID, bool)
|
||||||
FUNC1(set_use_occlusion_culling, bool)
|
FUNC1(set_use_occlusion_culling, bool)
|
||||||
|
FUNC1RC(Geometry::MeshData, occlusion_debug_get_current_polys, RID)
|
||||||
|
|
||||||
// Rooms
|
// Rooms
|
||||||
FUNCRID(room)
|
FUNCRID(room)
|
||||||
|
|
|
@ -2718,6 +2718,8 @@ VisualServer::VisualServer() {
|
||||||
// Occlusion culling
|
// Occlusion culling
|
||||||
GLOBAL_DEF("rendering/misc/occlusion_culling/max_active_spheres", 8);
|
GLOBAL_DEF("rendering/misc/occlusion_culling/max_active_spheres", 8);
|
||||||
ProjectSettings::get_singleton()->set_custom_property_info("rendering/misc/occlusion_culling/max_active_spheres", PropertyInfo(Variant::INT, "rendering/misc/occlusion_culling/max_active_spheres", PROPERTY_HINT_RANGE, "0,64"));
|
ProjectSettings::get_singleton()->set_custom_property_info("rendering/misc/occlusion_culling/max_active_spheres", PropertyInfo(Variant::INT, "rendering/misc/occlusion_culling/max_active_spheres", PROPERTY_HINT_RANGE, "0,64"));
|
||||||
|
GLOBAL_DEF("rendering/misc/occlusion_culling/max_active_polygons", 8);
|
||||||
|
ProjectSettings::get_singleton()->set_custom_property_info("rendering/misc/occlusion_culling/max_active_polygons", PropertyInfo(Variant::INT, "rendering/misc/occlusion_culling/max_active_polygons", PROPERTY_HINT_RANGE, "0,64"));
|
||||||
|
|
||||||
// Async. compilation and caching
|
// Async. compilation and caching
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
|
|
|
@ -901,15 +901,18 @@ public:
|
||||||
enum OccluderType {
|
enum OccluderType {
|
||||||
OCCLUDER_TYPE_UNDEFINED,
|
OCCLUDER_TYPE_UNDEFINED,
|
||||||
OCCLUDER_TYPE_SPHERE,
|
OCCLUDER_TYPE_SPHERE,
|
||||||
|
OCCLUDER_TYPE_MESH,
|
||||||
OCCLUDER_TYPE_NUM_TYPES,
|
OCCLUDER_TYPE_NUM_TYPES,
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual RID occluder_create() = 0;
|
virtual RID occluder_create() = 0;
|
||||||
virtual void occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type) = 0;
|
virtual void occluder_set_scenario(RID p_occluder, RID p_scenario, VisualServer::OccluderType p_type) = 0;
|
||||||
virtual void occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres) = 0;
|
virtual void occluder_spheres_update(RID p_occluder, const Vector<Plane> &p_spheres) = 0;
|
||||||
|
virtual void occluder_mesh_update(RID p_occluder, const Geometry::OccluderMeshData &p_mesh_data) = 0;
|
||||||
virtual void occluder_set_transform(RID p_occluder, const Transform &p_xform) = 0;
|
virtual void occluder_set_transform(RID p_occluder, const Transform &p_xform) = 0;
|
||||||
virtual void occluder_set_active(RID p_occluder, bool p_active) = 0;
|
virtual void occluder_set_active(RID p_occluder, bool p_active) = 0;
|
||||||
virtual void set_use_occlusion_culling(bool p_enable) = 0;
|
virtual void set_use_occlusion_culling(bool p_enable) = 0;
|
||||||
|
virtual Geometry::MeshData occlusion_debug_get_current_polys(RID p_scenario) const = 0;
|
||||||
|
|
||||||
// Rooms
|
// Rooms
|
||||||
enum RoomsDebugFeature {
|
enum RoomsDebugFeature {
|
||||||
|
|
Loading…
Reference in a new issue