Merge pull request #53354 from reduz/remove-clipped-camera

This commit is contained in:
Rémi Verschelde 2021-10-04 17:19:15 +02:00 committed by GitHub
commit 7722048ed0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 66 additions and 419 deletions

View file

@ -26,7 +26,7 @@
<method name="get_camera_transform" qualifiers="const">
<return type="Transform3D" />
<description>
Returns the transform of the camera plus the vertical ([member v_offset]) and horizontal ([member h_offset]) offsets; and any other adjustments made to the position and orientation of the camera by subclassed cameras such as [ClippedCamera3D] and [XRCamera3D].
Returns the transform of the camera plus the vertical ([member v_offset]) and horizontal ([member h_offset]) offsets; and any other adjustments made to the position and orientation of the camera by subclassed cameras such as [XRCamera3D].
</description>
</method>
<method name="get_cull_mask_value" qualifiers="const">
@ -42,6 +42,12 @@
Returns the camera's frustum planes in world space units as an array of [Plane]s in the following order: near, far, left, top, right, bottom. Not to be confused with [member frustum_offset].
</description>
</method>
<method name="get_pyramid_shape_rid">
<return type="RID" />
<description>
Returns the RID of a pyramid shape encompassing the camera's view frustum, ignoring the camera's near plane. The tip of the pyramid represents the position of the camera.
</description>
</method>
<method name="is_position_behind" qualifiers="const">
<return type="bool" />
<argument index="0" name="world_point" type="Vector3" />

View file

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ClippedCamera3D" inherits="Camera3D" version="4.0">
<brief_description>
A [Camera3D] that includes collision.
</brief_description>
<description>
This node extends [Camera3D] to add collisions with [Area3D] and/or [PhysicsBody3D] nodes. The camera cannot move through colliding objects.
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_exception">
<return type="void" />
<argument index="0" name="node" type="Object" />
<description>
Adds a collision exception so the camera does not collide with the specified node.
</description>
</method>
<method name="add_exception_rid">
<return type="void" />
<argument index="0" name="rid" type="RID" />
<description>
Adds a collision exception so the camera does not collide with the specified [RID].
</description>
</method>
<method name="clear_exceptions">
<return type="void" />
<description>
Removes all collision exceptions.
</description>
</method>
<method name="get_clip_offset" qualifiers="const">
<return type="float" />
<description>
Returns the distance the camera has been offset due to a collision.
</description>
</method>
<method name="get_collision_mask_value" qualifiers="const">
<return type="bool" />
<argument index="0" name="layer_number" type="int" />
<description>
Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [code]layer_number[/code] between 1 and 32.
</description>
</method>
<method name="remove_exception">
<return type="void" />
<argument index="0" name="node" type="Object" />
<description>
Removes a collision exception with the specified node.
</description>
</method>
<method name="remove_exception_rid">
<return type="void" />
<argument index="0" name="rid" type="RID" />
<description>
Removes a collision exception with the specified [RID].
</description>
</method>
<method name="set_collision_mask_value">
<return type="void" />
<argument index="0" name="layer_number" type="int" />
<argument index="1" name="value" type="bool" />
<description>
Based on [code]value[/code], enables or disables the specified layer in the [member collision_mask], given a [code]layer_number[/code] between 1 and 32.
</description>
</method>
</methods>
<members>
<member name="clip_to_areas" type="bool" setter="set_clip_to_areas" getter="is_clip_to_areas_enabled" default="false">
If [code]true[/code], the camera stops on contact with [Area3D]s.
</member>
<member name="clip_to_bodies" type="bool" setter="set_clip_to_bodies" getter="is_clip_to_bodies_enabled" default="true">
If [code]true[/code], the camera stops on contact with [PhysicsBody3D]s.
</member>
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
The camera's collision mask. Only objects in at least one collision layer matching the mask will be detected. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.0">
The camera's collision margin. The camera can't get closer than this distance to a colliding object.
</member>
<member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="ClippedCamera3D.ClipProcessCallback" default="0">
The camera's process callback. See [enum ClipProcessCallback].
</member>
</members>
<constants>
<constant name="CLIP_PROCESS_PHYSICS" value="0" enum="ClipProcessCallback">
The camera updates with the [code]_physics_process[/code] callback.
</constant>
<constant name="CLIP_PROCESS_IDLE" value="1" enum="ClipProcessCallback">
The camera updates with the [code]_process[/code] callback.
</constant>
</constants>
</class>

