2
0
Эх сурвалжийг харах

Merge pull request #51896 from nekomatata/restore-ray-shape

Refactor RayShape and rename to SeparationRayShape
Camille Mohr-Daurat 4 жил өмнө
parent
commit
ca4f20529c
44 өөрчлөгдсөн 964 нэмэгдсэн , 99 устгасан
  1. 17 8
      doc/classes/PhysicsServer2D.xml
  2. 19 10
      doc/classes/PhysicsServer3D.xml
  3. 24 0
      doc/classes/SeparationRayShape2D.xml
  4. 24 0
      doc/classes/SeparationRayShape3D.xml
  5. 1 0
      editor/icons/SeparationRayShape2D.svg
  6. 1 0
      editor/icons/SeparationRayShape3D.svg
  7. 6 0
      editor/import/resource_importer_scene.cpp
  8. 41 0
      editor/plugins/collision_shape_2d_editor_plugin.cpp
  9. 1 0
      editor/plugins/collision_shape_2d_editor_plugin.h
  10. 53 0
      editor/plugins/node_3d_editor_gizmos.cpp
  11. 5 5
      scene/2d/physics_body_2d.cpp
  12. 1 1
      scene/2d/physics_body_2d.h
  13. 4 4
      scene/3d/physics_body_3d.cpp
  14. 1 1
      scene/3d/physics_body_3d.h
  15. 6 0
      scene/register_scene_types.cpp
  16. 119 0
      scene/resources/separation_ray_shape_2d.cpp
  17. 61 0
      scene/resources/separation_ray_shape_2d.h
  18. 91 0
      scene/resources/separation_ray_shape_3d.cpp
  19. 56 0
      scene/resources/separation_ray_shape_3d.h
  20. 2 2
      servers/physics_2d/body_pair_2d_sw.cpp
  21. 10 8
      servers/physics_2d/collision_solver_2d_sat.cpp
  22. 76 6
      servers/physics_2d/collision_solver_2d_sw.cpp
  23. 3 3
      servers/physics_2d/collision_solver_2d_sw.h
  24. 9 2
      servers/physics_2d/physics_server_2d_sw.cpp
  25. 2 1
      servers/physics_2d/physics_server_2d_sw.h
  26. 3 2
      servers/physics_2d/physics_server_2d_wrap_mt.h
  27. 41 8
      servers/physics_2d/shape_2d_sw.cpp
  28. 39 0
      servers/physics_2d/shape_2d_sw.h
  29. 15 5
      servers/physics_2d/space_2d_sw.cpp
  30. 1 1
      servers/physics_2d/space_2d_sw.h
  31. 4 2
      servers/physics_3d/collision_solver_3d_sat.cpp
  32. 57 0
      servers/physics_3d/collision_solver_3d_sw.cpp
  33. 1 1
      servers/physics_3d/collision_solver_3d_sw.h
  34. 8 2
      servers/physics_3d/physics_server_3d_sw.cpp
  35. 2 1
      servers/physics_3d/physics_server_3d_sw.h
  36. 3 2
      servers/physics_3d/physics_server_3d_wrap_mt.h
  37. 85 0
      servers/physics_3d/shape_3d_sw.cpp
  38. 39 11
      servers/physics_3d/shape_3d_sw.h
  39. 12 2
      servers/physics_3d/space_3d_sw.cpp
  40. 1 1
      servers/physics_3d/space_3d_sw.h
  41. 5 3
      servers/physics_server_2d.cpp
  42. 4 2
      servers/physics_server_2d.h
  43. 7 3
      servers/physics_server_3d.cpp
  44. 4 2
      servers/physics_server_3d.h

+ 17 - 8
doc/classes/PhysicsServer2D.xml

@@ -595,7 +595,8 @@
 			<argument index="2" name="motion" type="Vector2" />
 			<argument index="2" name="motion" type="Vector2" />
 			<argument index="3" name="margin" type="float" default="0.08" />
 			<argument index="3" name="margin" type="float" default="0.08" />
 			<argument index="4" name="result" type="PhysicsTestMotionResult2D" default="null" />
 			<argument index="4" name="result" type="PhysicsTestMotionResult2D" default="null" />
-			<argument index="5" name="exclude" type="Array" default="[]" />
+			<argument index="5" name="collide_separation_ray" type="bool" default="false" />
+			<argument index="6" name="exclude" type="Array" default="[]" />
 			<description>
 			<description>
 				Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult2D] can be passed to return additional information in.
 				Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult2D] can be passed to return additional information in.
 			</description>
 			</description>
@@ -726,6 +727,11 @@
 			<description>
 			<description>
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="separation_ray_shape_create">
+			<return type="RID" />
+			<description>
+			</description>
+		</method>
 		<method name="set_active">
 		<method name="set_active">
 			<return type="void" />
 			<return type="void" />
 			<argument index="0" name="active" type="bool" />
 			<argument index="0" name="active" type="bool" />
@@ -840,25 +846,28 @@
 		<constant name="SHAPE_WORLD_MARGIN" value="0" enum="ShapeType">
 		<constant name="SHAPE_WORLD_MARGIN" value="0" enum="ShapeType">
 			This is the constant for creating world margin shapes. A world margin shape is an [i]infinite[/i] line with an origin point, and a normal. Thus, it can be used for front/behind checks.
 			This is the constant for creating world margin shapes. A world margin shape is an [i]infinite[/i] line with an origin point, and a normal. Thus, it can be used for front/behind checks.
 		</constant>
 		</constant>
-		<constant name="SHAPE_SEGMENT" value="1" enum="ShapeType">
+		<constant name="SHAPE_SEPARATION_RAY" value="1" enum="ShapeType">
+			This is the constant for creating separation ray shapes. A separation ray is defined by a length and separates itself from what is touching its far endpoint. Useful for character controllers.
+		</constant>
+		<constant name="SHAPE_SEGMENT" value="2" enum="ShapeType">
 			This is the constant for creating segment shapes. A segment shape is a [i]finite[/i] line from a point A to a point B. It can be checked for intersections.
 			This is the constant for creating segment shapes. A segment shape is a [i]finite[/i] line from a point A to a point B. It can be checked for intersections.
 		</constant>
 		</constant>
-		<constant name="SHAPE_CIRCLE" value="2" enum="ShapeType">
+		<constant name="SHAPE_CIRCLE" value="3" enum="ShapeType">
 			This is the constant for creating circle shapes. A circle shape only has a radius. It can be used for intersections and inside/outside checks.
 			This is the constant for creating circle shapes. A circle shape only has a radius. It can be used for intersections and inside/outside checks.
 		</constant>
 		</constant>
-		<constant name="SHAPE_RECTANGLE" value="3" enum="ShapeType">
+		<constant name="SHAPE_RECTANGLE" value="4" enum="ShapeType">
 			This is the constant for creating rectangle shapes. A rectangle shape is defined by a width and a height. It can be used for intersections and inside/outside checks.
 			This is the constant for creating rectangle shapes. A rectangle shape is defined by a width and a height. It can be used for intersections and inside/outside checks.
 		</constant>
 		</constant>
-		<constant name="SHAPE_CAPSULE" value="4" enum="ShapeType">
+		<constant name="SHAPE_CAPSULE" value="5" enum="ShapeType">
 			This is the constant for creating capsule shapes. A capsule shape is defined by a radius and a length. It can be used for intersections and inside/outside checks.
 			This is the constant for creating capsule shapes. A capsule shape is defined by a radius and a length. It can be used for intersections and inside/outside checks.
 		</constant>
 		</constant>
-		<constant name="SHAPE_CONVEX_POLYGON" value="5" enum="ShapeType">
+		<constant name="SHAPE_CONVEX_POLYGON" value="6" enum="ShapeType">
 			This is the constant for creating convex polygon shapes. A polygon is defined by a list of points. It can be used for intersections and inside/outside checks. Unlike the [member CollisionPolygon2D.polygon] property, polygons modified with [method shape_set_data] do not verify that the points supplied form is a convex polygon.
 			This is the constant for creating convex polygon shapes. A polygon is defined by a list of points. It can be used for intersections and inside/outside checks. Unlike the [member CollisionPolygon2D.polygon] property, polygons modified with [method shape_set_data] do not verify that the points supplied form is a convex polygon.
 		</constant>
 		</constant>
-		<constant name="SHAPE_CONCAVE_POLYGON" value="6" enum="ShapeType">
+		<constant name="SHAPE_CONCAVE_POLYGON" value="7" enum="ShapeType">
 			This is the constant for creating concave polygon shapes. A polygon is defined by a list of points. It can be used for intersections checks, but not for inside/outside checks.
 			This is the constant for creating concave polygon shapes. A polygon is defined by a list of points. It can be used for intersections checks, but not for inside/outside checks.
 		</constant>
 		</constant>
-		<constant name="SHAPE_CUSTOM" value="7" enum="ShapeType">
+		<constant name="SHAPE_CUSTOM" value="8" enum="ShapeType">
 			This constant is used internally by the engine. Any attempt to create this kind of shape results in an error.
 			This constant is used internally by the engine. Any attempt to create this kind of shape results in an error.
 		</constant>
 		</constant>
 		<constant name="AREA_PARAM_GRAVITY" value="0" enum="AreaParameter">
 		<constant name="AREA_PARAM_GRAVITY" value="0" enum="AreaParameter">

+ 19 - 10
doc/classes/PhysicsServer3D.xml

@@ -571,7 +571,8 @@
 			<argument index="2" name="motion" type="Vector3" />
 			<argument index="2" name="motion" type="Vector3" />
 			<argument index="3" name="margin" type="float" default="0.001" />
 			<argument index="3" name="margin" type="float" default="0.001" />
 			<argument index="4" name="result" type="PhysicsTestMotionResult3D" default="null" />
 			<argument index="4" name="result" type="PhysicsTestMotionResult3D" default="null" />
-			<argument index="5" name="exclude" type="Array" default="[]" />
+			<argument index="5" name="collide_separation_ray" type="bool" default="false" />
+			<argument index="6" name="exclude" type="Array" default="[]" />
 			<description>
 			<description>
 				Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult3D] can be passed to return additional information in.
 				Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult3D] can be passed to return additional information in.
 			</description>
 			</description>
@@ -849,6 +850,11 @@
 			<description>
 			<description>
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="separation_ray_shape_create">
+			<return type="RID" />
+			<description>
+			</description>
+		</method>
 		<method name="set_active">
 		<method name="set_active">
 			<return type="void" />
 			<return type="void" />
 			<argument index="0" name="active" type="bool" />
 			<argument index="0" name="active" type="bool" />
