Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/classes/CylinderMesh.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="64">
Number of radial segments on the cylinder. Higher values result in a more detailed cylinder/cone at the cost of performance.
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius">
Mean of the radii of the ends. Setting the radius here sets the radius of both ends to be the provided value.
</member>
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="4">
Number of edge rings along the height of the cylinder. Changing [member rings] does not have any visual impact unless a shader or procedural mesh tool is used to alter the vertex data. Higher values result in more subdivisions, which can be used to create smoother-looking effects with shaders or procedural mesh tools (at the cost of performance). When not altering the vertex data using a shader or procedural mesh tool, [member rings] should be kept to its default value.
</member>
Expand Down
12 changes: 12 additions & 0 deletions doc/classes/PhysicsServer3DExtension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,18 @@
<description>
</description>
</method>
<method name="_tapered_capsule_shape_create" qualifiers="virtual required">
<return type="RID" />
<description>
Creates a tapered capsule shape. If unavailable, a capsule should be created instead.
</description>
</method>
<method name="_tapered_cylinder_shape_create" qualifiers="virtual required">
<return type="RID" />
<description>
Creates a tapered cylinder. If unavailable, a cylinder should be created instead.
</description>
</method>
<method name="_world_boundary_shape_create" qualifiers="virtual required">
<return type="RID" />
<description>
Expand Down
36 changes: 36 additions & 0 deletions doc/classes/TaperedCapsuleMesh.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TaperedCapsuleMesh" inherits="PrimitiveMesh" api_type="core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A mesh that is a tapered capsule-shaped [PrimitiveMesh].
</brief_description>
<description>
A mesh that is a tapered capsule-shaped [PrimitiveMesh]. A tapered capsule is a cylinder with hemispherical ends where the hemispheres can have different radii, creating a tapered or cone-like appearance. The shape is defined by two radii ([member top_radius] for the top hemisphere and [member bottom_radius] for the bottom hemisphere) and a [member mid_height] for the cylindrical section between them.
This shape is useful for creating various organic forms, projectiles, or tapered objects where smooth transitions between different radii are needed.
</description>
<tutorials>
</tutorials>
<members>
<member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="0.5">
Radius of the bottom hemisphere of the tapered capsule. This determines the size of the lower hemispherical end. When [member bottom_radius] differs from [member top_radius], the cylindrical section becomes tapered, creating a cone-like transition between the hemispheres.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
Total height of the tapered capsule, calculated as [member mid_height] + [member top_radius] + [member bottom_radius]. This is the full extent from the bottom of the lower hemisphere to the top of the upper hemisphere. Setting this property adjusts [member mid_height] while keeping the radii unchanged.
</member>
<member name="mid_height" type="float" setter="set_mid_height" getter="get_mid_height" default="1.0">
Height of the cylindrical section of the tapered capsule (excluding the hemispherical ends). This is the distance between the centers of the two hemispheres.
[b]Note:[/b] The [member mid_height] must be greater than [code]0.0[/code]. Unlike regular capsules, tapered capsules can have any positive height regardless of the radius values, allowing for creative shapes where hemispheres can conceptually overlap.
</member>
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="64">
Number of radial segments around the tapered capsule. Higher values result in a smoother, more detailed shape at the cost of performance.
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius">
Mean of the radii of the hemispheres. Setting the radius here sets the radius of both hemispheres to be the provided value.
</member>
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="16">
Number of edge rings along the height of the tapered capsule. Changing [member rings] does not have any visual impact unless a shader or procedural mesh tool is used to alter the vertex data. Higher values result in more subdivisions, which can be used to create smoother-looking effects with shaders or procedural mesh tools (at the cost of performance).
</member>
<member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="0.5">
Radius of the top hemisphere of the tapered capsule. This determines the size of the upper hemispherical end.
</member>
</members>
</class>
29 changes: 29 additions & 0 deletions doc/classes/TaperedCapsuleShape3D.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TaperedCapsuleShape3D" inherits="Shape3D" api_type="core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A 3D tapered capsule shape used for physics collision.
</brief_description>
<description>
A 3D tapered capsule shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. A tapered capsule consists of a cylinder with hemispherical ends where the hemispheres can have different radii, creating a tapered or cone-like collision shape.
This shape is particularly useful for projectiles, organic forms, or any collision object that needs smooth transitions between different sizes. The tapered design allows for more realistic physics interactions compared to basic geometric shapes.
[b]Performance:[/b] [TaperedCapsuleShape3D] provides good collision detection performance, similar to [CapsuleShape3D]. It is more complex than basic shapes like [SphereShape3D] and [BoxShape3D], but offers more flexibility for specific use cases.
</description>
<tutorials>
<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/2747</link>
</tutorials>
<members>
<member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="0.5">
The radius of the bottom hemisphere of the tapered capsule. This determines the size of the lower hemispherical end of the collision shape. When [member bottom_radius] differs from [member top_radius], the cylindrical section becomes tapered, creating a cone-like transition between the hemispheres for more complex collision detection.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
The total height of the tapered capsule, calculated as [member mid_height] + [member top_radius] + [member bottom_radius]. This is the full extent from the bottom of the lower hemisphere to the top of the upper hemisphere. Setting this property adjusts [member mid_height] while keeping the radii unchanged.
</member>
<member name="mid_height" type="float" setter="set_mid_height" getter="get_mid_height" default="1.0">
The height of the cylindrical section of the tapered capsule (excluding the hemispherical ends). This is the distance between the centers of the two hemispheres.
[b]Note:[/b] The [member mid_height] must be greater than [code]0.0[/code]. Unlike regular capsules, tapered capsules can have any positive height regardless of the radius values, providing more flexibility in collision shape design.
</member>
<member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="0.5">
The radius of the top hemisphere of the tapered capsule. This determines the size of the upper hemispherical end of the collision shape.
</member>
</members>
</class>
25 changes: 25 additions & 0 deletions doc/classes/TaperedCylinderShape3D.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TaperedCylinderShape3D" inherits="Shape3D" api_type="core" keywords="cone" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A 3D tapered cylinder shape used for physics collision.
</brief_description>
<description>
A 3D tapered cylinder shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. A tapered cylinder consists of a cylinder where the ends can have different radii, creating a tapered or cone-like collision shape.
This shape is particularly useful for projectiles, organic forms, or any collision object that needs smooth transitions between different sizes. The tapered design allows for more realistic physics interactions compared to basic geometric shapes.
[b]Performance:[/b] [TaperedCylinderShape3D] provides good collision detection performance, similar to [CylinderShape3D]. It is more complex than basic shapes like [SphereShape3D] and [BoxShape3D], but offers more flexibility for specific use cases.
</description>
<tutorials>
<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/2747</link>
</tutorials>
<members>
<member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="0.5">
The radius of the bottom end of the tapered cylinder. This determines the size of the lower end of the collision shape. When [member bottom_radius] differs from [member top_radius], the cylindrical section becomes tapered, creating a cone-like transition between the ends for more complex collision detection.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
The height of the cylinder.
</member>
<member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="0.5">
The radius of the top end of the tapered cylinder. This determines the size of the upper end of the collision shape.
</member>
</members>
</class>
2 changes: 1 addition & 1 deletion editor/icons/CylinderMesh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/TaperedCapsuleMesh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/TaperedCapsuleShape3D.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/TaperedCylinderShape3D.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 154 additions & 0 deletions editor/scene/3d/gizmos/gizmo_3d_helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,160 @@ void Gizmo3DHelper::cylinder_commit_handle(int p_id, const String &p_radius_acti
ur->commit_action();
}