View file

@ -1840,47 +1840,6 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->add_lines(lines, material);
p_gizmo->add_handles(handles, get_material("handles"));
ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera);
if (clipcam) {
Node3D *parent = Object::cast_to<Node3D>(camera->get_parent());
if (!parent) {
return;
}
Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized();
Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized();
Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized();
Vector3 cam_pos = camera->get_global_transform().origin;
Vector3 parent_pos = parent->get_global_transform().origin;
Plane parent_plane(parent_pos, cam_normal);
Vector3 ray_from = parent_plane.project(cam_pos);
lines.clear();
lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
if (parent_plane.distance_to(cam_pos) < 0) {
lines.push_back(ray_from);
lines.push_back(cam_pos);
}
Transform3D local = camera->get_global_transform().affine_inverse();
for (int i = 0; i < lines.size(); i++) {
lines.write[i] = local.xform(lines[i]);
}
p_gizmo->add_lines(lines, material);
}
}
//////

View file

@ -492,6 +492,7 @@ void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_frustum"), &Camera3D::get_frustum);
ClassDB::bind_method(D_METHOD("is_position_in_frustum", "world_point"), &Camera3D::is_position_in_frustum);
ClassDB::bind_method(D_METHOD("get_camera_rid"), &Camera3D::get_camera);
ClassDB::bind_method(D_METHOD("get_pyramid_shape_rid"), &Camera3D::get_pyramid_shape_rid);
ClassDB::bind_method(D_METHOD("set_cull_mask_value", "layer_number", "value"), &Camera3D::set_cull_mask_value);
ClassDB::bind_method(D_METHOD("get_cull_mask_value", "layer_number"), &Camera3D::get_cull_mask_value);
@ -654,6 +655,33 @@ Vector3 Camera3D::get_doppler_tracked_velocity() const {
}
}
RID Camera3D::get_pyramid_shape_rid() {
if (pyramid_shape == RID()) {
pyramid_shape_points = get_near_plane_points();
pyramid_shape = PhysicsServer3D::get_singleton()->convex_polygon_shape_create();
PhysicsServer3D::get_singleton()->shape_set_data(pyramid_shape, pyramid_shape_points);
} else { //check if points changed
Vector<Vector3> local_points = get_near_plane_points();
bool all_equal = true;
for (int i = 0; i < 5; i++) {
if (local_points[i] != pyramid_shape_points[i]) {
all_equal = false;
break;
}
}
if (!all_equal) {
PhysicsServer3D::get_singleton()->shape_set_data(pyramid_shape, local_points);
pyramid_shape_points = local_points;
}
}
return pyramid_shape;
}
Camera3D::Camera3D() {
camera = RenderingServer::get_singleton()->camera_create();
set_perspective(75.0, 0.05, 4000.0);
@ -666,221 +694,7 @@ Camera3D::Camera3D() {
Camera3D::~Camera3D() {
RenderingServer::get_singleton()->free(camera);
}
////////////////////////////////////////
void ClippedCamera3D::set_margin(real_t p_margin) {
margin = p_margin;
}
real_t ClippedCamera3D::get_margin() const {
return margin;
}
void ClippedCamera3D::set_process_callback(ClipProcessCallback p_mode) {
if (process_callback == p_mode) {
return;
}
process_callback = p_mode;
set_process_internal(process_callback == CLIP_PROCESS_IDLE);
set_physics_process_internal(process_callback == CLIP_PROCESS_PHYSICS);
}
ClippedCamera3D::ClipProcessCallback ClippedCamera3D::get_process_callback() const {
return process_callback;
}
Transform3D ClippedCamera3D::get_camera_transform() const {
Transform3D t = Camera3D::get_camera_transform();
t.origin += -t.basis.get_axis(Vector3::AXIS_Z).normalized() * clip_offset;
return t;
}
void ClippedCamera3D::_notification(int p_what) {
if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
Node3D *parent = Object::cast_to<Node3D>(get_parent());
if (!parent) {
return;
}
PhysicsDirectSpaceState3D *dspace = get_world_3d()->get_direct_space_state();
ERR_FAIL_COND(!dspace); // most likely physics set to threads
Vector3 cam_fw = -get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized();
Vector3 cam_pos = get_global_transform().origin;
Vector3 parent_pos = parent->get_global_transform().origin;
Plane parent_plane(parent_pos, cam_fw);
if (parent_plane.is_point_over(cam_pos)) {
//cam is beyond parent plane
return;
}
Vector3 ray_from = parent_plane.project(cam_pos);
clip_offset = 0; //reset by default
{ //check if points changed
Vector<Vector3> local_points = get_near_plane_points();
bool all_equal = true;
for (int i = 0; i < 5; i++) {
if (points[i] != local_points[i]) {
all_equal = false;
break;
}
}
if (!all_equal) {
PhysicsServer3D::get_singleton()->shape_set_data(pyramid_shape, local_points);
points = local_points;
}
}
Transform3D xf = get_global_transform();
xf.origin = ray_from;
xf.orthonormalize();
real_t closest_safe = 1.0f, closest_unsafe = 1.0f;
if (dspace->cast_motion(pyramid_shape, xf, cam_pos - ray_from, margin, closest_safe, closest_unsafe, exclude, collision_mask, clip_to_bodies, clip_to_areas)) {
clip_offset = cam_pos.distance_to(ray_from + (cam_pos - ray_from) * closest_safe);
}
_update_camera();
}
if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
update_gizmos();
if (pyramid_shape.is_valid()) {
PhysicsServer3D::get_singleton()->free(pyramid_shape);
}
}
void ClippedCamera3D::set_collision_mask(uint32_t p_mask) {
collision_mask = p_mask;
}
uint32_t ClippedCamera3D::get_collision_mask() const {
return collision_mask;
}
void ClippedCamera3D::set_collision_mask_value(int p_layer_number, bool p_value) {
ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
uint32_t mask = get_collision_mask();
if (p_value) {
mask |= 1 << (p_layer_number - 1);
} else {
mask &= ~(1 << (p_layer_number - 1));
}
set_collision_mask(mask);
}
bool ClippedCamera3D::get_collision_mask_value(int p_layer_number) const {
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
return get_collision_mask() & (1 << (p_layer_number - 1));
}
void ClippedCamera3D::add_exception_rid(const RID &p_rid) {
exclude.insert(p_rid);
}
void ClippedCamera3D::add_exception(const Object *p_object) {
ERR_FAIL_NULL(p_object);
const CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_object);
if (!co) {
return;
}
add_exception_rid(co->get_rid());
}
void ClippedCamera3D::remove_exception_rid(const RID &p_rid) {
exclude.erase(p_rid);
}
void ClippedCamera3D::remove_exception(const Object *p_object) {
ERR_FAIL_NULL(p_object);
const CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_object);
if (!co) {
return;
}
remove_exception_rid(co->get_rid());
}
void ClippedCamera3D::clear_exceptions() {
exclude.clear();
}
real_t ClippedCamera3D::get_clip_offset() const {
return clip_offset;
}
void ClippedCamera3D::set_clip_to_areas(bool p_clip) {
clip_to_areas = p_clip;
}
bool ClippedCamera3D::is_clip_to_areas_enabled() const {
return clip_to_areas;
}
void ClippedCamera3D::set_clip_to_bodies(bool p_clip) {
clip_to_bodies = p_clip;
}
bool ClippedCamera3D::is_clip_to_bodies_enabled() const {
return clip_to_bodies;
}
void ClippedCamera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_margin", "margin"), &ClippedCamera3D::set_margin);
ClassDB::bind_method(D_METHOD("get_margin"), &ClippedCamera3D::get_margin);
ClassDB::bind_method(D_METHOD("set_process_callback", "process_callback"), &ClippedCamera3D::set_process_callback);
ClassDB::bind_method(D_METHOD("get_process_callback"), &ClippedCamera3D::get_process_callback);
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ClippedCamera3D::set_collision_mask);
ClassDB::bind_method(D_METHOD("get_collision_mask"), &ClippedCamera3D::get_collision_mask);
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &ClippedCamera3D::set_collision_mask_value);
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &ClippedCamera3D::get_collision_mask_value);
ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ClippedCamera3D::add_exception_rid);
ClassDB::bind_method(D_METHOD("add_exception", "node"), &ClippedCamera3D::add_exception);
ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &ClippedCamera3D::remove_exception_rid);
ClassDB::bind_method(D_METHOD("remove_exception", "node"), &ClippedCamera3D::remove_exception);
ClassDB::bind_method(D_METHOD("set_clip_to_areas", "enable"), &ClippedCamera3D::set_clip_to_areas);
ClassDB::bind_method(D_METHOD("is_clip_to_areas_enabled"), &ClippedCamera3D::is_clip_to_areas_enabled);
ClassDB::bind_method(D_METHOD("get_clip_offset"), &ClippedCamera3D::get_clip_offset);
ClassDB::bind_method(D_METHOD("set_clip_to_bodies", "enable"), &ClippedCamera3D::set_clip_to_bodies);
ClassDB::bind_method(D_METHOD("is_clip_to_bodies_enabled"), &ClippedCamera3D::is_clip_to_bodies_enabled);
ClassDB::bind_method(D_METHOD("clear_exceptions"), &ClippedCamera3D::clear_exceptions);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,32,0.01"), "set_margin", "get_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_callback", "get_process_callback");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
ADD_GROUP("Clip To", "clip_to");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_to_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_clip_to_areas", "is_clip_to_areas_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_to_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_clip_to_bodies", "is_clip_to_bodies_enabled");
BIND_ENUM_CONSTANT(CLIP_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(CLIP_PROCESS_IDLE);
}
ClippedCamera3D::ClippedCamera3D() {
set_physics_process_internal(true);
set_notify_local_transform(Engine::get_singleton()->is_editor_hint());
points.resize(5);
pyramid_shape = PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONVEX_POLYGON);
}
ClippedCamera3D::~ClippedCamera3D() {
PhysicsServer3D::get_singleton()->free(pyramid_shape);
}