@@ -1171,31 +1177,34 @@
 		<constant name="SHAPE_PLANE" value="0" enum="ShapeType">
 		<constant name="SHAPE_PLANE" value="0" enum="ShapeType">
 			The [Shape3D] is a [WorldMarginShape3D].
 			The [Shape3D] is a [WorldMarginShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_SPHERE" value="1" enum="ShapeType">
+		<constant name="SHAPE_SEPARATION_RAY" value="1" enum="ShapeType">
+			The [Shape3D] is a [SeparationRayShape3D].
+		</constant>
+		<constant name="SHAPE_SPHERE" value="2" enum="ShapeType">
 			The [Shape3D] is a [SphereShape3D].
 			The [Shape3D] is a [SphereShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_BOX" value="2" enum="ShapeType">
+		<constant name="SHAPE_BOX" value="3" enum="ShapeType">
 			The [Shape3D] is a [BoxShape3D].
 			The [Shape3D] is a [BoxShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_CAPSULE" value="3" enum="ShapeType">
+		<constant name="SHAPE_CAPSULE" value="4" enum="ShapeType">
 			The [Shape3D] is a [CapsuleShape3D].
 			The [Shape3D] is a [CapsuleShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_CYLINDER" value="4" enum="ShapeType">
+		<constant name="SHAPE_CYLINDER" value="5" enum="ShapeType">
 			The [Shape3D] is a [CylinderShape3D].
 			The [Shape3D] is a [CylinderShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_CONVEX_POLYGON" value="5" enum="ShapeType">
+		<constant name="SHAPE_CONVEX_POLYGON" value="6" enum="ShapeType">
 			The [Shape3D] is a [ConvexPolygonShape3D].
 			The [Shape3D] is a [ConvexPolygonShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_CONCAVE_POLYGON" value="6" enum="ShapeType">
+		<constant name="SHAPE_CONCAVE_POLYGON" value="7" enum="ShapeType">
 			The [Shape3D] is a [ConcavePolygonShape3D].
 			The [Shape3D] is a [ConcavePolygonShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_HEIGHTMAP" value="7" enum="ShapeType">
+		<constant name="SHAPE_HEIGHTMAP" value="8" enum="ShapeType">
 			The [Shape3D] is a [HeightMapShape3D].
 			The [Shape3D] is a [HeightMapShape3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_SOFT_BODY" value="8" enum="ShapeType">
+		<constant name="SHAPE_SOFT_BODY" value="9" enum="ShapeType">
 			The [Shape3D] is a [SoftBody3D].
 			The [Shape3D] is a [SoftBody3D].
 		</constant>
 		</constant>
-		<constant name="SHAPE_CUSTOM" value="9" enum="ShapeType">
+		<constant name="SHAPE_CUSTOM" value="10" enum="ShapeType">
 			This constant is used internally by the engine. Any attempt to create this kind of shape results in an error.
 			This constant is used internally by the engine. Any attempt to create this kind of shape results in an error.
 		</constant>
 		</constant>
 		<constant name="AREA_PARAM_GRAVITY" value="0" enum="AreaParameter">
 		<constant name="AREA_PARAM_GRAVITY" value="0" enum="AreaParameter">

+ 24 - 0
doc/classes/SeparationRayShape2D.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="SeparationRayShape2D" inherits="Shape2D" version="4.0">
+	<brief_description>
+		Separation ray shape for 2D collisions.
+	</brief_description>
+	<description>
+		Separation ray shape for 2D collisions. A ray is not really a collision body; instead, it tries to separate itself from whatever is touching its far endpoint. It's often useful for characters.
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="length" type="float" setter="set_length" getter="get_length" default="20.0">
+			The ray's length.
+		</member>
+		<member name="slide_on_slope" type="bool" setter="set_slide_on_slope" getter="get_slide_on_slope" default="false">
+			If [code]false[/code] (default), the shape always separates and returns a normal along its own direction.
+			If [code]true[/code], the shape can return the correct normal and separate in any direction, allowing sliding motion on slopes.
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 24 - 0
doc/classes/SeparationRayShape3D.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="SeparationRayShape3D" inherits="Shape3D" version="4.0">
+	<brief_description>
+		Separation ray shape for 3D collisions.
+	</brief_description>
+	<description>
+		Separation ray shape for 3D collisions, which can be set into a [PhysicsBody3D] or [Area3D]. A ray is not really a collision body; instead, it tries to separate itself from whatever is touching its far endpoint. It's often useful for characters.
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="length" type="float" setter="set_length" getter="get_length" default="1.0">
+			The ray's length.
+		</member>
+		<member name="slide_on_slope" type="bool" setter="set_slide_on_slope" getter="get_slide_on_slope" default="false">
+			If [code]false[/code] (default), the shape always separates and returns a normal along its own direction.
+			If [code]true[/code], the shape can return the correct normal and separate in any direction, allowing sliding motion on slopes.
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 1 - 0
editor/icons/SeparationRayShape2D.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1a1 1 0 0 0 -1 1v9.5859l-1.293-1.293a1 1 0 0 0 -.7207-.29102 1 1 0 0 0 -.69336.29102 1 1 0 0 0 0 1.4141l3 3a1.0001 1.0001 0 0 0 .0039062.003907 1 1 0 0 0 .050781.044921 1.0001 1.0001 0 0 0 .03125.027344 1 1 0 0 0 .048828.035156 1.0001 1.0001 0 0 0 .023438.015625 1 1 0 0 0 .076172.044922 1.0001 1.0001 0 0 0 .0058593.003906 1 1 0 0 0 .013672.007813 1.0001 1.0001 0 0 0 .078125.035156 1 1 0 0 0 .074219.025391 1.0001 1.0001 0 0 0 .025391.009766 1 1 0 0 0 .039062.009765 1.0001 1.0001 0 0 0 .068359.013672 1.0001 1.0001 0 0 0 .097656.011719 1.0001 1.0001 0 0 0 .0078125 0 1 1 0 0 0 .0625.003906 1 1 0 0 0 .015625-.001953 1.0001 1.0001 0 0 0 .083984-.003906 1 1 0 0 0 .015625-.001953 1.0001 1.0001 0 0 0 .083984-.013672 1.0001 1.0001 0 0 0 .052734-.013672 1 1 0 0 0 .058594-.015625 1.0001 1.0001 0 0 0 .078125-.029297 1 1 0 0 0 .013672-.00586 1.0001 1.0001 0 0 0 .076172-.037109 1 1 0 0 0 .013672-.007812 1.0001 1.0001 0 0 0 .072266-.044922 1 1 0 0 0 .011719-.007813 1.0001 1.0001 0 0 0 .068359-.052734 1 1 0 0 0 .011719-.009766 1.0001 1.0001 0 0 0 .050781-.046875l.0097657-.011719 2.9902-2.9883a1 1 0 0 0 0-1.4141 1 1 0 0 0 -1.4141 0l-1.293 1.293v-9.5859a1 1 0 0 0 -1-1z" fill="#68b6ff" fill-rule="evenodd"/></svg>

+ 1 - 0
editor/icons/SeparationRayShape3D.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m8 1-6 5 4 2.666v4.334l2 2v-5-2z" fill="#a2d2ff"/><path d="m8 1v7 2l-2-1.334v1.334l2 1.5v3.5l2-2v-4.334l4-2.666z" fill="#2998ff"/></g></svg>

+ 6 - 0
editor/import/resource_importer_scene.cpp

@@ -46,6 +46,7 @@
 #include "scene/resources/box_shape_3d.h"
 #include "scene/resources/box_shape_3d.h"
 #include "scene/resources/packed_scene.h"
 #include "scene/resources/packed_scene.h"
 #include "scene/resources/resource_format_text.h"
 #include "scene/resources/resource_format_text.h"
+#include "scene/resources/separation_ray_shape_3d.h"
 #include "scene/resources/sphere_shape_3d.h"
 #include "scene/resources/sphere_shape_3d.h"
 #include "scene/resources/surface_tool.h"
 #include "scene/resources/surface_tool.h"
 #include "scene/resources/world_margin_shape_3d.h"
 #include "scene/resources/world_margin_shape_3d.h"
@@ -379,6 +380,11 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
 				BoxShape3D *boxShape = memnew(BoxShape3D);
 				BoxShape3D *boxShape = memnew(BoxShape3D);
 				boxShape->set_size(Vector3(2, 2, 2));
 				boxShape->set_size(Vector3(2, 2, 2));
 				colshape->set_shape(boxShape);
 				colshape->set_shape(boxShape);
+			} else if (empty_draw_type == "SINGLE_ARROW") {
+				SeparationRayShape3D *rayShape = memnew(SeparationRayShape3D);
+				rayShape->set_length(1);
+				colshape->set_shape(rayShape);
+				Object::cast_to<Node3D>(sb)->rotate_x(Math_PI / 2);
 			} else if (empty_draw_type == "IMAGE") {
 			} else if (empty_draw_type == "IMAGE") {
 				WorldMarginShape3D *world_margin_shape = memnew(WorldMarginShape3D);
 				WorldMarginShape3D *world_margin_shape = memnew(WorldMarginShape3D);
 				colshape->set_shape(world_margin_shape);
 				colshape->set_shape(world_margin_shape);

+ 41 - 0
editor/plugins/collision_shape_2d_editor_plugin.cpp

@@ -38,6 +38,7 @@
 #include "scene/resources/convex_polygon_shape_2d.h"
 #include "scene/resources/convex_polygon_shape_2d.h"
 #include "scene/resources/rectangle_shape_2d.h"
 #include "scene/resources/rectangle_shape_2d.h"
 #include "scene/resources/segment_shape_2d.h"
 #include "scene/resources/segment_shape_2d.h"
+#include "scene/resources/separation_ray_shape_2d.h"
 #include "scene/resources/world_margin_shape_2d.h"
 #include "scene/resources/world_margin_shape_2d.h"
 
 
 void CollisionShape2DEditor::_node_removed(Node *p_node) {
 void CollisionShape2DEditor::_node_removed(Node *p_node) {
@@ -80,6 +81,15 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const {
 
 
 		} break;
 		} break;
 
 
+		case SEPARATION_RAY_SHAPE: {
+			Ref<SeparationRayShape2D> ray = node->get_shape();
+
+			if (idx == 0) {
+				return ray->get_length();
+			}
+
+		} break;
+
 		case RECTANGLE_SHAPE: {
 		case RECTANGLE_SHAPE: {
 			Ref<RectangleShape2D> rect = node->get_shape();
 			Ref<RectangleShape2D> rect = node->get_shape();
 
 
@@ -152,6 +162,15 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
 
 
 		} break;
 		} break;
 
 
+		case SEPARATION_RAY_SHAPE: {
+			Ref<SeparationRayShape2D> ray = node->get_shape();
+
+			ray->set_length(Math::abs(p_point.y));
+
+			canvas_item_editor->update_viewport();
+
+		} break;
+
 		case RECTANGLE_SHAPE: {
 		case RECTANGLE_SHAPE: {
 			if (idx < 8) {
 			if (idx < 8) {
 				Ref<RectangleShape2D> rect = node->get_shape();
 				Ref<RectangleShape2D> rect = node->get_shape();
@@ -253,6 +272,16 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
 
 
 		} break;
 		} break;
 
 
+		case SEPARATION_RAY_SHAPE: {
+			Ref<SeparationRayShape2D> ray = node->get_shape();
+
+			undo_redo->add_do_method(ray.ptr(), "set_length", ray->get_length());
+			undo_redo->add_do_method(canvas_item_editor, "update_viewport");
+			undo_redo->add_undo_method(ray.ptr(), "set_length", p_org);
+			undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
+
+		} break;
+
 		case RECTANGLE_SHAPE: {
 		case RECTANGLE_SHAPE: {
 			Ref<RectangleShape2D> rect = node->get_shape();
 			Ref<RectangleShape2D> rect = node->get_shape();
 
 
@@ -394,6 +423,8 @@ void CollisionShape2DEditor::_get_current_shape_type() {
 		shape_type = CONVEX_POLYGON_SHAPE;
 		shape_type = CONVEX_POLYGON_SHAPE;
 	} else if (Object::cast_to<WorldMarginShape2D>(*s)) {
 	} else if (Object::cast_to<WorldMarginShape2D>(*s)) {
 		shape_type = WORLD_MARGIN_SHAPE;
 		shape_type = WORLD_MARGIN_SHAPE;
+	} else if (Object::cast_to<SeparationRayShape2D>(*s)) {
+		shape_type = SEPARATION_RAY_SHAPE;
 	} else if (Object::cast_to<RectangleShape2D>(*s)) {
 	} else if (Object::cast_to<RectangleShape2D>(*s)) {
 		shape_type = RECTANGLE_SHAPE;
 		shape_type = RECTANGLE_SHAPE;
 	} else if (Object::cast_to<SegmentShape2D>(*s)) {
 	} else if (Object::cast_to<SegmentShape2D>(*s)) {
@@ -471,6 +502,16 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla
 
 
 		} break;
 		} break;
 
 
+		case SEPARATION_RAY_SHAPE: {
+			Ref<SeparationRayShape2D> shape = node->get_shape();
+
+			handles.resize(1);
+			handles.write[0] = Point2(0, shape->get_length());
+
+			p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
+
+		} break;
+
 		case RECTANGLE_SHAPE: {
 		case RECTANGLE_SHAPE: {
 			Ref<RectangleShape2D> shape = node->get_shape();
 			Ref<RectangleShape2D> shape = node->get_shape();
 
 

+ 1 - 0
editor/plugins/collision_shape_2d_editor_plugin.h

@@ -47,6 +47,7 @@ class CollisionShape2DEditor : public Control {
 		CONCAVE_POLYGON_SHAPE,
 		CONCAVE_POLYGON_SHAPE,
 		CONVEX_POLYGON_SHAPE,
 		CONVEX_POLYGON_SHAPE,
 		WORLD_MARGIN_SHAPE,
 		WORLD_MARGIN_SHAPE,
+		SEPARATION_RAY_SHAPE,
 		RECTANGLE_SHAPE,
 		RECTANGLE_SHAPE,
 		SEGMENT_SHAPE
 		SEGMENT_SHAPE
 	};
 	};

+ 53 - 0
editor/plugins/node_3d_editor_gizmos.cpp

@@ -66,6 +66,7 @@
 #include "scene/resources/cylinder_shape_3d.h"
 #include "scene/resources/cylinder_shape_3d.h"
 #include "scene/resources/height_map_shape_3d.h"
 #include "scene/resources/height_map_shape_3d.h"
 #include "scene/resources/primitive_meshes.h"
 #include "scene/resources/primitive_meshes.h"
+#include "scene/resources/separation_ray_shape_3d.h"
 #include "scene/resources/sphere_shape_3d.h"
 #include "scene/resources/sphere_shape_3d.h"
 #include "scene/resources/surface_tool.h"
 #include "scene/resources/surface_tool.h"
 #include "scene/resources/world_margin_shape_3d.h"
 #include "scene/resources/world_margin_shape_3d.h"
@@ -4067,6 +4068,10 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g
 		return p_id == 0 ? "Radius" : "Height";
 		return p_id == 0 ? "Radius" : "Height";
 	}
 	}
 
 
+	if (Object::cast_to<SeparationRayShape3D>(*s)) {
+		return "Length";
+	}
+
 	return "";
 	return "";
 }
 }
 
 
@@ -4098,6 +4103,11 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p
 		return p_id == 0 ? cs2->get_radius() : cs2->get_height();
 		return p_id == 0 ? cs2->get_radius() : cs2->get_height();
 	}
 	}
 
 
+	if (Object::cast_to<SeparationRayShape3D>(*s)) {
+		Ref<SeparationRayShape3D> cs2 = s;
+		return cs2->get_length();
+	}
+
 	return Variant();
 	return Variant();
 }
 }
 
 
@@ -4133,6 +4143,22 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i
 		ss->set_radius(d);
 		ss->set_radius(d);
 	}
 	}
 
 
+	if (Object::cast_to<SeparationRayShape3D>(*s)) {
+		Ref<SeparationRayShape3D> rs = s;
+		Vector3 ra, rb;
+		Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb);
+		float d = ra.z;
+		if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+			d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+		}
+
+		if (d < 0.001) {
+			d = 0.001;
+		}
+
+		rs->set_length(d);
+	}
+
 	if (Object::cast_to<BoxShape3D>(*s)) {
 	if (Object::cast_to<BoxShape3D>(*s)) {
 		Vector3 axis;
 		Vector3 axis;
 		axis[p_id] = 1.0;
 		axis[p_id] = 1.0;
@@ -4287,6 +4313,20 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo
 
 
 		ur->commit_action();
 		ur->commit_action();
 	}
 	}
+
+	if (Object::cast_to<SeparationRayShape3D>(*s)) {
+		Ref<SeparationRayShape3D> ss = s;
+		if (p_cancel) {
+			ss->set_length(p_restore);
+			return;
+		}
+
+		UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+		ur->create_action(TTR("Change Separation Ray Shape Length"));
+		ur->add_do_method(ss.ptr(), "set_length", ss->get_length());
+		ur->add_undo_method(ss.ptr(), "set_length", p_restore);
+		ur->commit_action();
+	}
 }
 }
 
 
 void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
