Browse Source

Clipped camera implementation, a camera that avoids going into geometry.

Juan Linietsky 7 years ago
parent
commit
e20864c340
4 changed files with 310 additions and 3 deletions
  1. 43 1
      editor/spatial_editor_gizmos.cpp
  2. 214 2
      scene/3d/camera.cpp
  3. 52 0
      scene/3d/camera.h
  4. 1 0
      scene/register_scene_types.cpp

+ 43 - 1
editor/spatial_editor_gizmos.cpp

@@ -1337,6 +1337,48 @@ void CameraSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
 	p_gizmo->add_collision_segments(lines);
 	p_gizmo->add_unscaled_billboard(icon, 0.05);
 	p_gizmo->add_handles(handles, get_material("handles"));
+
+	ClippedCamera *clipcam = Object::cast_to<ClippedCamera>(camera);
+	if (clipcam) {
+		Spatial *parent = Object::cast_to<Spatial>(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);
+		}
+
+		Transform 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);
+		p_gizmo->add_collision_segments(lines);
+	}
 }
 
 //////
@@ -3613,7 +3655,7 @@ void NavigationMeshSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
 	p_gizmo->add_collision_segments(lines);
 }
 
-//////
+	//////
 
 #define BODY_A_RADIUS 0.25
 #define BODY_B_RADIUS 0.27

+ 214 - 2
scene/3d/camera.cpp

@@ -31,9 +31,10 @@
 #include "camera.h"
 
 #include "camera_matrix.h"
+#include "collision_object.h"
+#include "engine.h"
 #include "scene/resources/material.h"
 #include "scene/resources/surface_tool.h"
-
 void Camera::_update_audio_listener_state() {
 }
 
@@ -313,6 +314,32 @@ bool Camera::is_position_behind(const Vector3 &p_pos) const {
 	return eyedir.dot(p_pos) < (eyedir.dot(t.origin) + near);
 }
 
+Vector<Vector3> Camera::get_near_plane_points() const {
+	if (!is_inside_tree()) {
+		ERR_EXPLAIN("Camera is not inside scene.");
+		ERR_FAIL_COND_V(!is_inside_tree(), Vector<Vector3>());
+	}
+
+	Size2 viewport_size = get_viewport()->get_visible_rect().size;
+
+	CameraMatrix cm;
+
+	if (mode == PROJECTION_ORTHOGONAL)
+		cm.set_orthogonal(size, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
+	else
+		cm.set_perspective(fov, viewport_size.aspect(), near, far, keep_aspect == KEEP_WIDTH);
+
+	Vector3 endpoints[8];
+	cm.get_endpoints(Transform(), endpoints);
+
+	Vector<Vector3> points;
+	points.push_back(Vector3());
+	for (int i = 0; i < 4; i++) {
+		points.push_back(endpoints[i + 4]);
+	}
+	return points;
+}
+
 Point2 Camera::unproject_position(const Vector3 &p_pos) const {
 
 	if (!is_inside_tree()) {
@@ -484,7 +511,7 @@ void Camera::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "fov", PROPERTY_HINT_RANGE, "1,179,0.1"), "set_fov", "get_fov");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "size", PROPERTY_HINT_RANGE, "0.1,16384,0.01"), "set_size", "get_size");
-	ADD_PROPERTY(PropertyInfo(Variant::REAL, "near", PROPERTY_HINT_EXP_RANGE, "0.1,8192,0.1,or_greater"), "set_znear", "get_znear");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "near", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01,or_greater"), "set_znear", "get_znear");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "far", PROPERTY_HINT_EXP_RANGE, "0.1,8192,0.1,or_greater"), "set_zfar", "get_zfar");
 
 	BIND_ENUM_CONSTANT(PROJECTION_PERSPECTIVE);
@@ -638,3 +665,188 @@ Camera::~Camera() {
 
 	VisualServer::get_singleton()->free(camera);
 }
