Sphere occluders (portals and general use)

Add framework for supporting geometrical occluders within rooms, and add support for sphere occluders.
Includes gizmos for editing.

They also work outside the portal system.
This commit is contained in:
lawnjelly 2021-08-05 15:44:43 +01:00
parent 450f7fdc39
commit 115f4dce55
36 changed files with 1825 additions and 7 deletions

View file

@ -42,6 +42,7 @@
#include "scene/3d/listener.h"
#include "scene/3d/mesh_instance.h"
#include "scene/3d/navigation_mesh.h"
#include "scene/3d/occluder.h"
#include "scene/3d/particles.h"
#include "scene/3d/physics_joint.h"
#include "scene/3d/portal.h"
@ -60,6 +61,7 @@
#include "scene/resources/convex_polygon_shape.h"
#include "scene/resources/cylinder_shape.h"
#include "scene/resources/height_map_shape.h"
#include "scene/resources/occluder_shape.h"
#include "scene/resources/plane_shape.h"
#include "scene/resources/primitive_meshes.h"
#include "scene/resources/ray_shape.h"
@ -4938,3 +4940,274 @@ PortalSpatialGizmo::PortalSpatialGizmo(Portal *p_portal) {
_color_portal_front = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_front", Color(0.05, 0.05, 1.0, 0.3));
_color_portal_back = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_back", Color(1.0, 1.0, 0.0, 0.15));
}
/////////////////////
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_handle_material("occluder_handle");
create_handle_material("extra_handle", false, SpatialEditor::get_singleton()->get_icon("EditorInternalHandle", "EditorIcons"));
}
Ref<EditorSpatialGizmo> OccluderGizmoPlugin::create_gizmo(Spatial *p_spatial) {
Ref<OccluderSpatialGizmo> ref;
Occluder *occluder = Object::cast_to<Occluder>(p_spatial);
if (occluder) {
ref = Ref<OccluderSpatialGizmo>(memnew(OccluderSpatialGizmo(occluder)));
}
return ref;
}
bool OccluderGizmoPlugin::has_gizmo(Spatial *p_spatial) {
if (Object::cast_to<Occluder>(p_spatial)) {
return true;
}
return false;
}
String OccluderGizmoPlugin::get_name() const {
return "Occluder";
}
int OccluderGizmoPlugin::get_priority() const {
return -1;
}
//////////////////////
String OccluderSpatialGizmo::get_handle_name(int p_idx) const {
const OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
if (occ_sphere) {
int num_spheres = occ_sphere->get_spheres().size();
if (p_idx >= num_spheres) {
p_idx -= num_spheres;
return "Radius " + itos(p_idx);
} else {
return "Sphere " + itos(p_idx);
}
}
return "Unknown";
}
Variant OccluderSpatialGizmo::get_handle_value(int p_idx) {
const OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
if (occ_sphere) {
Vector<Plane> spheres = occ_sphere->get_spheres();
int num_spheres = spheres.size();
if (p_idx >= num_spheres) {
p_idx -= num_spheres;
return spheres[p_idx].d;
} else {
return spheres[p_idx].normal;
}
}
return 0;
}
void OccluderSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) {
if (!_occluder) {
return;
}
Transform tr = _occluder->get_global_transform();
Transform tr_inv = tr.affine_inverse();
// selection ray
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
Vector3 camera_dir = p_camera->get_transform().basis.get_axis(2);
// find the smallest camera axis, we will only transform the handles on 2 axes max,
// to try and make things more user friendly (it is confusing trying to change 3d position
// from a 2d view)
int biggest_axis = 0;
real_t biggest = 0.0;
for (int n = 0; n < 3; n++) {
real_t val = Math::abs(camera_dir.get_axis(n));
if (val > biggest) {
biggest = val;
biggest_axis = n;
}
}
// find world space of selected point
OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
if (occ_sphere) {
Vector<Plane> spheres = occ_sphere->get_spheres();
int num_spheres = spheres.size();
// radius?
bool is_radius = false;
if (p_idx >= num_spheres) {
p_idx -= num_spheres;
is_radius = true;
}
Vector3 pt_world = spheres[p_idx].normal;
pt_world = tr.xform(pt_world);
Vector3 pt_world_center = pt_world;
// a plane between the radius point and the centre
Plane plane;
if (is_radius) {
plane = Plane(Vector3(0, 0, 1), pt_world.z);
} else {
plane = Plane(pt_world, camera_dir);
}
Vector3 inters;
if (plane.intersects_ray(ray_from, ray_dir, &inters)) {
if (SpatialEditor::get_singleton()->is_snap_enabled()) {
float snap = SpatialEditor::get_singleton()->get_translate_snap();
inters.snap(Vector3(snap, snap, snap));
}
if (is_radius) {
pt_world = inters;
// new radius is simply the dist between this point and the centre of the sphere
real_t radius = (pt_world - pt_world_center).length();
occ_sphere->set_sphere_radius(p_idx, radius);
} else {
for (int n = 0; n < 3; n++) {
if (n != biggest_axis) {
pt_world.set_axis(n, inters.get_axis(n));
}
}
Vector3 pt_local = tr_inv.xform(pt_world);
occ_sphere->set_sphere_position(p_idx, pt_local);
}
return;
}
}
}
void OccluderSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
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;
ur->create_action(TTR("Set Occluder Sphere Radius"));
ur->add_do_method(occ_sphere, "set_sphere_radius", p_idx, spheres[p_idx].d);
ur->add_undo_method(occ_sphere, "set_sphere_radius", p_idx, p_restore);
} else {
ur->create_action(TTR("Set Occluder Sphere Position"));
ur->add_do_method(occ_sphere, "set_sphere_position", p_idx, spheres[p_idx].normal);
ur->add_undo_method(occ_sphere, "set_sphere_position", 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);
return occ_sphere;
}
const OccluderShapeSphere *OccluderSpatialGizmo::get_occluder_shape_sphere() const {
if (!_occluder) {
return nullptr;
}
Ref<OccluderShape> rshape = _occluder->get_shape();
if (rshape.is_null() || !rshape.is_valid()) {
return nullptr;
}
const OccluderShape *shape = rshape.ptr();
const OccluderShapeSphere *occ_sphere = Object::cast_to<OccluderShapeSphere>(shape);
return occ_sphere;
}
void OccluderSpatialGizmo::redraw() {
clear();
if (!_occluder) {
return;
}
Ref<Material> material_occluder = gizmo_plugin->get_material("occluder", this);
Color color(1, 1, 1, 1);
const OccluderShapeSphere *occ_sphere = get_occluder_shape_sphere();
if (occ_sphere) {
Vector<Plane> spheres = occ_sphere->get_spheres();
if (!spheres.size()) {
return;
}
Vector<Vector3> points;
Vector<Vector3> handles;
Vector<Vector3> radius_handles;
for (int n = 0; n < spheres.size(); n++) {
const Plane &p = spheres[n];
real_t r = p.d;
Vector3 offset = p.normal;
handles.push_back(offset);
// add a handle for the radius
radius_handles.push_back(offset + Vector3(r, 0, 0));
const int deg_change = 4;
for (int i = 0; i <= 360; i += deg_change) {
real_t ra = Math::deg2rad((real_t)i);
real_t rb = Math::deg2rad((real_t)i + deg_change);
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
points.push_back(offset + Vector3(a.x, 0, a.y));
points.push_back(offset + Vector3(b.x, 0, b.y));
points.push_back(offset + Vector3(0, a.x, a.y));
points.push_back(offset + Vector3(0, b.x, b.y));
points.push_back(offset + Vector3(a.x, a.y, 0));
points.push_back(offset + Vector3(b.x, b.y, 0));
}
} // for n through spheres
add_lines(points, material_occluder, false, color);
// handles
Ref<Material> material_handle = gizmo_plugin->get_material("occluder_handle", this);
Ref<Material> material_extra_handle = gizmo_plugin->get_material("extra_handle", this);
add_handles(handles, material_handle);
add_handles(radius_handles, material_extra_handle, false, true);
}
}
OccluderSpatialGizmo::OccluderSpatialGizmo(Occluder *p_occluder) {
_occluder = p_occluder;
set_spatial_node(p_occluder);
}