View file

@ -86,6 +86,9 @@ private:
DopplerTracking doppler_tracking = DOPPLER_TRACKING_DISABLED;
Ref<VelocityTracker3D> velocity_tracker;
RID pyramid_shape;
Vector<Vector3> pyramid_shape_points;
protected:
void _update_camera();
virtual void _request_camera_update();
@ -168,6 +171,8 @@ public:
Vector3 get_doppler_tracked_velocity() const;
RID get_pyramid_shape_rid();
Camera3D();
~Camera3D();
};
@ -176,63 +181,4 @@ VARIANT_ENUM_CAST(Camera3D::Projection);
VARIANT_ENUM_CAST(Camera3D::KeepAspect);
VARIANT_ENUM_CAST(Camera3D::DopplerTracking);
class ClippedCamera3D : public Camera3D {
GDCLASS(ClippedCamera3D, Camera3D);
public:
enum ClipProcessCallback {
CLIP_PROCESS_PHYSICS,
CLIP_PROCESS_IDLE,
};
private:
ClipProcessCallback process_callback = CLIP_PROCESS_PHYSICS;
RID pyramid_shape;
real_t margin = 0.0;
real_t clip_offset = 0.0;
uint32_t collision_mask = 1;
bool clip_to_areas = false;
bool clip_to_bodies = true;
Set<RID> exclude;
Vector<Vector3> points;
protected:
void _notification(int p_what);
static void _bind_methods();
virtual Transform3D get_camera_transform() const override;
public:
void set_clip_to_areas(bool p_clip);
bool is_clip_to_areas_enabled() const;
void set_clip_to_bodies(bool p_clip);
bool is_clip_to_bodies_enabled() const;
void set_margin(real_t p_margin);
real_t get_margin() const;
void set_process_callback(ClipProcessCallback p_mode);
ClipProcessCallback get_process_callback() const;
void set_collision_mask(uint32_t p_mask);
uint32_t get_collision_mask() const;
void set_collision_mask_value(int p_layer_number, bool p_value);
bool get_collision_mask_value(int p_layer_number) const;
void add_exception_rid(const RID &p_rid);
void add_exception(const Object *p_object);
void remove_exception_rid(const RID &p_rid);
void remove_exception(const Object *p_object);
void clear_exceptions();
real_t get_clip_offset() const;
ClippedCamera3D();
~ClippedCamera3D();
};
VARIANT_ENUM_CAST(ClippedCamera3D::ClipProcessCallback);
#endif