+
+////////////////////////////////////////
+
+void ClippedCamera::set_margin(float p_margin) {
+	margin = p_margin;
+}
+float ClippedCamera::get_margin() const {
+	return margin;
+}
+void ClippedCamera::set_process_mode(ProcessMode p_mode) {
+
+	if (process_mode == p_mode) {
+		return;
+	}
+	set_process_internal(p_mode == CLIP_PROCESS_IDLE);
+	set_physics_process_internal(p_mode == CLIP_PROCESS_PHYSICS);
+}
+ClippedCamera::ProcessMode ClippedCamera::get_process_mode() const {
+	return process_mode;
+}
+
+Transform ClippedCamera::get_camera_transform() const {
+
+	Transform t = Camera::get_camera_transform();
+	t.origin += -t.basis.get_axis(Vector3::AXIS_Z).normalized() * clip_offset;
+	return t;
+}
+
+void ClippedCamera::_notification(int p_what) {
+	if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
+
+		Spatial *parent = Object::cast_to<Spatial>(get_parent());
+		if (!parent) {
+			return;
+		}
+
+		PhysicsDirectSpaceState *dspace = get_world()->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 defau;t
+
+		{ //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) {
+				PhysicsServer::get_singleton()->shape_set_data(pyramid_shape, local_points);
+				points = local_points;
+			}
+		}
+
+		Transform xf = get_global_transform();
+		xf.origin = ray_from;
+		xf.orthonormalize();
+
+		float csafe, cunsafe;
+		if (dspace->cast_motion(pyramid_shape, xf, cam_pos - ray_from, margin, csafe, cunsafe, exclude, collision_mask)) {
+			clip_offset = cam_pos.distance_to(ray_from + (cam_pos - ray_from).normalized() * csafe);
+		}
+
+		_update_camera();
+	}
+
+	if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
+		update_gizmo();
+	}
+}
+
+void ClippedCamera::set_collision_mask(uint32_t p_mask) {
+
+	collision_mask = p_mask;
+}
+
+uint32_t ClippedCamera::get_collision_mask() const {
+
+	return collision_mask;
+}
+
+void ClippedCamera::set_collision_mask_bit(int p_bit, bool p_value) {
+
+	uint32_t mask = get_collision_mask();
+	if (p_value)
+		mask |= 1 << p_bit;
+	else
+		mask &= ~(1 << p_bit);
+	set_collision_mask(mask);
+}
+
+bool ClippedCamera::get_collision_mask_bit(int p_bit) const {
+
+	return get_collision_mask() & (1 << p_bit);
+}
+
+void ClippedCamera::add_exception_rid(const RID &p_rid) {
+
+	exclude.insert(p_rid);
+}
+
+void ClippedCamera::add_exception(const Object *p_object) {
+
+	ERR_FAIL_NULL(p_object);
+	const CollisionObject *co = Object::cast_to<CollisionObject>(p_object);
+	if (!co)
+		return;
+	add_exception_rid(co->get_rid());
+}
+
+void ClippedCamera::remove_exception_rid(const RID &p_rid) {
+
+	exclude.erase(p_rid);
+}
+
+void ClippedCamera::remove_exception(const Object *p_object) {
+
+	ERR_FAIL_NULL(p_object);
+	const CollisionObject *co = Object::cast_to<CollisionObject>(p_object);
+	if (!co)
+		return;
+	remove_exception_rid(co->get_rid());
+}
+
+void ClippedCamera::clear_exceptions() {
+
+	exclude.clear();
+}
+void ClippedCamera::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_margin", "margin"), &ClippedCamera::set_margin);
+	ClassDB::bind_method(D_METHOD("get_margin"), &ClippedCamera::get_margin);
+
+	ClassDB::bind_method(D_METHOD("set_process_mode", "process_mode"), &ClippedCamera::set_process_mode);
+	ClassDB::bind_method(D_METHOD("get_process_mode"), &ClippedCamera::get_process_mode);
+
+	ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ClippedCamera::set_collision_mask);
+	ClassDB::bind_method(D_METHOD("get_collision_mask"), &ClippedCamera::get_collision_mask);
+
+	ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &ClippedCamera::set_collision_mask_bit);
+	ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &ClippedCamera::get_collision_mask_bit);
+
+	ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ClippedCamera::add_exception_rid);
+	ClassDB::bind_method(D_METHOD("add_exception", "node"), &ClippedCamera::add_exception);
+
+	ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &ClippedCamera::remove_exception_rid);
+	ClassDB::bind_method(D_METHOD("remove_exception", "node"), &ClippedCamera::remove_exception);
+
+	ClassDB::bind_method(D_METHOD("clear_exceptions"), &ClippedCamera::clear_exceptions);
+
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "margin", PROPERTY_HINT_RANGE, "0,32,0.01"), "set_margin", "get_margin");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
+}
+ClippedCamera::ClippedCamera() {
+	margin = 0;
+	clip_offset = 0;
+	process_mode = CLIP_PROCESS_PHYSICS;
+	set_physics_process_internal(true);
+	collision_mask = 1;
+	set_notify_local_transform(Engine::get_singleton()->is_editor_hint());
+	points.resize(5);
+	pyramid_shape = PhysicsServer::get_singleton()->shape_create(PhysicsServer::SHAPE_CONVEX_POLYGON);
+}
+ClippedCamera::~ClippedCamera() {
+	PhysicsServer::get_singleton()->free(pyramid_shape);
+}