@@ -4557,6 +4597,19 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 		p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
 		p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
 	}
 	}
 
 
+	if (Object::cast_to<SeparationRayShape3D>(*s)) {
+		Ref<SeparationRayShape3D> rs = s;
+
+		Vector<Vector3> points;
+		points.push_back(Vector3());
+		points.push_back(Vector3(0, 0, rs->get_length()));
+		p_gizmo->add_lines(points, material);
+		p_gizmo->add_collision_segments(points);
+		Vector<Vector3> handles;
+		handles.push_back(Vector3(0, 0, rs->get_length()));
+		p_gizmo->add_handles(handles, handles_material);
+	}
+
 	if (Object::cast_to<HeightMapShape3D>(*s)) {
 	if (Object::cast_to<HeightMapShape3D>(*s)) {
 		Ref<HeightMapShape3D> hms = s;
 		Ref<HeightMapShape3D> hms = s;
 
 

+ 5 - 5
scene/2d/physics_body_2d.cpp

@@ -70,12 +70,12 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_t
 	return Ref<KinematicCollision2D>();
 	return Ref<KinematicCollision2D>();
 }
 }
 
 
-bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, const Set<RID> &p_exclude) {
+bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) {
 	if (is_only_update_transform_changes_enabled()) {
 	if (is_only_update_transform_changes_enabled()) {
 		ERR_PRINT("Move functions do not work together with 'sync to physics' option. Please read the documentation.");
 		ERR_PRINT("Move functions do not work together with 'sync to physics' option. Please read the documentation.");
 	}
 	}
 	Transform2D gt = get_global_transform();
 	Transform2D gt = get_global_transform();
-	bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_exclude);
+	bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude);
 
 
 	// Restore direction of motion to be along original motion,
 	// Restore direction of motion to be along original motion,
 	// in order to avoid sliding due to recovery,
 	// in order to avoid sliding due to recovery,
@@ -1075,7 +1075,7 @@ bool CharacterBody2D::move_and_slide() {
 		PhysicsServer2D::MotionResult floor_result;
 		PhysicsServer2D::MotionResult floor_result;
 		Set<RID> exclude;
 		Set<RID> exclude;
 		exclude.insert(platform_rid);
 		exclude.insert(platform_rid);
-		if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, false, exclude)) {
+		if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, false, false, exclude)) {
 			motion_results.push_back(floor_result);
 			motion_results.push_back(floor_result);
 			_set_collision_direction(floor_result);
 			_set_collision_direction(floor_result);
 		}
 		}
@@ -1285,7 +1285,7 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up)
 
 
 	Transform2D gt = get_global_transform();
 	Transform2D gt = get_global_transform();
 	PhysicsServer2D::MotionResult result;
 	PhysicsServer2D::MotionResult result;
-	if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false)) {
+	if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) {
 		bool apply = true;
 		bool apply = true;
 		if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
 		if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
 			on_floor = true;
 			on_floor = true;
@@ -1319,7 +1319,7 @@ bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facin
 	}
 	}
 
 
 	PhysicsServer2D::MotionResult result;
 	PhysicsServer2D::MotionResult result;
-	if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false)) {
+	if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) {
 		if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
 		if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
 			return true;
 			return true;
 		}
 		}

+ 1 - 1
scene/2d/physics_body_2d.h

@@ -50,7 +50,7 @@ protected:
 	Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_test_only = false, real_t p_margin = 0.08);
 	Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_test_only = false, real_t p_margin = 0.08);
 
 
 public:
 public:
-	bool move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, const Set<RID> &p_exclude = Set<RID>());
+	bool move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>());
 	bool test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08);
 	bool test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08);
 
 
 	TypedArray<PhysicsBody2D> get_collision_exceptions();
 	TypedArray<PhysicsBody2D> get_collision_exceptions();

+ 4 - 4
scene/3d/physics_body_3d.cpp

@@ -111,9 +111,9 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_t
 	return Ref<KinematicCollision3D>();
 	return Ref<KinematicCollision3D>();
 }
 }
 
 
-bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, const Set<RID> &p_exclude) {
+bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) {
 	Transform3D gt = get_global_transform();
 	Transform3D gt = get_global_transform();
-	bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_exclude);
+	bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude);
 
 
 	// Restore direction of motion to be along original motion,
 	// Restore direction of motion to be along original motion,
 	// in order to avoid sliding due to recovery,
 	// in order to avoid sliding due to recovery,
@@ -1113,7 +1113,7 @@ bool CharacterBody3D::move_and_slide() {
 		PhysicsServer3D::MotionResult floor_result;
 		PhysicsServer3D::MotionResult floor_result;
 		Set<RID> exclude;
 		Set<RID> exclude;
 		exclude.insert(on_floor_body);
 		exclude.insert(on_floor_body);
-		if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, exclude)) {
+		if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, false, exclude)) {
 			motion_results.push_back(floor_result);
 			motion_results.push_back(floor_result);
 			_set_collision_direction(floor_result);
 			_set_collision_direction(floor_result);
 		}
 		}
@@ -1174,7 +1174,7 @@ bool CharacterBody3D::move_and_slide() {
 		// Apply snap.
 		// Apply snap.
 		Transform3D gt = get_global_transform();
 		Transform3D gt = get_global_transform();
 		PhysicsServer3D::MotionResult result;
 		PhysicsServer3D::MotionResult result;
-		if (move_and_collide(snap, result, margin, true, false)) {
+		if (move_and_collide(snap, result, margin, true, false, true)) {
 			bool apply = true;
 			bool apply = true;
 			if (up_direction != Vector3()) {
 			if (up_direction != Vector3()) {
 				if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
 				if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {

+ 1 - 1
scene/3d/physics_body_3d.h

@@ -53,7 +53,7 @@ protected:
 	Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001);
 	Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001);
 
 
 public:
 public:
-	bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, const Set<RID> &p_exclude = Set<RID>());
+	bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>());
 	bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001);
 	bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001);
 
 
 	void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock);
 	void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock);

+ 6 - 0
scene/register_scene_types.cpp

@@ -160,6 +160,8 @@
 #include "scene/resources/rectangle_shape_2d.h"
 #include "scene/resources/rectangle_shape_2d.h"
 #include "scene/resources/resource_format_text.h"
 #include "scene/resources/resource_format_text.h"
 #include "scene/resources/segment_shape_2d.h"
 #include "scene/resources/segment_shape_2d.h"
+#include "scene/resources/separation_ray_shape_2d.h"
+#include "scene/resources/separation_ray_shape_3d.h"
 #include "scene/resources/skeleton_modification_2d.h"
 #include "scene/resources/skeleton_modification_2d.h"
 #include "scene/resources/skeleton_modification_2d_ccdik.h"
 #include "scene/resources/skeleton_modification_2d_ccdik.h"
 #include "scene/resources/skeleton_modification_2d_fabrik.h"
 #include "scene/resources/skeleton_modification_2d_fabrik.h"
@@ -752,6 +754,7 @@ void register_scene_types() {
 	OS::get_singleton()->yield(); //may take time to init
 	OS::get_singleton()->yield(); //may take time to init
 
 
 	GDREGISTER_VIRTUAL_CLASS(Shape3D);
 	GDREGISTER_VIRTUAL_CLASS(Shape3D);
+	GDREGISTER_CLASS(SeparationRayShape3D);
 	GDREGISTER_CLASS(SphereShape3D);
 	GDREGISTER_CLASS(SphereShape3D);
 	GDREGISTER_CLASS(BoxShape3D);
 	GDREGISTER_CLASS(BoxShape3D);
 	GDREGISTER_CLASS(CapsuleShape3D);
 	GDREGISTER_CLASS(CapsuleShape3D);
@@ -840,6 +843,7 @@ void register_scene_types() {
 	GDREGISTER_VIRTUAL_CLASS(Shape2D);
 	GDREGISTER_VIRTUAL_CLASS(Shape2D);
 	GDREGISTER_CLASS(WorldMarginShape2D);
 	GDREGISTER_CLASS(WorldMarginShape2D);
 	GDREGISTER_CLASS(SegmentShape2D);
 	GDREGISTER_CLASS(SegmentShape2D);
+	GDREGISTER_CLASS(SeparationRayShape2D);
 	GDREGISTER_CLASS(CircleShape2D);
 	GDREGISTER_CLASS(CircleShape2D);
 	GDREGISTER_CLASS(RectangleShape2D);
 	GDREGISTER_CLASS(RectangleShape2D);
 	GDREGISTER_CLASS(CapsuleShape2D);
 	GDREGISTER_CLASS(CapsuleShape2D);
@@ -960,6 +964,8 @@ void register_scene_types() {
 	ClassDB::add_compatibility_class("ProceduralSky", "Sky");
 	ClassDB::add_compatibility_class("ProceduralSky", "Sky");
 	ClassDB::add_compatibility_class("ProximityGroup", "ProximityGroup3D");
 	ClassDB::add_compatibility_class("ProximityGroup", "ProximityGroup3D");
 	ClassDB::add_compatibility_class("RayCast", "RayCast3D");
 	ClassDB::add_compatibility_class("RayCast", "RayCast3D");
+	ClassDB::add_compatibility_class("RayShape", "SeparationRayShape3D");
+	ClassDB::add_compatibility_class("RayShape2D", "SeparationRayShape2D");
 	ClassDB::add_compatibility_class("RemoteTransform", "RemoteTransform3D");
 	ClassDB::add_compatibility_class("RemoteTransform", "RemoteTransform3D");
 	ClassDB::add_compatibility_class("RigidBody", "RigidBody3D");
 	ClassDB::add_compatibility_class("RigidBody", "RigidBody3D");
 	ClassDB::add_compatibility_class("Shape", "Shape3D");
 	ClassDB::add_compatibility_class("Shape", "Shape3D");

+ 119 - 0
scene/resources/separation_ray_shape_2d.cpp

@@ -0,0 +1,119 @@
+/*************************************************************************/
+/*  separation_ray_shape_2d.cpp                                          */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 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 "separation_ray_shape_2d.h"
+
+#include "servers/physics_server_2d.h"
+#include "servers/rendering_server.h"
+
+void SeparationRayShape2D::_update_shape() {
+	Dictionary d;
+	d["length"] = length;
+	d["slide_on_slope"] = slide_on_slope;
+	PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), d);
+	emit_changed();
+}
+
+void SeparationRayShape2D::draw(const RID &p_to_rid, const Color &p_color) {
+	const Vector2 target_position = Vector2(0, get_length());
+
+	const float max_arrow_size = 6;
+	const float line_width = 1.4;
+	bool no_line = target_position.length() < line_width;
+	float arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size);
+
+	if (no_line) {
+		arrow_size = target_position.length();
+	} else {
+		RS::get_singleton()->canvas_item_add_line(p_to_rid, Vector2(), target_position - target_position.normalized() * arrow_size, p_color, line_width);
+	}
+
+	Transform2D xf;
+	xf.rotate(target_position.angle());
+	xf.translate(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0));
+
+	Vector<Vector2> pts;
+	pts.push_back(xf.xform(Vector2(arrow_size, 0)));
+	pts.push_back(xf.xform(Vector2(0, 0.5 * arrow_size)));
+	pts.push_back(xf.xform(Vector2(0, -0.5 * arrow_size)));
+
+	Vector<Color> cols;
+	for (int i = 0; i < 3; i++) {
+		cols.push_back(p_color);
+	}
+
+	RS::get_singleton()->canvas_item_add_primitive(p_to_rid, pts, cols, Vector<Point2>(), RID());
+}
+
+Rect2 SeparationRayShape2D::get_rect() const {
+	Rect2 rect;
+	rect.position = Vector2();
+	rect.expand_to(Vector2(0, length));
+	rect = rect.grow(Math_SQRT12 * 4);
+	return rect;
+}
+
+real_t SeparationRayShape2D::get_enclosing_radius() const {
+	return length;
+}
+
+void SeparationRayShape2D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape2D::set_length);
+	ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape2D::get_length);
+
+	ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape2D::set_slide_on_slope);
+	ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape2D::get_slide_on_slope);
+
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length"), "set_length", "get_length");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope");
+}
+
+void SeparationRayShape2D::set_length(real_t p_length) {
+	length = p_length;
+	_update_shape();
+}
+
+real_t SeparationRayShape2D::get_length() const {
+	return length;
+}
+
+void SeparationRayShape2D::set_slide_on_slope(bool p_active) {
+	slide_on_slope = p_active;
+	_update_shape();
+}
+
+bool SeparationRayShape2D::get_slide_on_slope() const {
+	return slide_on_slope;
+}
+
+SeparationRayShape2D::SeparationRayShape2D() :
+		Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) {
+	_update_shape();
+}