Vector<Vector3> Gizmo3DHelper::tapered_capsule_cylinder_get_handles(real_t p_top_radius, real_t p_bottom_radius, real_t p_height) {
Vector<Vector3> handles;
handles.push_back(Vector3(p_top_radius, p_height * 0.5, 0)); // Radius Top handle on cylinder lip
handles.push_back(Vector3(p_bottom_radius, -p_height * 0.5, 0)); // Radius Bottom handle on cylinder lip
handles.push_back(Vector3(0, p_height * 0.5, 0)); // Mid Height handle at cylinder top center
return handles;
}

String Gizmo3DHelper::tapered_capsule_cylinder_get_handle_name(int p_id) {
switch (p_id) {
case 0:
return "Radius Top";
case 1:
return "Radius Bottom";
case 2:
return "Height";
}
return "";
}

void Gizmo3DHelper::tapered_capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_top_radius, real_t &r_bottom_radius, real_t &r_mid_height) {
if (p_id == 0 || p_id == 1) { // Radius handles
// Compute the smallest radius so that the ray from the camera is tangent to that circle
bool is_top = p_id == 0;
Vector3 center = Vector3(0, is_top ? r_mid_height / 2 : -r_mid_height / 2, 0);
real_t d = (Geometry3D::get_closest_point_to_segment(center, p_segment) - center).length();
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < CMP_EPSILON) {
d = CMP_EPSILON;
}
if (is_top) {
r_top_radius = d;
} else {
r_bottom_radius = d;
}
} else if (p_id == 2) { // Mid Height handle
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(0, -r_mid_height * 0.5, 0), Vector3(0, r_mid_height * 0.5 + 4096, 0), p_segment[0], p_segment[1], ra, rb);
real_t d = ra.y + r_mid_height * 0.5;
if (d < 0.001) {
d = 0.001;
}
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
r_mid_height = d;
}
}