View file

@ -29,6 +29,7 @@
/*************************************************************************/
#include "spring_arm_3d.h"
#include "scene/3d/camera_3d.h"
void SpringArm3D::_notification(int p_what) {
switch (p_what) {
@ -133,17 +134,32 @@ void SpringArm3D::process_spring() {
Vector3 motion;
const Vector3 cast_direction(get_global_transform().basis.xform(Vector3(0, 0, 1)));
motion = Vector3(cast_direction * (spring_length));
if (shape.is_null()) {
motion = Vector3(cast_direction * (spring_length));
PhysicsDirectSpaceState3D::RayResult r;
bool intersected = get_world_3d()->get_direct_space_state()->intersect_ray(get_global_transform().origin, get_global_transform().origin + motion, r, excluded_objects, mask);
if (intersected) {
real_t dist = get_global_transform().origin.distance_to(r.position);
dist -= margin;
motion_delta = dist / (spring_length);
Camera3D *camera = nullptr;
for (int i = get_child_count() - 1; 0 <= i; --i) {
camera = Object::cast_to<Camera3D>(get_child(i));
if (camera) {
break;
}
}
if (camera != nullptr) {
//use camera rotation, but spring arm position
Transform3D base_transform = camera->get_global_transform();
base_transform.origin = get_global_transform().origin;
get_world_3d()->get_direct_space_state()->cast_motion(camera->get_pyramid_shape_rid(), base_transform, motion, 0, motion_delta, motion_delta_unsafe, excluded_objects, mask);
} else {
PhysicsDirectSpaceState3D::RayResult r;
bool intersected = get_world_3d()->get_direct_space_state()->intersect_ray(get_global_transform().origin, get_global_transform().origin + motion, r, excluded_objects, mask);
if (intersected) {
real_t dist = get_global_transform().origin.distance_to(r.position);
dist -= margin;
motion_delta = dist / (spring_length);
}
}
} else {
motion = Vector3(cast_direction * spring_length);
get_world_3d()->get_direct_space_state()->cast_motion(shape->get_rid(), get_global_transform(), motion, 0, motion_delta, motion_delta_unsafe, excluded_objects, mask);
}

View file

@ -448,7 +448,6 @@ void register_scene_types() {
GDREGISTER_VIRTUAL_CLASS(VisualInstance3D);
GDREGISTER_VIRTUAL_CLASS(GeometryInstance3D);
GDREGISTER_CLASS(Camera3D);
GDREGISTER_CLASS(ClippedCamera3D);
GDREGISTER_CLASS(AudioListener3D);
GDREGISTER_CLASS(XRCamera3D);
GDREGISTER_CLASS(XRController3D);