+ 61 - 0
scene/resources/separation_ray_shape_2d.h

@@ -0,0 +1,61 @@
+/*************************************************************************/
+/*  separation_ray_shape_2d.h                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 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 SEPARATION_RAY_SHAPE_2D_H
+#define SEPARATION_RAY_SHAPE_2D_H
+
+#include "scene/resources/shape_2d.h"
+
+class SeparationRayShape2D : public Shape2D {
+	GDCLASS(SeparationRayShape2D, Shape2D);
+
+	real_t length = 20.0;
+	bool slide_on_slope = false;
+
+	void _update_shape();
+
+protected:
+	static void _bind_methods();
+
+public:
+	void set_length(real_t p_length);
+	real_t get_length() const;
+
+	void set_slide_on_slope(bool p_active);
+	bool get_slide_on_slope() const;
+
+	virtual void draw(const RID &p_to_rid, const Color &p_color) override;
+	virtual Rect2 get_rect() const override;
+	virtual real_t get_enclosing_radius() const override;
+
+	SeparationRayShape2D();
+};
+
+#endif // SEPARATION_RAY_SHAPE_2D_H

+ 91 - 0
scene/resources/separation_ray_shape_3d.cpp

@@ -0,0 +1,91 @@
+/*************************************************************************/
+/*  separation_ray_shape_3d.cpp                                          */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 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 "separation_ray_shape_3d.h"
+
+#include "servers/physics_server_3d.h"
+
+Vector<Vector3> SeparationRayShape3D::get_debug_mesh_lines() const {
+	Vector<Vector3> points;
+	points.push_back(Vector3());
+	points.push_back(Vector3(0, 0, get_length()));
+
+	return points;
+}
+
+real_t SeparationRayShape3D::get_enclosing_radius() const {
+	return length;
+}
+
+void SeparationRayShape3D::_update_shape() {
+	Dictionary d;
+	d["length"] = length;
+	d["slide_on_slope"] = slide_on_slope;
+	PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d);
+	Shape3D::_update_shape();
+}
+
+void SeparationRayShape3D::set_length(float p_length) {
+	length = p_length;
+	_update_shape();
+	notify_change_to_owners();
+}
+
+float SeparationRayShape3D::get_length() const {
+	return length;
+}
+
+void SeparationRayShape3D::set_slide_on_slope(bool p_active) {
+	slide_on_slope = p_active;
+	_update_shape();
+	notify_change_to_owners();
+}
+
+bool SeparationRayShape3D::get_slide_on_slope() const {
+	return slide_on_slope;
+}
+
+void SeparationRayShape3D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape3D::set_length);
+	ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape3D::get_length);
+
+	ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape3D::set_slide_on_slope);
+	ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape3D::get_slide_on_slope);
+
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_length", "get_length");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope");
+}
+
+SeparationRayShape3D::SeparationRayShape3D() :
+		Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) {
+	/* Code copied from setters to prevent the use of uninitialized variables */
+	_update_shape();
+	notify_change_to_owners();
+}

+ 56 - 0
scene/resources/separation_ray_shape_3d.h