void Gizmo3DHelper::tapered_capsule_commit_handle(int p_id, bool p_cancel, Object *p_object) {
if (p_cancel) {
Vector3 restore = initial_value;
p_object->set("top_radius", restore.x);
p_object->set("bottom_radius", restore.y);
p_object->set("mid_height", restore.z);
return;
}

EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
String action_name;
Vector3 restore = initial_value;
switch (p_id) {
case 0:
action_name = TTR("Change Tapered Capsule Radius Top");
ur->create_action(action_name);
ur->add_do_method(p_object, "set_top_radius", p_object->get("top_radius"));
ur->add_undo_method(p_object, "set_top_radius", restore.x);
break;
case 1:
action_name = TTR("Change Tapered Capsule Radius Bottom");
ur->create_action(action_name);
ur->add_do_method(p_object, "set_bottom_radius", p_object->get("bottom_radius"));
ur->add_undo_method(p_object, "set_bottom_radius", restore.y);
break;
case 2:
action_name = TTR("Change Tapered Capsule Mid Height");
ur->create_action(action_name);
ur->add_do_method(p_object, "set_mid_height", p_object->get("mid_height"));
ur->add_undo_method(p_object, "set_mid_height", restore.z);
break;
}
ur->commit_action();
}

void Gizmo3DHelper::tapered_cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_top_radius, real_t &r_bottom_radius, real_t &r_height) {
Vector3 ra, rb;

if (p_id == 0 || p_id == 1) { // Radius handles
// Compute the smallest radius so that the ray from the camera is tangent to a cylinder of that radius
bool is_top = p_id == 0;
Vector3 p0(p_segment[0].x, 0, p_segment[0].z);
Vector3 p1(p_segment[1].x, 0, p_segment[1].z);
real_t d = Geometry3D::get_closest_point_to_segment(Vector3(), p0, p1).length();
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < CMP_EPSILON) {
d = CMP_EPSILON;
}
if (is_top) {
r_top_radius = d;
} else {
r_bottom_radius = d;
}
} else if (p_id == 2) { // Height handle
Geometry3D::get_closest_points_between_segments(Vector3(0, -r_height * 0.5, 0), Vector3(0, r_height * 0.5 + 4096, 0), p_segment[0], p_segment[1], ra, rb);
real_t d = ra.y + r_height * 0.5;
if (d < 0.001) {
d = 0.001;
}
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
r_height = d;
}
}

void Gizmo3DHelper::tapered_cylinder_commit_handle(int p_id, bool p_cancel, Object *p_object) {
if (p_cancel) {
Vector3 restore = initial_value;
p_object->set("top_radius", restore.x);
p_object->set("bottom_radius", restore.y);
p_object->set("height", restore.z);
return;
}

EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
String action_name;
Vector3 restore = initial_value;
switch (p_id) {
case 0:
action_name = TTR("Change Tapered Cylinder Radius Top");
ur->create_action(action_name);
ur->add_do_method(p_object, "set_top_radius", p_object->get("top_radius"));
ur->add_undo_method(p_object, "set_top_radius", restore.x);
break;
case 1:
action_name = TTR("Change Tapered Cylinder Radius Bottom");
ur->create_action(action_name);
ur->add_do_method(p_object, "set_bottom_radius", p_object->get("bottom_radius"));
ur->add_undo_method(p_object, "set_bottom_radius", restore.y);
break;
case 2:
action_name = TTR("Change Tapered Cylinder Mid Height");
ur->create_action(action_name);
ur->add_do_method(p_object, "set_height", p_object->get("height"));
ur->add_undo_method(p_object, "set_height", restore.z);
break;
}
ur->commit_action();
}

Vector<Vector3> Gizmo3DHelper::cone_frustum_get_handles(real_t p_height, real_t p_radius_top, real_t p_radius_bottom) {
Vector<Vector3> handles;
handles.push_back(Vector3(0, p_height * 0.5, 0));
Expand Down
13 changes: 13 additions & 0 deletions editor/scene/3d/gizmos/gizmo_3d_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ class Gizmo3DHelper : public RefCounted {
cylinder_commit_handle(p_id, p_radius_action_name, p_height_action_name, p_cancel, p_position_object, p_height_object, p_radius_object, p_position_property, p_height_property, p_radius_property);
}

// Tapered Capsule and Cylinder

Vector<Vector3> tapered_capsule_cylinder_get_handles(real_t p_top_radius, real_t p_bottom_radius, real_t p_height);
String tapered_capsule_cylinder_get_handle_name(int p_id);

// Tapered capsule
void tapered_capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_top_radius, real_t &r_bottom_radius, real_t &r_mid_height);
void tapered_capsule_commit_handle(int p_id, bool p_cancel, Object *p_object);

// Tapered cylinder
void tapered_cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_top_radius, real_t &r_bottom_radius, real_t &r_height);
void tapered_cylinder_commit_handle(int p_id, bool p_cancel, Object *p_object);

// Cone frustum

Vector<Vector3> cone_frustum_get_handles(real_t p_height, real_t p_radius_top, real_t p_radius_bottom);
Expand Down
Loading
Loading