Add OccluderShapePolygon
Add OccluderShapePolygon, glue to Occluder and gizmos etc.
This commit is contained in:
parent
b5eef640e1
commit
8ea20f5fdd
32 changed files with 2065 additions and 84 deletions
|
@ -38,7 +38,7 @@
|
|||
|
||||
template <class T, class U = uint32_t, bool force_trivial = false>
|
||||
class LocalVector {
|
||||
private:
|
||||
protected:
|
||||
U count = 0;
|
||||
U capacity = 0;
|
||||
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
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include "geometry.h"
|
||||
|
||||
#include "core/local_vector.h"
|
||||
#include "core/print_string.h"
|
||||
|
||||
#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() {
|
||||
Map<int, int> vtx_remap;
|
||||
|
||||
|
@ -1363,6 +1375,28 @@ Vector<Geometry::PackRectsResult> Geometry::partial_pack_rects(const Vector<Vect
|
|||
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:
|
||||
// https://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order
|
||||
void Geometry::sort_polygon_winding(Vector<Vector2> &r_verts, bool p_clockwise) {
|
||||
|
|
|
@ -555,11 +555,17 @@ public:
|
|||
double dot11 = v1.dot(v1);
|
||||
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
|
||||
double invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
|
||||
double invDenom = 1.0 / denom;
|
||||
double b2 = (dot11 * dot02 - dot01 * dot12) * 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);
|
||||
}
|
||||
|
||||
|
@ -978,6 +984,24 @@ public:
|
|||
Vector<Vector3> 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) {
|
||||
|
@ -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_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 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);
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ public:
|
|||
static _ALWAYS_INLINE_ double abs(double g) { return absd(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_ int64_t abs(int64_t g) { return g > 0 ? g : -g; }
|
||||
|
||||
static _ALWAYS_INLINE_ double fposmod(double p_x, double 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">
|
||||
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 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">
|
||||
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.
|
||||
|
|
|
@ -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")) {
|
||||
#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));
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!p_exclude_toplevel_transform) {
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
#include "scene/resources/cylinder_shape.h"
|
||||
#include "scene/resources/height_map_shape.h"
|
||||
#include "scene/resources/occluder_shape.h"
|
||||
#include "scene/resources/occluder_shape_polygon.h"
|
||||
#include "scene/resources/plane_shape.h"
|
||||
#include "scene/resources/primitive_meshes.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));
|
||||
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("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";
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -5145,16 +5170,63 @@ void OccluderSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2
|
|||
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) {
|
||||
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
|
||||
|
||||
OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
|
||||
if (occ_sphere) {
|
||||
Vector<Plane> spheres = occ_sphere->get_spheres();
|
||||
int num_spheres = spheres.size();
|
||||
|
||||
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
|
||||
|
||||
if (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();
|
||||
_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() {
|
||||
if (!_occluder) {
|
||||
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);
|
||||
OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(get_occluder_shape());
|
||||
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 *occ_sphere = Object::cast_to<OccluderShapeSphere>(get_occluder_shape());
|
||||
return occ_sphere;
|
||||
}
|
||||
|
||||
const OccluderShape *OccluderSpatialGizmo::get_occluder_shape() const {
|
||||
if (!_occluder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -5197,9 +5294,20 @@ const OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() con
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const OccluderShape *shape = rshape.ptr();
|
||||
const OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(shape);
|
||||
return occ_sphere;
|
||||
return rshape.ptr();
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -5258,9 +5366,90 @@ void OccluderSpatialGizmo::redraw() {
|
|||
add_handles(handles, material_handle);
|
||||
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) {
|
||||
_occluder = 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 OccluderShape;
|
||||
class OccluderShapeSphere;
|
||||
class OccluderShapePolygon;
|
||||
|
||||
class OccluderSpatialGizmo : public EditorSpatialGizmo {
|
||||
GDCLASS(OccluderSpatialGizmo, EditorSpatialGizmo);
|
||||
|
||||
Occluder *_occluder = nullptr;
|
||||
|
||||
OccluderShapeSphere *get_occluder_shape_sphere();
|
||||
const OccluderShape *get_occluder_shape() 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:
|
||||
virtual String get_handle_name(int p_idx) const;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "occluder.h"
|
||||
|
||||
#include "core/engine.h"
|
||||
#include "servers/visual/portals/portal_occlusion_culler.h"
|
||||
|
||||
void Occluder::resource_changed(RES res) {
|
||||
update_gizmo();
|
||||
|
@ -72,6 +73,15 @@ Ref<OccluderShape> Occluder::get_shape() const {
|
|||
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 warning = Spatial::get_configuration_warning();
|
||||
|
||||
|
@ -80,18 +90,23 @@ String Occluder::get_configuration_warning() const {
|
|||
warning += "\n\n";
|
||||
}
|
||||
warning += TTR("No shape is set.");
|
||||
return warning;
|
||||
}
|
||||
|
||||
Transform tr = get_global_transform();
|
||||
Vector3 scale = tr.basis.get_scale();
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (_shape.ptr()->requires_uniform_scale()) {
|
||||
Transform tr = get_global_transform();
|
||||
Vector3 scale = tr.basis.get_scale();
|
||||
|
||||
if ((!Math::is_equal_approx(scale.x, scale.y, 0.01f)) ||
|
||||
(!Math::is_equal_approx(scale.x, scale.z, 0.01f))) {
|
||||
if (!warning.empty()) {
|
||||
warning += "\n\n";
|
||||
if ((!Math::is_equal_approx(scale.x, scale.y, 0.01f)) ||
|
||||
(!Math::is_equal_approx(scale.x, scale.z, 0.01f))) {
|
||||
if (!warning.empty()) {
|
||||
warning += "\n\n";
|
||||
}
|
||||
warning += TTR("Only uniform scales are supported.");
|
||||
}
|
||||
warning += TTR("Only uniform scales are supported.");
|
||||
}
|
||||
#endif
|
||||
|
||||
return warning;
|
||||
}
|
||||
|
@ -106,11 +121,21 @@ void Occluder::_notification(int p_what) {
|
|||
_shape->update_shape_to_visual_server();
|
||||
_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;
|
||||
case NOTIFICATION_EXIT_WORLD: {
|
||||
if (_shape.is_valid()) {
|
||||
_shape->notification_exit_world();
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
set_process_internal(false);
|
||||
}
|
||||
#endif
|
||||
} break;
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (_shape.is_valid() && is_inside_tree()) {
|
||||
|
@ -128,6 +153,12 @@ void Occluder::_notification(int p_what) {
|
|||
#endif
|
||||
}
|
||||
} 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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
// for editor gizmo
|
||||
virtual AABB get_fallback_gizmo_aabb() const;
|
||||
#endif
|
||||
|
||||
Occluder();
|
||||
~Occluder();
|
||||
};
|
||||
|
|
|
@ -300,6 +300,12 @@ Transform Spatial::get_global_gizmo_transform() const {
|
|||
Transform Spatial::get_local_gizmo_transform() const {
|
||||
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
|
||||
|
||||
Spatial *Spatial::get_parent_spatial() const {
|
||||
|
|
|
@ -167,6 +167,7 @@ public:
|
|||
#ifdef TOOLS_ENABLED
|
||||
virtual Transform get_global_gizmo_transform() const;
|
||||
virtual Transform get_local_gizmo_transform() const;
|
||||
virtual AABB get_fallback_gizmo_aabb() const;
|
||||
#endif
|
||||
|
||||
void set_as_toplevel(bool p_enabled);
|
||||
|
|
|
@ -220,6 +220,7 @@
|
|||
#include "scene/resources/environment.h"
|
||||
#include "scene/resources/mesh_library.h"
|
||||
#include "scene/resources/occluder_shape.h"
|
||||
#include "scene/resources/occluder_shape_polygon.h"
|
||||
#endif
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For freetype.
|
||||
|
@ -668,6 +669,7 @@ void register_scene_types() {
|
|||
ClassDB::register_class<ConcavePolygonShape>();
|
||||
ClassDB::register_virtual_class<OccluderShape>();
|
||||
ClassDB::register_class<OccluderShapeSphere>();
|
||||
ClassDB::register_class<OccluderShapePolygon>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#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() {
|
||||
|
@ -75,6 +81,29 @@ void OccluderShapeSphere::_bind_methods() {
|
|||
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() {
|
||||
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) {
|
||||
_spheres.set(_spheres.size() - 1, Plane(Vector3(), 1.0));
|
||||
}
|
||||
|
||||
_update_aabb();
|
||||
#endif
|
||||
|
||||
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];
|
||||
p.normal = p_position;
|
||||
_spheres.set(p_idx, p);
|
||||
#ifdef TOOLS_ENABLED
|
||||
_update_aabb();
|
||||
#endif
|
||||
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];
|
||||
p.d = MAX(p_radius, _min_radius);
|
||||
_spheres.set(p_idx, p);
|
||||
#ifdef TOOLS_ENABLED
|
||||
_update_aabb();
|
||||
#endif
|
||||
notify_change_to_owners();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,12 @@ public:
|
|||
void update_active_to_visual_server(bool p_active);
|
||||
void notification_exit_world();
|
||||
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 {
|
||||
|
@ -66,6 +72,11 @@ class OccluderShapeSphere : public OccluderShape {
|
|||
Vector<Plane> _spheres;
|
||||
const real_t _min_radius = 0.1;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
AABB _aabb_local;
|
||||
void _update_aabb();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
|
@ -80,6 +91,11 @@ public:
|
|||
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;
|
||||
virtual bool requires_uniform_scale() const { return false; }
|
||||
#endif
|
||||
|
||||
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 "core/engine.h"
|
||||
#include "core/math/aabb.h"
|
||||
#include "core/project_settings.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) {
|
||||
_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;
|
||||
|
||||
_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++) {
|
||||
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;
|
||||
_sphere_closest_dist = FLT_MAX;
|
||||
|
||||
// TODO : occlusion cull spheres AGAINST themselves.
|
||||
// i.e. a sphere that is occluded by another occluder is no
|
||||
// use as an occluder...
|
||||
// polys
|
||||
_num_polys = 0;
|
||||
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++) {
|
||||
int id = p_occluder_pool_ids[o];
|
||||
VSOccluder &occ = p_portal_renderer.get_pool_occluder(id);
|
||||
|
@ -61,6 +287,9 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
|||
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) {
|
||||
// make sure world space spheres are up to date
|
||||
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 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;
|
||||
|
||||
// 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) {
|
||||
_spheres[_num_spheres] = occluder_sphere;
|
||||
_sphere_distances[_num_spheres] = dist;
|
||||
goodness_of_fit[_num_spheres] = fit;
|
||||
goodness_of_fit_sphere[_num_spheres] = fit;
|
||||
|
||||
if (fit < weakest_fit) {
|
||||
weakest_fit = fit;
|
||||
if (fit < weakest_fit_sphere) {
|
||||
weakest_fit_sphere = fit;
|
||||
weakest_sphere = _num_spheres;
|
||||
}
|
||||
|
||||
|
@ -106,10 +335,10 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
|||
_num_spheres++;
|
||||
} else {
|
||||
// must beat the weakest
|
||||
if (fit > weakest_fit) {
|
||||
if (fit > weakest_fit_sphere) {
|
||||
_spheres[weakest_sphere] = occluder_sphere;
|
||||
_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
|
||||
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)
|
||||
weakest_fit = FLT_MAX;
|
||||
weakest_fit_sphere = FLT_MAX;
|
||||
for (int s = 0; s < _max_spheres; s++) {
|
||||
if (goodness_of_fit[s] < weakest_fit) {
|
||||
weakest_fit = goodness_of_fit[s];
|
||||
if (goodness_of_fit_sphere[s] < weakest_fit_sphere) {
|
||||
weakest_fit_sphere = goodness_of_fit_sphere[s];
|
||||
weakest_sphere = s;
|
||||
}
|
||||
}
|
||||
|
@ -128,8 +357,109 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
|||
}
|
||||
}
|
||||
} // 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
|
||||
|
||||
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
|
||||
// divide by zero in the quick reject
|
||||
_sphere_closest_dist = MAX(_sphere_closest_dist, 0.001);
|
||||
|
@ -150,41 +480,400 @@ void PortalOcclusionCuller::prepare_generic(PortalRenderer &p_portal_renderer, c
|
|||
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) {
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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
|
||||
for (int s = 0; s < _num_spheres; s++) {
|
||||
// first get the sphere distance
|
||||
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
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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];
|
||||
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
|
||||
real_t dist;
|
||||
|
||||
if (occluder_sphere.intersect_ray(_pt_camera, ray_dir, dist, occluder_radius)) {
|
||||
if ((dist < dist_to_occludee) && (s != p_ignore_sphere)) {
|
||||
if (occluder_sphere.intersect_ray(_pt_camera, p_ray_dir, dist, occluder_radius)) {
|
||||
if ((dist < p_dist_to_occludee) && (s != p_ignore_sphere)) {
|
||||
// occluded
|
||||
return true;
|
||||
}
|
||||
|
@ -207,6 +896,51 @@ bool PortalOcclusionCuller::cull_sphere(const Vector3 &p_occludee_center, real_t
|
|||
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() {
|
||||
_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
|
||||
|
||||
class PortalRenderer;
|
||||
#include "core/math/camera_matrix.h"
|
||||
#include "core/math/geometry.h"
|
||||
#include "portal_types.h"
|
||||
|
||||
class PortalOcclusionCuller {
|
||||
enum {
|
||||
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:
|
||||
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) {
|
||||
if (p_near_plane) {
|
||||
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);
|
||||
|
||||
bool cull_aabb(const AABB &p_aabb) const {
|
||||
if (!_num_spheres) {
|
||||
if (!_occluders_present) {
|
||||
return false;
|
||||
}
|
||||
if (cull_aabb_to_polys(p_aabb)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return cull_sphere(p_aabb.get_center(), p_aabb.size.length() * 0.5);
|
||||
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) const;
|
||||
|
||||
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:
|
||||
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
|
||||
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++) {
|
||||
|
@ -99,10 +158,94 @@ private:
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// active based on their distance to the camera, screen space etc.
|
||||
Occlusion::Sphere _spheres[MAX_SPHERES];
|
||||
|
@ -111,7 +254,67 @@ private:
|
|||
int _num_spheres = 0;
|
||||
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_cam_dir;
|
||||
|
||||
CameraMatrix _matrix_camera;
|
||||
PortalRenderer *_portal_renderer = nullptr;
|
||||
|
||||
Clipper _clipper;
|
||||
|
||||
bool _occluders_present = false;
|
||||
|
||||
static bool _debug_log;
|
||||
};
|
||||
|
||||
#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) {
|
||||
p_handle--;
|
||||
VSOccluder &occ = _occluder_pool[p_handle];
|
||||
|
@ -591,6 +688,9 @@ void PortalRenderer::occluder_destroy(OccluderHandle p_handle) {
|
|||
case VSOccluder::OT_SPHERE: {
|
||||
occluder_update_spheres(p_handle + 1, Vector<Plane>());
|
||||
} break;
|
||||
case VSOccluder::OT_MESH: {
|
||||
occluder_update_mesh(p_handle + 1, Geometry::OccluderMeshData());
|
||||
} break;
|
||||
default: {
|
||||
} 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);
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
DEV_ASSERT(p_convex.size() == 6);
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
#ifndef 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/pooled_list.h"
|
||||
#include "core/vector.h"
|
||||
|
@ -187,30 +189,50 @@ public:
|
|||
// occluders
|
||||
OccluderHandle occluder_create(VSOccluder::Type p_type);
|
||||
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_active(OccluderHandle p_handle, bool p_active);
|
||||
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
|
||||
// CameraMatrix, i.e.
|
||||
// order PLANE_NEAR,PLANE_FAR,PLANE_LEFT,PLANE_TOP,PLANE_RIGHT,PLANE_BOTTOM
|
||||
int cull_convex(const Vector3 &p_point, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_result_max, uint32_t p_mask, int32_t &r_previous_room_id_hint) {
|
||||
if (!_override_camera)
|
||||
return cull_convex_implementation(p_point, p_convex, p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
|
||||
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) {
|
||||
// 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 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,
|
||||
// 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?
|
||||
if (!_occluder_pool.active_size() || !use_occlusion_culling) {
|
||||
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; }
|
||||
|
@ -235,10 +257,13 @@ public:
|
|||
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 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]; }
|
||||
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 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]; }
|
||||
|
||||
|
@ -295,6 +320,8 @@ private:
|
|||
// occluders
|
||||
TrackedPooledList<VSOccluder> _occluder_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;
|
||||
|
||||
|
@ -327,6 +354,7 @@ public:
|
|||
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_polys(VSOccluder &r_occluder);
|
||||
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];
|
||||
VSOccluder_Sphere &osphere = _occluder_sphere_pool[pool_id];
|
||||
|
||||
// transform position and radius
|
||||
osphere.world.pos = tr.xform(osphere.local.pos);
|
||||
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;
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
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
|
||||
// can this be avoided? NYI
|
||||
// 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
|
||||
#endif
|
||||
|
||||
struct CameraMatrix;
|
||||
class PortalRenderer;
|
||||
struct VSRoom;
|
||||
|
||||
|
@ -113,7 +114,10 @@ public:
|
|||
|
||||
// special function for occlusion culling only that does not use portals / rooms,
|
||||
// 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:
|
||||
// main tracing function is recursive
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "core/math/vector3.h"
|
||||
#include "core/object_id.h"
|
||||
#include "core/rid.h"
|
||||
#include "portal_defines.h"
|
||||
|
||||
// visual server scene instance.
|
||||
// we can't have a pointer to nested class outside of visual server scene...
|
||||
|
@ -391,6 +392,7 @@ struct VSOccluder {
|
|||
enum Type : uint32_t {
|
||||
OT_UNDEFINED,
|
||||
OT_SPHERE,
|
||||
OT_MESH,
|
||||
OT_NUM_TYPES,
|
||||
} type;
|
||||
|
||||
|
@ -444,6 +446,30 @@ struct Sphere {
|
|||
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
|
||||
|
||||
struct VSOccluder_Sphere {
|
||||
|
@ -456,4 +482,32 @@ struct VSOccluder_Sphere {
|
|||
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
|
||||
|
|
|
@ -585,9 +585,11 @@ public:
|
|||
BIND0R(RID, occluder_create)
|
||||
BIND3(occluder_set_scenario, RID, RID, OccluderType)
|
||||
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_active, RID, bool)
|
||||
BIND1(set_use_occlusion_culling, bool)
|
||||
BIND1RC(Geometry::MeshData, occlusion_debug_get_current_polys, RID)
|
||||
|
||||
// Rooms
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
// this is not scenario specific, and is global
|
||||
// (mainly for debugging)
|
||||
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
|
||||
void VisualServerScene::callbacks_register(VisualServerCallbacks *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
|
||||
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;
|
||||
if (p_scenario->_portal_renderer.is_active()) {
|
||||
// Note that the portal renderer ASSUMES that the planes exactly match the convention in
|
||||
// CameraMatrix of enum Planes (6 planes, in order, near, far etc)
|
||||
// If this is not the case, it should not be used.
|
||||
res = p_scenario->_portal_renderer.cull_convex(p_point, p_convex, (VSInstance **)p_result_array, p_result_max, p_mask, r_previous_room_id_hint);
|
||||
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
|
||||
|
@ -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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -2321,7 +2339,7 @@ bool VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons
|
|||
|
||||
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));
|
||||
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);
|
||||
|
||||
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));
|
||||
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();
|
||||
|
||||
/* 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;
|
||||
|
||||
reflection_probe_cull_count = 0;
|
||||
|
|
|
@ -694,10 +694,14 @@ public:
|
|||
virtual RID occluder_create();
|
||||
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_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_active(RID p_occluder, bool p_active);
|
||||
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
|
||||
struct Room : RID_Data {
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
|
||||
virtual void instance_geometry_set_flag(RID p_instance, VS::InstanceFlags p_flags, bool p_enabled);
|
||||
|
|
|
@ -508,9 +508,11 @@ public:
|
|||
FUNCRID(occluder)
|
||||
FUNC3(occluder_set_scenario, RID, RID, OccluderType)
|
||||
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_active, RID, bool)
|
||||
FUNC1(set_use_occlusion_culling, bool)
|
||||
FUNC1RC(Geometry::MeshData, occlusion_debug_get_current_polys, RID)
|
||||
|
||||
// Rooms
|
||||
FUNCRID(room)
|
||||
|
|
|
@ -2718,6 +2718,8 @@ VisualServer::VisualServer() {
|
|||
// Occlusion culling
|
||||
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"));
|
||||
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
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
|
|
@ -901,15 +901,18 @@ public:
|
|||
enum OccluderType {
|
||||
OCCLUDER_TYPE_UNDEFINED,
|
||||
OCCLUDER_TYPE_SPHERE,
|
||||
OCCLUDER_TYPE_MESH,
|
||||
OCCLUDER_TYPE_NUM_TYPES,
|
||||
};
|
||||
|
||||
virtual RID occluder_create() = 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_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_active(RID p_occluder, bool p_active) = 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
|
||||
enum RoomsDebugFeature {
|
||||
|
|
Loading…
Reference in a new issue