+ 52 - 0
scene/3d/camera.h

@@ -139,6 +139,8 @@ public:
 	bool is_position_behind(const Vector3 &p_pos) const;
 	virtual Vector3 project_position(const Point2 &p_point) const;
 
+	Vector<Vector3> get_near_plane_points() const;
+
 	void set_cull_mask(uint32_t p_layers);
 	uint32_t get_cull_mask() const;
 
@@ -172,4 +174,54 @@ VARIANT_ENUM_CAST(Camera::Projection);
 VARIANT_ENUM_CAST(Camera::KeepAspect);
 VARIANT_ENUM_CAST(Camera::DopplerTracking);
 
+class ClippedCamera : public Camera {
+
+	GDCLASS(ClippedCamera, Camera);
+
+public:
+	enum ProcessMode {
+		CLIP_PROCESS_PHYSICS,
+		CLIP_PROCESS_IDLE,
+	};
+
+private:
+	ProcessMode process_mode;
+	RID pyramid_shape;
+	float margin;
+	float clip_offset;
+	uint32_t collision_mask;
+
+	Set<RID> exclude;
+
+	Vector<Vector3> points;
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+	virtual Transform get_camera_transform() const;
+
+public:
+	void set_margin(float p_margin);
+	float get_margin() const;
+
+	void set_process_mode(ProcessMode p_mode);
+	ProcessMode get_process_mode() const;
+
+	void set_collision_mask(uint32_t p_mask);
+	uint32_t get_collision_mask() const;
+
+	void set_collision_mask_bit(int p_bit, bool p_value);
+	bool get_collision_mask_bit(int p_bit) 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();
+
+	ClippedCamera();
+	~ClippedCamera();
+};
+
+VARIANT_ENUM_CAST(ClippedCamera::ProcessMode);
 #endif

+ 1 - 0
scene/register_scene_types.cpp

@@ -375,6 +375,7 @@ void register_scene_types() {
 	ClassDB::register_virtual_class<VisualInstance>();
 	ClassDB::register_virtual_class<GeometryInstance>();
 	ClassDB::register_class<Camera>();
+	ClassDB::register_class<ClippedCamera>();
 	ClassDB::register_class<Listener>();
 	ClassDB::register_class<ARVRCamera>();
 	ClassDB::register_class<ARVRController>();