@@ -0,0 +1,56 @@
+/*************************************************************************/
+/*  separation_ray_shape_3d.h                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 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 SEPARATION_RAY_SHAPE_H
+#define SEPARATION_RAY_SHAPE_H
+#include "scene/resources/shape_3d.h"
+
+class SeparationRayShape3D : public Shape3D {
+	GDCLASS(SeparationRayShape3D, Shape3D);
+	float length = 1.0;
+	bool slide_on_slope = false;
+
+protected:
+	static void _bind_methods();
+	virtual void _update_shape() override;
+
+public:
+	void set_length(float p_length);
+	float get_length() const;
+
+	void set_slide_on_slope(bool p_active);
+	bool get_slide_on_slope() const;
+
+	virtual Vector<Vector3> get_debug_mesh_lines() const override;
+	virtual real_t get_enclosing_radius() const override;
+
+	SeparationRayShape3D();
+};
+#endif // SEPARATION_RAY_SHAPE_H

+ 2 - 2
servers/physics_2d/body_pair_2d_sw.cpp

@@ -298,7 +298,7 @@ bool BodyPair2DSW::setup(real_t p_step) {
 	}
 	}
 
 
 	if (!prev_collided) {
 	if (!prev_collided) {
-		if (A->is_shape_set_as_one_way_collision(shape_A)) {
+		if (shape_B_ptr->allows_one_way_collision() && A->is_shape_set_as_one_way_collision(shape_A)) {
 			Vector2 direction = xform_A.get_axis(1).normalized();
 			Vector2 direction = xform_A.get_axis(1).normalized();
 			bool valid = false;
 			bool valid = false;
 			for (int i = 0; i < contact_count; i++) {
 			for (int i = 0; i < contact_count; i++) {
@@ -319,7 +319,7 @@ bool BodyPair2DSW::setup(real_t p_step) {
 			}
 			}
 		}
 		}
 
 
-		if (B->is_shape_set_as_one_way_collision(shape_B)) {
+		if (shape_A_ptr->allows_one_way_collision() && B->is_shape_set_as_one_way_collision(shape_B)) {
 			Vector2 direction = xform_B.get_axis(1).normalized();
 			Vector2 direction = xform_B.get_axis(1).normalized();
 			bool valid = false;
 			bool valid = false;
 			for (int i = 0; i < contact_count; i++) {
 			for (int i = 0; i < contact_count; i++) {

+ 10 - 8
servers/physics_2d/collision_solver_2d_sat.cpp

@@ -1115,11 +1115,13 @@ bool sat_2d_calculate_penetration(const Shape2DSW *p_shape_A, const Transform2D
 	PhysicsServer2D::ShapeType type_A = p_shape_A->get_type();
 	PhysicsServer2D::ShapeType type_A = p_shape_A->get_type();
 
 
 	ERR_FAIL_COND_V(type_A == PhysicsServer2D::SHAPE_WORLD_MARGIN, false);
 	ERR_FAIL_COND_V(type_A == PhysicsServer2D::SHAPE_WORLD_MARGIN, false);
+	ERR_FAIL_COND_V(type_A == PhysicsServer2D::SHAPE_SEPARATION_RAY, false);
 	ERR_FAIL_COND_V(p_shape_A->is_concave(), false);
 	ERR_FAIL_COND_V(p_shape_A->is_concave(), false);
 
 
 	PhysicsServer2D::ShapeType type_B = p_shape_B->get_type();
 	PhysicsServer2D::ShapeType type_B = p_shape_B->get_type();
 
 
 	ERR_FAIL_COND_V(type_B == PhysicsServer2D::SHAPE_WORLD_MARGIN, false);
 	ERR_FAIL_COND_V(type_B == PhysicsServer2D::SHAPE_WORLD_MARGIN, false);
+	ERR_FAIL_COND_V(type_B == PhysicsServer2D::SHAPE_SEPARATION_RAY, false);
 	ERR_FAIL_COND_V(p_shape_B->is_concave(), false);
 	ERR_FAIL_COND_V(p_shape_B->is_concave(), false);
 
 
 	static const CollisionFunc collision_table[5][5] = {
 	static const CollisionFunc collision_table[5][5] = {
@@ -1382,23 +1384,23 @@ bool sat_2d_calculate_penetration(const Shape2DSW *p_shape_A, const Transform2D
 
 
 	if (p_margin_A || p_margin_B) {
 	if (p_margin_A || p_margin_B) {
 		if (*motion_A == Vector2() && *motion_B == Vector2()) {
 		if (*motion_A == Vector2() && *motion_B == Vector2()) {
-			collision_func = collision_table_margin[type_A - 1][type_B - 1];
+			collision_func = collision_table_margin[type_A - 2][type_B - 2];
 		} else if (*motion_A != Vector2() && *motion_B == Vector2()) {
 		} else if (*motion_A != Vector2() && *motion_B == Vector2()) {
-			collision_func = collision_table_castA_margin[type_A - 1][type_B - 1];
+			collision_func = collision_table_castA_margin[type_A - 2][type_B - 2];
 		} else if (*motion_A == Vector2() && *motion_B != Vector2()) {
 		} else if (*motion_A == Vector2() && *motion_B != Vector2()) {
-			collision_func = collision_table_castB_margin[type_A - 1][type_B - 1];
+			collision_func = collision_table_castB_margin[type_A - 2][type_B - 2];
 		} else {
 		} else {
-			collision_func = collision_table_castA_castB_margin[type_A - 1][type_B - 1];
+			collision_func = collision_table_castA_castB_margin[type_A - 2][type_B - 2];
 		}
 		}
 	} else {
 	} else {
 		if (*motion_A == Vector2() && *motion_B == Vector2()) {
 		if (*motion_A == Vector2() && *motion_B == Vector2()) {
-			collision_func = collision_table[type_A - 1][type_B - 1];
+			collision_func = collision_table[type_A - 2][type_B - 2];
 		} else if (*motion_A != Vector2() && *motion_B == Vector2()) {
 		} else if (*motion_A != Vector2() && *motion_B == Vector2()) {
-			collision_func = collision_table_castA[type_A - 1][type_B - 1];
+			collision_func = collision_table_castA[type_A - 2][type_B - 2];
 		} else if (*motion_A == Vector2() && *motion_B != Vector2()) {
 		} else if (*motion_A == Vector2() && *motion_B != Vector2()) {
-			collision_func = collision_table_castB[type_A - 1][type_B - 1];
+			collision_func = collision_table_castB[type_A - 2][type_B - 2];
 		} else {
 		} else {
-			collision_func = collision_table_castA_castB[type_A - 1][type_B - 1];
+			collision_func = collision_table_castA_castB[type_A - 2][type_B - 2];
 		}
 		}
 	}
 	}
 
 

+ 76 - 6
servers/physics_2d/collision_solver_2d_sw.cpp

@@ -73,6 +73,65 @@ bool CollisionSolver2DSW::solve_static_world_margin(const Shape2DSW *p_shape_A,
 	return found;
 	return found;
 }
 }
 
 
+bool CollisionSolver2DSW::solve_separation_ray(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis, real_t p_margin) {
+	const SeparationRayShape2DSW *ray = static_cast<const SeparationRayShape2DSW *>(p_shape_A);
+	if (p_shape_B->get_type() == PhysicsServer2D::SHAPE_SEPARATION_RAY) {
+		return false;
+	}
+
+	Vector2 from = p_transform_A.get_origin();
+	Vector2 to = from + p_transform_A[1] * (ray->get_length() + p_margin);
+	if (p_motion_A != Vector2()) {
+		//not the best but should be enough
+		Vector2 normal = (to - from).normalized();
+		to += normal * MAX(0.0, normal.dot(p_motion_A));
+	}
+	Vector2 support_A = to;
+
+	Transform2D invb = p_transform_B.affine_inverse();
+	from = invb.xform(from);
+	to = invb.xform(to);
+
+	Vector2 p, n;
+	if (!p_shape_B->intersect_segment(from, to, p, n)) {
+		if (r_sep_axis) {
+			*r_sep_axis = p_transform_A[1].normalized();
+		}
+		return false;
+	}
+
+	// Discard contacts when the ray is fully contained inside the shape.
+	if (n == Vector2()) {
+		if (r_sep_axis) {
+			*r_sep_axis = p_transform_A[1].normalized();
+		}
+		return false;
+	}
+
+	// Discard contacts in the wrong direction.
+	if (n.dot(from - to) < CMP_EPSILON) {
+		if (r_sep_axis) {
+			*r_sep_axis = p_transform_A[1].normalized();
+		}
+		return false;
+	}
+
+	Vector2 support_B = p_transform_B.xform(p);
+	if (ray->get_slide_on_slope()) {
+		Vector2 global_n = invb.basis_xform_inv(n).normalized();
+		support_B = support_A + (support_B - support_A).length() * global_n;
+	}
+
+	if (p_result_callback) {
+		if (p_swap_result) {
+			p_result_callback(support_B, support_A, p_userdata);
+		} else {
+			p_result_callback(support_A, support_B, p_userdata);
+		}
+	}
+	return true;
+}
+
 struct _ConcaveCollisionInfo2D {
 struct _ConcaveCollisionInfo2D {
 	const Transform2D *transform_A;
 	const Transform2D *transform_A;
 	const Shape2DSW *shape_A;
 	const Shape2DSW *shape_A;
@@ -106,7 +165,7 @@ void CollisionSolver2DSW::concave_callback(void *p_userdata, Shape2DSW *p_convex
 	cinfo.collisions++;
 	cinfo.collisions++;
 }
 }
 
 
-bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis, real_t p_margin_A, real_t p_margin_B) {
+bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) {
 	const ConcaveShape2DSW *concave_B = static_cast<const ConcaveShape2DSW *>(p_shape_B);
 	const ConcaveShape2DSW *concave_B = static_cast<const ConcaveShape2DSW *>(p_shape_B);
 
 
 	_ConcaveCollisionInfo2D cinfo;
 	_ConcaveCollisionInfo2D cinfo;
@@ -119,7 +178,7 @@ bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transf
 	cinfo.swap_result = p_swap_result;
 	cinfo.swap_result = p_swap_result;
 	cinfo.collided = false;
 	cinfo.collided = false;
 	cinfo.collisions = 0;
 	cinfo.collisions = 0;
-	cinfo.sep_axis = sep_axis;
+	cinfo.sep_axis = r_sep_axis;
 	cinfo.margin_A = p_margin_A;
 	cinfo.margin_A = p_margin_A;
 	cinfo.margin_B = p_margin_B;
 	cinfo.margin_B = p_margin_B;
 
 
@@ -150,7 +209,7 @@ bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transf
 	return cinfo.collided;
 	return cinfo.collided;
 }
 }
 
 
-bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *sep_axis, real_t p_margin_A, real_t p_margin_B) {
+bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) {
 	PhysicsServer2D::ShapeType type_A = p_shape_A->get_type();
 	PhysicsServer2D::ShapeType type_A = p_shape_A->get_type();
 	PhysicsServer2D::ShapeType type_B = p_shape_B->get_type();
 	PhysicsServer2D::ShapeType type_B = p_shape_B->get_type();
 	bool concave_A = p_shape_A->is_concave();
 	bool concave_A = p_shape_A->is_concave();
@@ -177,18 +236,29 @@ bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p
 			return solve_static_world_margin(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false);
 			return solve_static_world_margin(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false);
 		}
 		}
 
 
+	} else if (type_A == PhysicsServer2D::SHAPE_SEPARATION_RAY) {
+		if (type_B == PhysicsServer2D::SHAPE_SEPARATION_RAY) {
+			return false; //no ray-ray
+		}
+
+		if (swap) {
+			return solve_separation_ray(p_shape_B, p_motion_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, r_sep_axis, p_margin_B);
+		} else {
+			return solve_separation_ray(p_shape_A, p_motion_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, r_sep_axis, p_margin_A);
+		}
+
 	} else if (concave_B) {
 	} else if (concave_B) {
 		if (concave_A) {
 		if (concave_A) {
 			return false;
 			return false;
 		}
 		}
 
 
 		if (!swap) {
 		if (!swap) {
-			return solve_concave(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, sep_axis, margin_A, margin_B);
+			return solve_concave(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, r_sep_axis, margin_A, margin_B);
 		} else {
 		} else {
-			return solve_concave(p_shape_B, p_transform_B, p_motion_B, p_shape_A, p_transform_A, p_motion_A, p_result_callback, p_userdata, true, sep_axis, margin_A, margin_B);
+			return solve_concave(p_shape_B, p_transform_B, p_motion_B, p_shape_A, p_transform_A, p_motion_A, p_result_callback, p_userdata, true, r_sep_axis, margin_A, margin_B);
 		}
 		}
 
 
 	} else {
 	} else {
-		return collision_solver(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, sep_axis, margin_A, margin_B);
+		return collision_solver(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, r_sep_axis, margin_A, margin_B);
 	}
 	}
 }
 }

+ 3 - 3
servers/physics_2d/collision_solver_2d_sw.h

@@ -40,11 +40,11 @@ public:
 private:
 private:
 	static bool solve_static_world_margin(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
 	static bool solve_static_world_margin(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
 	static void concave_callback(void *p_userdata, Shape2DSW *p_convex);
 	static void concave_callback(void *p_userdata, Shape2DSW *p_convex);
-	static bool solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0);
-	static bool solve_raycast(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *sep_axis = nullptr);
+	static bool solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0);
+	static bool solve_separation_ray(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis = nullptr, real_t p_margin = 0);
 
 
 public:
 public:
-	static bool solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0);
+	static bool solve(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, Vector2 *r_sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0);
 };
 };
 
 
 #endif // COLLISION_SOLVER_2D_SW_H
 #endif // COLLISION_SOLVER_2D_SW_H

+ 9 - 2
servers/physics_2d/physics_server_2d_sw.cpp

@@ -45,6 +45,9 @@ RID PhysicsServer2DSW::_shape_create(ShapeType p_shape) {
 		case SHAPE_WORLD_MARGIN: {
 		case SHAPE_WORLD_MARGIN: {
 			shape = memnew(WorldMarginShape2DSW);
 			shape = memnew(WorldMarginShape2DSW);
 		} break;
 		} break;
+		case SHAPE_SEPARATION_RAY: {
+			shape = memnew(SeparationRayShape2DSW);
+		} break;
 		case SHAPE_SEGMENT: {
 		case SHAPE_SEGMENT: {
 			shape = memnew(SegmentShape2DSW);
 			shape = memnew(SegmentShape2DSW);
 		} break;
 		} break;
@@ -79,6 +82,10 @@ RID PhysicsServer2DSW::world_margin_shape_create() {
 	return _shape_create(SHAPE_WORLD_MARGIN);
 	return _shape_create(SHAPE_WORLD_MARGIN);
 }
 }
 
 
+RID PhysicsServer2DSW::separation_ray_shape_create() {
+	return _shape_create(SHAPE_SEPARATION_RAY);
+}
+
 RID PhysicsServer2DSW::segment_shape_create() {
 RID PhysicsServer2DSW::segment_shape_create() {
 	return _shape_create(SHAPE_SEGMENT);
 	return _shape_create(SHAPE_SEGMENT);
 }
 }
@@ -939,7 +946,7 @@ void PhysicsServer2DSW::body_set_pickable(RID p_body, bool p_pickable) {
 	body->set_pickable(p_pickable);
 	body->set_pickable(p_pickable);
 }
 }
 
 
-bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, MotionResult *r_result, const Set<RID> &p_exclude) {
+bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) {
 	Body2DSW *body = body_owner.getornull(p_body);
 	Body2DSW *body = body_owner.getornull(p_body);
 	ERR_FAIL_COND_V(!body, false);
 	ERR_FAIL_COND_V(!body, false);
 	ERR_FAIL_COND_V(!body->get_space(), false);
 	ERR_FAIL_COND_V(!body->get_space(), false);
@@ -947,7 +954,7 @@ bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from,
 
 
 	_update_shapes();
 	_update_shapes();
 
 
-	return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_exclude);
+	return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude);
 }
 }
 
 
 PhysicsDirectBodyState2D *PhysicsServer2DSW::body_get_direct_state(RID p_body) {
 PhysicsDirectBodyState2D *PhysicsServer2DSW::body_get_direct_state(RID p_body) {

+ 2 - 1
servers/physics_2d/physics_server_2d_sw.h

@@ -88,6 +88,7 @@ public:
 	};
 	};
 
 
 	virtual RID world_margin_shape_create() override;
 	virtual RID world_margin_shape_create() override;
+	virtual RID separation_ray_shape_create() override;
 	virtual RID segment_shape_create() override;
 	virtual RID segment_shape_create() override;
 	virtual RID circle_shape_create() override;
 	virtual RID circle_shape_create() override;
 	virtual RID rectangle_shape_create() override;
 	virtual RID rectangle_shape_create() override;
@@ -246,7 +247,7 @@ public:
 
 
 	virtual void body_set_pickable(RID p_body, bool p_pickable) override;
 	virtual void body_set_pickable(RID p_body, bool p_pickable) override;
 
 
-	virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override;
+	virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override;
 
 
 	// this function only works on physics process, errors and returns null otherwise
 	// this function only works on physics process, errors and returns null otherwise
 	virtual PhysicsDirectBodyState2D *body_get_direct_state(RID p_body) override;
 	virtual PhysicsDirectBodyState2D *body_get_direct_state(RID p_body) override;

+ 3 - 2
servers/physics_2d/physics_server_2d_wrap_mt.h

@@ -80,6 +80,7 @@ public:
 
 
 	//FUNC1RID(shape,ShapeType); todo fix
 	//FUNC1RID(shape,ShapeType); todo fix
 	FUNCRID(world_margin_shape)
 	FUNCRID(world_margin_shape)
+	FUNCRID(separation_ray_shape)
 	FUNCRID(segment_shape)
 	FUNCRID(segment_shape)
 	FUNCRID(circle_shape)
 	FUNCRID(circle_shape)
 	FUNCRID(rectangle_shape)
 	FUNCRID(rectangle_shape)
@@ -252,9 +253,9 @@ public:
 
 
 	FUNC2(body_set_pickable, RID, bool);
 	FUNC2(body_set_pickable, RID, bool);
 
 
-	bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override {
+	bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override {
 		ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
 		ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
-		return physics_2d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_exclude);
+		return physics_2d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude);
 	}
 	}
 
 
 	// this function only works on physics process, errors and returns null otherwise
 	// this function only works on physics process, errors and returns null otherwise

+ 41 - 8
servers/physics_2d/shape_2d_sw.cpp

@@ -143,6 +143,46 @@ Variant WorldMarginShape2DSW::get_data() const {
 /*********************************************************/
 /*********************************************************/
 /*********************************************************/
 /*********************************************************/
 
 
+void SeparationRayShape2DSW::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
+	r_amount = 1;
+
+	if (p_normal.y > 0) {
+		*r_supports = Vector2(0, length);
+	} else {
+		*r_supports = Vector2();
+	}
+}
+
+bool SeparationRayShape2DSW::contains_point(const Vector2 &p_point) const {
+	return false;
+}
+
+bool SeparationRayShape2DSW::intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const {
+	return false; //rays can't be intersected
+}
+
+real_t SeparationRayShape2DSW::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
+	return 0; //rays are mass-less
+}
+
+void SeparationRayShape2DSW::set_data(const Variant &p_data) {
+	Dictionary d = p_data;
+	length = d["length"];
+	slide_on_slope = d["slide_on_slope"];
+	configure(Rect2(0, 0, 0.001, length));
+}
+
+Variant SeparationRayShape2DSW::get_data() const {
+	Dictionary d;
+	d["length"] = length;
+	d["slide_on_slope"] = slide_on_slope;
+	return d;
+}
+
+/*********************************************************/
+/*********************************************************/
+/*********************************************************/
+
 void SegmentShape2DSW::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
 void SegmentShape2DSW::get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const {
 	if (Math::abs(p_normal.dot(n)) > _SEGMENT_IS_VALID_SUPPORT_THRESHOLD) {
 	if (Math::abs(p_normal.dot(n)) > _SEGMENT_IS_VALID_SUPPORT_THRESHOLD) {
 		r_supports[0] = a;
 		r_supports[0] = a;
@@ -530,14 +570,7 @@ bool ConvexPolygonShape2DSW::intersect_segment(const Vector2 &p_begin, const Vec
 		}
 		}
 	}
 	}
 
 
-	if (inters) {
-		if (n.dot(r_normal) > 0) {
-			r_normal = -r_normal;
-		}
-	}
-
-	//return get_aabb().intersects_segment(p_begin,p_end,&r_point,&r_normal);
-	return inters; //todo
+	return inters;
 }
 }
 
 
 real_t ConvexPolygonShape2DSW::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {
 real_t ConvexPolygonShape2DSW::get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const {

+ 39 - 0
servers/physics_2d/shape_2d_sw.h

@@ -64,6 +64,8 @@ public:
 	_FORCE_INLINE_ Rect2 get_aabb() const { return aabb; }
 	_FORCE_INLINE_ Rect2 get_aabb() const { return aabb; }
 	_FORCE_INLINE_ bool is_configured() const { return configured; }
 	_FORCE_INLINE_ bool is_configured() const { return configured; }
 
 
+	virtual bool allows_one_way_collision() const { return true; }
+
 	virtual bool is_concave() const { return false; }
 	virtual bool is_concave() const { return false; }
 
 
 	virtual bool contains_point(const Vector2 &p_point) const = 0;
 	virtual bool contains_point(const Vector2 &p_point) const = 0;
@@ -177,6 +179,43 @@ public:
 	}
 	}
 };
 };
 
 
+class SeparationRayShape2DSW : public Shape2DSW {
+	real_t length;
+	bool slide_on_slope;
+
+public:
+	_FORCE_INLINE_ real_t get_length() const { return length; }
+	_FORCE_INLINE_ bool get_slide_on_slope() const { return slide_on_slope; }
+
+	virtual PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::SHAPE_SEPARATION_RAY; }
+
+	virtual bool allows_one_way_collision() const override { return false; }
+
+	virtual void project_rangev(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const override { project_range(p_normal, p_transform, r_min, r_max); }
+	virtual void get_supports(const Vector2 &p_normal, Vector2 *r_supports, int &r_amount) const override;
+
+	virtual bool contains_point(const Vector2 &p_point) const override;
+	virtual bool intersect_segment(const Vector2 &p_begin, const Vector2 &p_end, Vector2 &r_point, Vector2 &r_normal) const override;
+	virtual real_t get_moment_of_inertia(real_t p_mass, const Size2 &p_scale) const override;
+
+	virtual void set_data(const Variant &p_data) override;
+	virtual Variant get_data() const override;
+
+	_FORCE_INLINE_ void project_range(const Vector2 &p_normal, const Transform2D &p_transform, real_t &r_min, real_t &r_max) const {
+		//real large
+		r_max = p_normal.dot(p_transform.get_origin());
+		r_min = p_normal.dot(p_transform.xform(Vector2(0, length)));
+		if (r_max < r_min) {
+			SWAP(r_max, r_min);
+		}
+	}
+
+	DEFAULT_PROJECT_RANGE_CAST
+
+	_FORCE_INLINE_ SeparationRayShape2DSW() {}
+	_FORCE_INLINE_ SeparationRayShape2DSW(real_t p_length) { length = p_length; }
+};
+
 class SegmentShape2DSW : public Shape2DSW {
 class SegmentShape2DSW : public Shape2DSW {
 	Vector2 a;
 	Vector2 a;
 	Vector2 b;
 	Vector2 b;

+ 15 - 5
servers/physics_2d/space_2d_sw.cpp

@@ -528,7 +528,7 @@ int Space2DSW::_cull_aabb_for_body(Body2DSW *p_body, const Rect2 &p_aabb) {
 	return amount;
 	return amount;
 }
 }
 
 
-bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, const Set<RID> &p_exclude) {
+bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) {
 	//give me back regular physics engine logic
 	//give me back regular physics engine logic
 	//this is madness
 	//this is madness
 	//and most people using this function will think
 	//and most people using this function will think
@@ -621,7 +621,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
 
 
 					Transform2D col_obj_shape_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
 					Transform2D col_obj_shape_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
 
 
-					if (col_obj->is_shape_set_as_one_way_collision(shape_idx)) {
+					if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(shape_idx)) {
 						cbk.valid_dir = col_obj_shape_xform.get_axis(1).normalized();
 						cbk.valid_dir = col_obj_shape_xform.get_axis(1).normalized();
 
 
 						real_t owc_margin = col_obj->get_shape_one_way_collision_margin(shape_idx);
 						real_t owc_margin = col_obj->get_shape_one_way_collision_margin(shape_idx);
@@ -726,6 +726,16 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
 			}
 			}
 
 
 			Shape2DSW *body_shape = p_body->get_shape(body_shape_idx);
 			Shape2DSW *body_shape = p_body->get_shape(body_shape_idx);
+
+			// Colliding separation rays allows to properly snap to the ground,
+			// otherwise it's not needed in regular motion.
+			if (!p_collide_separation_ray && (body_shape->get_type() == PhysicsServer2D::SHAPE_SEPARATION_RAY)) {
+				// When slide on slope is on, separation ray shape acts like a regular shape.
+				if (!static_cast<SeparationRayShape2DSW *>(body_shape)->get_slide_on_slope()) {
+					continue;
+				}
+			}
+
 			Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(body_shape_idx);
 			Transform2D body_shape_xform = body_transform * p_body->get_shape_transform(body_shape_idx);
 
 
 			bool stuck = false;
 			bool stuck = false;
@@ -762,7 +772,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
 
 
 				//test initial overlap
 				//test initial overlap
 				if (CollisionSolver2DSW::solve(body_shape, body_shape_xform, Vector2(), against_shape, col_obj_shape_xform, Vector2(), nullptr, nullptr, nullptr, 0)) {
 				if (CollisionSolver2DSW::solve(body_shape, body_shape_xform, Vector2(), against_shape, col_obj_shape_xform, Vector2(), nullptr, nullptr, nullptr, 0)) {
-					if (col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) {
+					if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) {
 						Vector2 direction = col_obj_shape_xform.get_axis(1).normalized();
 						Vector2 direction = col_obj_shape_xform.get_axis(1).normalized();
 						if (motion_normal.dot(direction) < 0) {
 						if (motion_normal.dot(direction) < 0) {
 							continue;
 							continue;
@@ -806,7 +816,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
 					}
 					}
 				}
 				}
 
 
-				if (col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) {
+				if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(col_shape_idx)) {
 					Vector2 cd[2];
 					Vector2 cd[2];
 					PhysicsServer2DSW::CollCbkData cbk;
 					PhysicsServer2DSW::CollCbkData cbk;
 					cbk.max = 1;
 					cbk.max = 1;
@@ -904,7 +914,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
 
 
 				Transform2D col_obj_shape_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
 				Transform2D col_obj_shape_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
 
 
-				if (col_obj->is_shape_set_as_one_way_collision(shape_idx)) {
+				if (body_shape->allows_one_way_collision() && col_obj->is_shape_set_as_one_way_collision(shape_idx)) {
 					rcd.valid_dir = col_obj_shape_xform.get_axis(1).normalized();
 					rcd.valid_dir = col_obj_shape_xform.get_axis(1).normalized();
 
 
 					real_t owc_margin = col_obj->get_shape_one_way_collision_margin(shape_idx);
 					real_t owc_margin = col_obj->get_shape_one_way_collision_margin(shape_idx);

+ 1 - 1
servers/physics_2d/space_2d_sw.h

@@ -183,7 +183,7 @@ public:
 
 
 	int get_collision_pairs() const { return collision_pairs; }
 	int get_collision_pairs() const { return collision_pairs; }
 
 
-	bool test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, const Set<RID> &p_exclude = Set<RID>());
+	bool test_body_motion(Body2DSW *p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, PhysicsServer2D::MotionResult *r_result, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>());
 
 
 	void set_debug_contacts(int p_amount) { contact_debug.resize(p_amount); }
 	void set_debug_contacts(int p_amount) { contact_debug.resize(p_amount); }
 	_FORCE_INLINE_ bool is_debugging_contacts() const { return !contact_debug.is_empty(); }
 	_FORCE_INLINE_ bool is_debugging_contacts() const { return !contact_debug.is_empty(); }

+ 4 - 2
servers/physics_3d/collision_solver_3d_sat.cpp

@@ -2273,11 +2273,13 @@ bool sat_calculate_penetration(const Shape3DSW *p_shape_A, const Transform3D &p_
 	PhysicsServer3D::ShapeType type_A = p_shape_A->get_type();
 	PhysicsServer3D::ShapeType type_A = p_shape_A->get_type();
 
 
 	ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_PLANE, false);
 	ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_PLANE, false);
+	ERR_FAIL_COND_V(type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY, false);
 	ERR_FAIL_COND_V(p_shape_A->is_concave(), false);
 	ERR_FAIL_COND_V(p_shape_A->is_concave(), false);
 
 
 	PhysicsServer3D::ShapeType type_B = p_shape_B->get_type();
 	PhysicsServer3D::ShapeType type_B = p_shape_B->get_type();
 
 
 	ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_PLANE, false);
 	ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_PLANE, false);
+	ERR_FAIL_COND_V(type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY, false);
 	ERR_FAIL_COND_V(p_shape_B->is_concave(), false);
 	ERR_FAIL_COND_V(p_shape_B->is_concave(), false);
 
 
 	static const CollisionFunc collision_table[6][6] = {
 	static const CollisionFunc collision_table[6][6] = {
@@ -2382,10 +2384,10 @@ bool sat_calculate_penetration(const Shape3DSW *p_shape_A, const Transform3D &p_
 
 
 	CollisionFunc collision_func;
 	CollisionFunc collision_func;
 	if (margin_A != 0.0 || margin_B != 0.0) {
 	if (margin_A != 0.0 || margin_B != 0.0) {
-		collision_func = collision_table_margin[type_A - 1][type_B - 1];
+		collision_func = collision_table_margin[type_A - 2][type_B - 2];
 
 
 	} else {
 	} else {
-		collision_func = collision_table[type_A - 1][type_B - 1];
+		collision_func = collision_table[type_A - 2][type_B - 2];
 	}
 	}
 	ERR_FAIL_COND_V(!collision_func, false);
 	ERR_FAIL_COND_V(!collision_func, false);
 
 

+ 57 - 0
servers/physics_3d/collision_solver_3d_sw.cpp

@@ -89,6 +89,49 @@ bool CollisionSolver3DSW::solve_static_plane(const Shape3DSW *p_shape_A, const T
 	return found;
 	return found;
 }
 }
 
 
+bool CollisionSolver3DSW::solve_separation_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin) {
+	const SeparationRayShape3DSW *ray = static_cast<const SeparationRayShape3DSW *>(p_shape_A);
+
+	Vector3 from = p_transform_A.origin;
+	Vector3 to = from + p_transform_A.basis.get_axis(2) * (ray->get_length() + p_margin);
+	Vector3 support_A = to;
+
+	Transform3D ai = p_transform_B.affine_inverse();
+
+	from = ai.xform(from);
+	to = ai.xform(to);
+
+	Vector3 p, n;
+	if (!p_shape_B->intersect_segment(from, to, p, n)) {
+		return false;
+	}
+
+	// Discard contacts when the ray is fully contained inside the shape.
+	if (n == Vector3()) {
+		return false;
+	}
+
+	// Discard contacts in the wrong direction.
+	if (n.dot(from - to) < CMP_EPSILON) {
+		return false;
+	}
+
+	Vector3 support_B = p_transform_B.xform(p);
+	if (ray->get_slide_on_slope()) {
+		Vector3 global_n = ai.basis.xform_inv(n).normalized();
+		support_B = support_A + (support_B - support_A).length() * global_n;
+	}
+
+	if (p_result_callback) {
+		if (p_swap_result) {
+			p_result_callback(support_B, 0, support_A, 0, p_userdata);
+		} else {
+			p_result_callback(support_A, 0, support_B, 0, p_userdata);
+		}
+	}
+	return true;
+}
+
 struct _SoftBodyContactCollisionInfo {
 struct _SoftBodyContactCollisionInfo {
 	int node_index = 0;
 	int node_index = 0;
 	CollisionSolver3DSW::CallbackResult result_callback = nullptr;
 	CollisionSolver3DSW::CallbackResult result_callback = nullptr;
@@ -318,6 +361,9 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo
 		if (type_B == PhysicsServer3D::SHAPE_PLANE) {
 		if (type_B == PhysicsServer3D::SHAPE_PLANE) {
 			return false;
 			return false;
 		}
 		}
+		if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) {
+			return false;
+		}
 		if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) {
 		if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) {
 			return false;
 			return false;
 		}
 		}
@@ -328,6 +374,17 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo
 			return solve_static_plane(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false);
 			return solve_static_plane(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false);
 		}
 		}
 
 
+	} else if (type_A == PhysicsServer3D::SHAPE_SEPARATION_RAY) {
+		if (type_B == PhysicsServer3D::SHAPE_SEPARATION_RAY) {
+			return false;
+		}
+
+		if (swap) {
+			return solve_separation_ray(p_shape_B, p_transform_B, p_shape_A, p_transform_A, p_result_callback, p_userdata, true, p_margin_B);
+		} else {
+			return solve_separation_ray(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, p_margin_A);
+		}
+
 	} else if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) {
 	} else if (type_B == PhysicsServer3D::SHAPE_SOFT_BODY) {
 		if (type_A == PhysicsServer3D::SHAPE_SOFT_BODY) {
 		if (type_A == PhysicsServer3D::SHAPE_SOFT_BODY) {
 			// Soft Body / Soft Body not supported.
 			// Soft Body / Soft Body not supported.

+ 1 - 1
servers/physics_3d/collision_solver_3d_sw.h

@@ -43,7 +43,7 @@ private:
 	static void soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex);
 	static void soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex);
 	static void concave_callback(void *p_userdata, Shape3DSW *p_convex);
 	static void concave_callback(void *p_userdata, Shape3DSW *p_convex);
 	static bool solve_static_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
 	static bool solve_static_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
-	static bool solve_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
+	static bool solve_separation_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin = 0);
 	static bool solve_soft_body(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
 	static bool solve_soft_body(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
 	static bool solve_concave(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A = 0, real_t p_margin_B = 0);
 	static bool solve_concave(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A = 0, real_t p_margin_B = 0);
 	static void concave_distance_callback(void *p_userdata, Shape3DSW *p_convex);
 	static void concave_distance_callback(void *p_userdata, Shape3DSW *p_convex);

+ 8 - 2
servers/physics_3d/physics_server_3d_sw.cpp

@@ -48,6 +48,12 @@ RID PhysicsServer3DSW::plane_shape_create() {
 	shape->set_self(rid);
 	shape->set_self(rid);
 	return rid;
 	return rid;
 }
 }
+RID PhysicsServer3DSW::separation_ray_shape_create() {
+	Shape3DSW *shape = memnew(SeparationRayShape3DSW);
+	RID rid = shape_owner.make_rid(shape);
+	shape->set_self(rid);
+	return rid;
+}
 RID PhysicsServer3DSW::sphere_shape_create() {
 RID PhysicsServer3DSW::sphere_shape_create() {
 	Shape3DSW *shape = memnew(SphereShape3DSW);
 	Shape3DSW *shape = memnew(SphereShape3DSW);
 	RID rid = shape_owner.make_rid(shape);
 	RID rid = shape_owner.make_rid(shape);
@@ -848,7 +854,7 @@ void PhysicsServer3DSW::body_set_ray_pickable(RID p_body, bool p_enable) {
 	body->set_ray_pickable(p_enable);
 	body->set_ray_pickable(p_enable);
 }
 }
 
 
-bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, MotionResult *r_result, const Set<RID> &p_exclude) {
+bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) {
 	Body3DSW *body = body_owner.getornull(p_body);
 	Body3DSW *body = body_owner.getornull(p_body);
 	ERR_FAIL_COND_V(!body, false);
 	ERR_FAIL_COND_V(!body, false);
 	ERR_FAIL_COND_V(!body->get_space(), false);
 	ERR_FAIL_COND_V(!body->get_space(), false);
@@ -856,7 +862,7 @@ bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from,
 
 
 	_update_shapes();
 	_update_shapes();
 
 
-	return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_exclude);
+	return body->get_space()->test_body_motion(body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude);
 }
 }
 
 
 PhysicsDirectBodyState3D *PhysicsServer3DSW::body_get_direct_state(RID p_body) {
 PhysicsDirectBodyState3D *PhysicsServer3DSW::body_get_direct_state(RID p_body) {

+ 2 - 1
servers/physics_3d/physics_server_3d_sw.h

@@ -83,6 +83,7 @@ public:
 	static void _shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata);
 	static void _shape_col_cbk(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata);
 
 
 	virtual RID plane_shape_create() override;
 	virtual RID plane_shape_create() override;
+	virtual RID separation_ray_shape_create() override;
 	virtual RID sphere_shape_create() override;
 	virtual RID sphere_shape_create() override;
 	virtual RID box_shape_create() override;
 	virtual RID box_shape_create() override;
 	virtual RID capsule_shape_create() override;
 	virtual RID capsule_shape_create() override;
@@ -241,7 +242,7 @@ public:
 
 
 	virtual void body_set_ray_pickable(RID p_body, bool p_enable) override;
 	virtual void body_set_ray_pickable(RID p_body, bool p_enable) override;
 
 
-	virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override;
+	virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override;
 
 
 	// this function only works on physics process, errors and returns null otherwise
 	// this function only works on physics process, errors and returns null otherwise
 	virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override;
 	virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override;

+ 3 - 2
servers/physics_3d/physics_server_3d_wrap_mt.h

@@ -79,6 +79,7 @@ public:
 
 
 	//FUNC1RID(shape,ShapeType); todo fix
 	//FUNC1RID(shape,ShapeType); todo fix
 	FUNCRID(plane_shape)
 	FUNCRID(plane_shape)
+	FUNCRID(separation_ray_shape)
 	FUNCRID(sphere_shape)
 	FUNCRID(sphere_shape)
 	FUNCRID(box_shape)
 	FUNCRID(box_shape)
 	FUNCRID(capsule_shape)
 	FUNCRID(capsule_shape)
@@ -249,9 +250,9 @@ public:
 
 
 	FUNC2(body_set_ray_pickable, RID, bool);
 	FUNC2(body_set_ray_pickable, RID, bool);
 
 
-	bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) override {
+	bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) override {
 		ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
 		ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
-		return physics_3d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_exclude);
+		return physics_3d_server->body_test_motion(p_body, p_from, p_motion, p_margin, r_result, p_collide_separation_ray, p_exclude);
 	}
 	}
 
 
 	// this function only works on physics process, errors and returns null otherwise
 	// this function only works on physics process, errors and returns null otherwise

+ 85 - 0
servers/physics_3d/shape_3d_sw.cpp

@@ -164,6 +164,91 @@ Variant PlaneShape3DSW::get_data() const {
 PlaneShape3DSW::PlaneShape3DSW() {
 PlaneShape3DSW::PlaneShape3DSW() {
 }
 }
 
 
+//
+
+real_t SeparationRayShape3DSW::get_length() const {
+	return length;
+}
+
+bool SeparationRayShape3DSW::get_slide_on_slope() const {
+	return slide_on_slope;
+}
+
+void SeparationRayShape3DSW::project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const {
+	// don't think this will be even used
+	r_min = 0;
+	r_max = 1;
+}
+
+Vector3 SeparationRayShape3DSW::get_support(const Vector3 &p_normal) const {
+	if (p_normal.z > 0) {
+		return Vector3(0, 0, length);
+	} else {
+		return Vector3(0, 0, 0);
+	}
+}
+
+void SeparationRayShape3DSW::get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const {
+	if (Math::abs(p_normal.z) < _EDGE_IS_VALID_SUPPORT_THRESHOLD) {
+		r_amount = 2;
+		r_type = FEATURE_EDGE;
+		r_supports[0] = Vector3(0, 0, 0);
+		r_supports[1] = Vector3(0, 0, length);
+	} else if (p_normal.z > 0) {
+		r_amount = 1;
+		r_type = FEATURE_POINT;
+		*r_supports = Vector3(0, 0, length);
+	} else {
+		r_amount = 1;
+		r_type = FEATURE_POINT;
+		*r_supports = Vector3(0, 0, 0);
+	}
+}
+
+bool SeparationRayShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const {
+	return false; //simply not possible
+}
+
+bool SeparationRayShape3DSW::intersect_point(const Vector3 &p_point) const {
+	return false; //simply not possible
+}
+
+Vector3 SeparationRayShape3DSW::get_closest_point_to(const Vector3 &p_point) const {
+	Vector3 s[2] = {
+		Vector3(0, 0, 0),
+		Vector3(0, 0, length)
+	};
+
+	return Geometry3D::get_closest_point_to_segment(p_point, s);
+}
+
+Vector3 SeparationRayShape3DSW::get_moment_of_inertia(real_t p_mass) const {
+	return Vector3();
+}
+
+void SeparationRayShape3DSW::_setup(real_t p_length, bool p_slide_on_slope) {
+	length = p_length;
+	slide_on_slope = p_slide_on_slope;
+	configure(AABB(Vector3(0, 0, 0), Vector3(0.1, 0.1, length)));
+}
+
+void SeparationRayShape3DSW::set_data(const Variant &p_data) {
+	Dictionary d = p_data;
+	_setup(d["length"], d["slide_on_slope"]);
+}
+
+Variant SeparationRayShape3DSW::get_data() const {
+	Dictionary d;
+	d["length"] = length;
+	d["slide_on_slope"] = slide_on_slope;
+	return d;
+}
+
+SeparationRayShape3DSW::SeparationRayShape3DSW() {
+	length = 1;
+	slide_on_slope = false;
+}
+
 /********** SPHERE *************/
 /********** SPHERE *************/
 
 
 real_t SphereShape3DSW::get_radius() const {
 real_t SphereShape3DSW::get_radius() const {

+ 39 - 11
servers/physics_3d/shape_3d_sw.h

@@ -117,23 +117,51 @@ class PlaneShape3DSW : public Shape3DSW {
 public:
 public:
 	Plane get_plane() const;
 	Plane get_plane() const;
 
 
-	virtual real_t get_area() const { return INFINITY; }
-	virtual PhysicsServer3D::ShapeType get_type() const { return PhysicsServer3D::SHAPE_PLANE; }
-	virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const;
-	virtual Vector3 get_support(const Vector3 &p_normal) const;
-	virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const { r_amount = 0; }
+	virtual real_t get_area() const override { return INFINITY; }
+	virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_PLANE; }
+	virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override;
+	virtual Vector3 get_support(const Vector3 &p_normal) const override;
+	virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; }
 
 
-	virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const;
-	virtual bool intersect_point(const Vector3 &p_point) const;
-	virtual Vector3 get_closest_point_to(const Vector3 &p_point) const;
-	virtual Vector3 get_moment_of_inertia(real_t p_mass) const;
+	virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const override;
+	virtual bool intersect_point(const Vector3 &p_point) const override;
+	virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override;
+	virtual Vector3 get_moment_of_inertia(real_t p_mass) const override;
 
 
-	virtual void set_data(const Variant &p_data);
-	virtual Variant get_data() const;
+	virtual void set_data(const Variant &p_data) override;
+	virtual Variant get_data() const override;
 
 
 	PlaneShape3DSW();
 	PlaneShape3DSW();
 };
 };
 
 
+class SeparationRayShape3DSW : public Shape3DSW {
+	real_t length;
+	bool slide_on_slope;
+
+	void _setup(real_t p_length, bool p_slide_on_slope);
+
+public:
+	real_t get_length() const;
+	bool get_slide_on_slope() const;
+
+	virtual real_t get_area() const override { return 0.0; }
+	virtual PhysicsServer3D::ShapeType get_type() const override { return PhysicsServer3D::SHAPE_SEPARATION_RAY; }
+	virtual void project_range(const Vector3 &p_normal, const Transform3D &p_transform, real_t &r_min, real_t &r_max) const override;
+	virtual Vector3 get_support(const Vector3 &p_normal) const override;
+	virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override;
+
+	virtual bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_result, Vector3 &r_normal) const override;
+	virtual bool intersect_point(const Vector3 &p_point) const override;
+	virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override;
+
+	virtual Vector3 get_moment_of_inertia(real_t p_mass) const override;
+
+	virtual void set_data(const Variant &p_data) override;
+	virtual Variant get_data() const override;
+
+	SeparationRayShape3DSW();
+};
+
 class SphereShape3DSW : public Shape3DSW {
 class SphereShape3DSW : public Shape3DSW {
 	real_t radius;
 	real_t radius;
 
 

+ 12 - 2
servers/physics_3d/space_3d_sw.cpp

@@ -569,7 +569,7 @@ int Space3DSW::_cull_aabb_for_body(Body3DSW *p_body, const AABB &p_aabb) {
 	return amount;
 	return amount;
 }
 }
 
 
-bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, const Set<RID> &p_exclude) {
+bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_collide_separation_ray, const Set<RID> &p_exclude) {
 	//give me back regular physics engine logic
 	//give me back regular physics engine logic
 	//this is madness
 	//this is madness
 	//and most people using this function will think
 	//and most people using this function will think
@@ -714,9 +714,19 @@ bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, co
 				continue;
 				continue;
 			}
 			}
 
 
-			Transform3D body_shape_xform = body_transform * p_body->get_shape_transform(j);
 			Shape3DSW *body_shape = p_body->get_shape(j);
 			Shape3DSW *body_shape = p_body->get_shape(j);
 
 
+			// Colliding separation rays allows to properly snap to the ground,
+			// otherwise it's not needed in regular motion.
+			if (!p_collide_separation_ray && (body_shape->get_type() == PhysicsServer3D::SHAPE_SEPARATION_RAY)) {
+				// When slide on slope is on, separation ray shape acts like a regular shape.
+				if (!static_cast<SeparationRayShape3DSW *>(body_shape)->get_slide_on_slope()) {
+					continue;
+				}
+			}
+
+			Transform3D body_shape_xform = body_transform * p_body->get_shape_transform(j);
+
 			Transform3D body_shape_xform_inv = body_shape_xform.affine_inverse();
 			Transform3D body_shape_xform_inv = body_shape_xform.affine_inverse();
 			MotionShape3DSW mshape;
 			MotionShape3DSW mshape;
 			mshape.shape = body_shape;
 			mshape.shape = body_shape;

+ 1 - 1
servers/physics_3d/space_3d_sw.h

@@ -203,7 +203,7 @@ public:
 	void set_elapsed_time(ElapsedTime p_time, uint64_t p_msec) { elapsed_time[p_time] = p_msec; }
 	void set_elapsed_time(ElapsedTime p_time, uint64_t p_msec) { elapsed_time[p_time] = p_msec; }
 	uint64_t get_elapsed_time(ElapsedTime p_time) const { return elapsed_time[p_time]; }
 	uint64_t get_elapsed_time(ElapsedTime p_time) const { return elapsed_time[p_time]; }
 
 
-	bool test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, const Set<RID> &p_exclude = Set<RID>());
+	bool test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>());
 
 
 	Space3DSW();
 	Space3DSW();
 	~Space3DSW();
 	~Space3DSW();

+ 5 - 3
servers/physics_server_2d.cpp

@@ -500,7 +500,7 @@ void PhysicsTestMotionResult2D::_bind_methods() {
 
 
 ///////////////////////////////////////
 ///////////////////////////////////////
 
 
-bool PhysicsServer2D::_body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult2D> &p_result, const Vector<RID> &p_exclude) {
+bool PhysicsServer2D::_body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult2D> &p_result, bool p_collide_separation_ray, const Vector<RID> &p_exclude) {
 	MotionResult *r = nullptr;
 	MotionResult *r = nullptr;
 	if (p_result.is_valid()) {
 	if (p_result.is_valid()) {
 		r = p_result->get_result_ptr();
 		r = p_result->get_result_ptr();
@@ -509,11 +509,12 @@ bool PhysicsServer2D::_body_test_motion(RID p_body, const Transform2D &p_from, c
 	for (int i = 0; i < p_exclude.size(); i++) {
 	for (int i = 0; i < p_exclude.size(); i++) {
 		exclude.insert(p_exclude[i]);
 		exclude.insert(p_exclude[i]);
 	}
 	}
-	return body_test_motion(p_body, p_from, p_motion, p_margin, r, exclude);
+	return body_test_motion(p_body, p_from, p_motion, p_margin, r, p_collide_separation_ray, exclude);
 }
 }
 
 
 void PhysicsServer2D::_bind_methods() {
 void PhysicsServer2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("world_margin_shape_create"), &PhysicsServer2D::world_margin_shape_create);
 	ClassDB::bind_method(D_METHOD("world_margin_shape_create"), &PhysicsServer2D::world_margin_shape_create);
+	ClassDB::bind_method(D_METHOD("separation_ray_shape_create"), &PhysicsServer2D::separation_ray_shape_create);
 	ClassDB::bind_method(D_METHOD("segment_shape_create"), &PhysicsServer2D::segment_shape_create);
 	ClassDB::bind_method(D_METHOD("segment_shape_create"), &PhysicsServer2D::segment_shape_create);
 	ClassDB::bind_method(D_METHOD("circle_shape_create"), &PhysicsServer2D::circle_shape_create);
 	ClassDB::bind_method(D_METHOD("circle_shape_create"), &PhysicsServer2D::circle_shape_create);
 	ClassDB::bind_method(D_METHOD("rectangle_shape_create"), &PhysicsServer2D::rectangle_shape_create);
 	ClassDB::bind_method(D_METHOD("rectangle_shape_create"), &PhysicsServer2D::rectangle_shape_create);
@@ -635,7 +636,7 @@ void PhysicsServer2D::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("body_set_force_integration_callback", "body", "callable", "userdata"), &PhysicsServer2D::body_set_force_integration_callback, DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("body_set_force_integration_callback", "body", "callable", "userdata"), &PhysicsServer2D::body_set_force_integration_callback, DEFVAL(Variant()));
 
 
-	ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "exclude"), &PhysicsServer2D::_body_test_motion, DEFVAL(0.08), DEFVAL(Variant()), DEFVAL(Array()));
+	ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "collide_separation_ray", "exclude"), &PhysicsServer2D::_body_test_motion, DEFVAL(0.08), DEFVAL(Variant()), DEFVAL(false), DEFVAL(Array()));
 
 
 	ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer2D::body_get_direct_state);
 	ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer2D::body_get_direct_state);
 
 
@@ -675,6 +676,7 @@ void PhysicsServer2D::_bind_methods() {
 	BIND_ENUM_CONSTANT(SPACE_PARAM_TEST_MOTION_MIN_CONTACT_DEPTH);
 	BIND_ENUM_CONSTANT(SPACE_PARAM_TEST_MOTION_MIN_CONTACT_DEPTH);
 
 
 	BIND_ENUM_CONSTANT(SHAPE_WORLD_MARGIN);
 	BIND_ENUM_CONSTANT(SHAPE_WORLD_MARGIN);
+	BIND_ENUM_CONSTANT(SHAPE_SEPARATION_RAY);
 	BIND_ENUM_CONSTANT(SHAPE_SEGMENT);
 	BIND_ENUM_CONSTANT(SHAPE_SEGMENT);
 	BIND_ENUM_CONSTANT(SHAPE_CIRCLE);
 	BIND_ENUM_CONSTANT(SHAPE_CIRCLE);
 	BIND_ENUM_CONSTANT(SHAPE_RECTANGLE);
 	BIND_ENUM_CONSTANT(SHAPE_RECTANGLE);

+ 4 - 2
servers/physics_server_2d.h

@@ -210,7 +210,7 @@ class PhysicsServer2D : public Object {
 
 
 	static PhysicsServer2D *singleton;
 	static PhysicsServer2D *singleton;
 
 
-	virtual bool _body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, const Ref<PhysicsTestMotionResult2D> &p_result = Ref<PhysicsTestMotionResult2D>(), const Vector<RID> &p_exclude = Vector<RID>());
+	virtual bool _body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, const Ref<PhysicsTestMotionResult2D> &p_result = Ref<PhysicsTestMotionResult2D>(), bool p_collide_separation_ray = false, const Vector<RID> &p_exclude = Vector<RID>());
 
 
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();
@@ -220,6 +220,7 @@ public:
 
 
 	enum ShapeType {
 	enum ShapeType {
 		SHAPE_WORLD_MARGIN, ///< plane:"plane"
 		SHAPE_WORLD_MARGIN, ///< plane:"plane"
+		SHAPE_SEPARATION_RAY, ///< float:"length"
 		SHAPE_SEGMENT, ///< float:"length"
 		SHAPE_SEGMENT, ///< float:"length"
 		SHAPE_CIRCLE, ///< float:"radius"
 		SHAPE_CIRCLE, ///< float:"radius"
 		SHAPE_RECTANGLE, ///< vec3:"extents"
 		SHAPE_RECTANGLE, ///< vec3:"extents"
@@ -230,6 +231,7 @@ public:
 	};
 	};
 
 
 	virtual RID world_margin_shape_create() = 0;
 	virtual RID world_margin_shape_create() = 0;
+	virtual RID separation_ray_shape_create() = 0;
 	virtual RID segment_shape_create() = 0;
 	virtual RID segment_shape_create() = 0;
 	virtual RID circle_shape_create() = 0;
 	virtual RID circle_shape_create() = 0;
 	virtual RID rectangle_shape_create() = 0;
 	virtual RID rectangle_shape_create() = 0;
@@ -485,7 +487,7 @@ public:
 		}
 		}
 	};
 	};
 
 
-	virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) = 0;
+	virtual bool body_test_motion(RID p_body, const Transform2D &p_from, const Vector2 &p_motion, real_t p_margin = 0.08, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) = 0;
 
 
 	struct SeparationResult {
 	struct SeparationResult {
 		real_t collision_depth;
 		real_t collision_depth;

+ 7 - 3
servers/physics_server_3d.cpp

@@ -447,7 +447,7 @@ void PhysicsTestMotionResult3D::_bind_methods() {
 
 
 ///////////////////////////////////////
 ///////////////////////////////////////
 
 
-bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult3D> &p_result, const Vector<RID> &p_exclude) {
+bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin, const Ref<PhysicsTestMotionResult3D> &p_result, bool p_collide_separation_ray, const Vector<RID> &p_exclude) {
 	MotionResult *r = nullptr;
 	MotionResult *r = nullptr;
 	if (p_result.is_valid()) {
 	if (p_result.is_valid()) {
 		r = p_result->get_result_ptr();
 		r = p_result->get_result_ptr();
@@ -456,13 +456,15 @@ bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, c
 	for (int i = 0; i < p_exclude.size(); i++) {
 	for (int i = 0; i < p_exclude.size(); i++) {
 		exclude.insert(p_exclude[i]);
 		exclude.insert(p_exclude[i]);
 	}
 	}
-	return body_test_motion(p_body, p_from, p_motion, p_margin, r, exclude);
+	return body_test_motion(p_body, p_from, p_motion, p_margin, r, p_collide_separation_ray, exclude);
 }
 }
 
 
 RID PhysicsServer3D::shape_create(ShapeType p_shape) {
 RID PhysicsServer3D::shape_create(ShapeType p_shape) {
 	switch (p_shape) {
 	switch (p_shape) {
 		case SHAPE_PLANE:
 		case SHAPE_PLANE:
 			return plane_shape_create();
 			return plane_shape_create();
+		case SHAPE_SEPARATION_RAY:
+			return separation_ray_shape_create();
 		case SHAPE_SPHERE:
 		case SHAPE_SPHERE:
 			return sphere_shape_create();
 			return sphere_shape_create();
 		case SHAPE_BOX:
 		case SHAPE_BOX:
@@ -488,6 +490,7 @@ void PhysicsServer3D::_bind_methods() {
 #ifndef _3D_DISABLED
 #ifndef _3D_DISABLED
 
 
 	ClassDB::bind_method(D_METHOD("plane_shape_create"), &PhysicsServer3D::plane_shape_create);
 	ClassDB::bind_method(D_METHOD("plane_shape_create"), &PhysicsServer3D::plane_shape_create);
+	ClassDB::bind_method(D_METHOD("separation_ray_shape_create"), &PhysicsServer3D::separation_ray_shape_create);
 	ClassDB::bind_method(D_METHOD("sphere_shape_create"), &PhysicsServer3D::sphere_shape_create);
 	ClassDB::bind_method(D_METHOD("sphere_shape_create"), &PhysicsServer3D::sphere_shape_create);
 	ClassDB::bind_method(D_METHOD("box_shape_create"), &PhysicsServer3D::box_shape_create);
 	ClassDB::bind_method(D_METHOD("box_shape_create"), &PhysicsServer3D::box_shape_create);
 	ClassDB::bind_method(D_METHOD("capsule_shape_create"), &PhysicsServer3D::capsule_shape_create);
 	ClassDB::bind_method(D_METHOD("capsule_shape_create"), &PhysicsServer3D::capsule_shape_create);
@@ -609,7 +612,7 @@ void PhysicsServer3D::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("body_set_ray_pickable", "body", "enable"), &PhysicsServer3D::body_set_ray_pickable);
 	ClassDB::bind_method(D_METHOD("body_set_ray_pickable", "body", "enable"), &PhysicsServer3D::body_set_ray_pickable);
 
 
-	ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "exclude"), &PhysicsServer3D::_body_test_motion, DEFVAL(0.001), DEFVAL(Variant()), DEFVAL(Array()));
+	ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "margin", "result", "collide_separation_ray", "exclude"), &PhysicsServer3D::_body_test_motion, DEFVAL(0.001), DEFVAL(Variant()), DEFVAL(false), DEFVAL(Array()));
 
 
 	ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer3D::body_get_direct_state);
 	ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer3D::body_get_direct_state);
 
 
@@ -748,6 +751,7 @@ void PhysicsServer3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_process_info", "process_info"), &PhysicsServer3D::get_process_info);
 	ClassDB::bind_method(D_METHOD("get_process_info", "process_info"), &PhysicsServer3D::get_process_info);
 
 
 	BIND_ENUM_CONSTANT(SHAPE_PLANE);
 	BIND_ENUM_CONSTANT(SHAPE_PLANE);
+	BIND_ENUM_CONSTANT(SHAPE_SEPARATION_RAY);
 	BIND_ENUM_CONSTANT(SHAPE_SPHERE);
 	BIND_ENUM_CONSTANT(SHAPE_SPHERE);
 	BIND_ENUM_CONSTANT(SHAPE_BOX);
 	BIND_ENUM_CONSTANT(SHAPE_BOX);
 	BIND_ENUM_CONSTANT(SHAPE_CAPSULE);
 	BIND_ENUM_CONSTANT(SHAPE_CAPSULE);

+ 4 - 2
servers/physics_server_3d.h

@@ -212,7 +212,7 @@ class PhysicsServer3D : public Object {
 
 
 	static PhysicsServer3D *singleton;
 	static PhysicsServer3D *singleton;
 
 
-	virtual bool _body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, const Ref<PhysicsTestMotionResult3D> &p_result = Ref<PhysicsTestMotionResult3D>(), const Vector<RID> &p_exclude = Vector<RID>());
+	virtual bool _body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, const Ref<PhysicsTestMotionResult3D> &p_result = Ref<PhysicsTestMotionResult3D>(), bool p_collide_separation_ray = false, const Vector<RID> &p_exclude = Vector<RID>());
 
 
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();
@@ -222,6 +222,7 @@ public:
 
 
 	enum ShapeType {
 	enum ShapeType {
 		SHAPE_PLANE, ///< plane:"plane"
 		SHAPE_PLANE, ///< plane:"plane"
+		SHAPE_SEPARATION_RAY, ///< float:"length"
 		SHAPE_SPHERE, ///< float:"radius"
 		SHAPE_SPHERE, ///< float:"radius"
 		SHAPE_BOX, ///< vec3:"extents"
 		SHAPE_BOX, ///< vec3:"extents"
 		SHAPE_CAPSULE, ///< dict( float:"radius", float:"height"):capsule
 		SHAPE_CAPSULE, ///< dict( float:"radius", float:"height"):capsule
@@ -236,6 +237,7 @@ public:
 	RID shape_create(ShapeType p_shape);
 	RID shape_create(ShapeType p_shape);
 
 
 	virtual RID plane_shape_create() = 0;
 	virtual RID plane_shape_create() = 0;
+	virtual RID separation_ray_shape_create() = 0;
 	virtual RID sphere_shape_create() = 0;
 	virtual RID sphere_shape_create() = 0;
 	virtual RID box_shape_create() = 0;
 	virtual RID box_shape_create() = 0;
 	virtual RID capsule_shape_create() = 0;
 	virtual RID capsule_shape_create() = 0;
@@ -497,7 +499,7 @@ public:
 		}
 		}
 	};
 	};
 
 
-	virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, const Set<RID> &p_exclude = Set<RID>()) = 0;
+	virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()) = 0;
 
 
 	/* SOFT BODY */
 	/* SOFT BODY */