diff --git a/doc/classes/SphereMesh.xml b/doc/classes/SphereMesh.xml
index 8f815bebbfe..0c47652cb36 100644
--- a/doc/classes/SphereMesh.xml
+++ b/doc/classes/SphereMesh.xml
@@ -9,12 +9,18 @@
+
+ Depth of sphere.
+
Full height of the sphere.
If [code]true[/code], a hemisphere is created rather than a full sphere.
- [b]Note:[/b] To get a regular hemisphere, the height and radius of the sphere must be equal.
+ [b]Note:[/b] To get a regular hemisphere, one of the following must be true:
+ 1. Shape mode is set to SPHERE.
+ 2. If the shape mode is SPHEROID, the height must be equal to the radius.
+ 3. If shape mode is ELLIPSOID, the width and depth must be equal, and the height must be half of the width or depth.
Number of radial segments on the sphere.
@@ -25,5 +31,22 @@
Number of segments along the height of the sphere.
+
+ Sets the shape mode to one of [enum SphereMesh.Shape].
+
+
+ Width of sphere.
+
+
+
+ A regular sphere controlled only by radius.
+
+
+ A sphere with adjustable height and radius.
+
+
+ A sphere with adjustable height, width, and depth.
+
+
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp
index d17724baa04..5c2032d8272 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp
@@ -370,7 +370,7 @@ void NavMeshGenerator3D::generator_parse_staticbody3d_node(const Refget_radius(), sphere->get_radius() * 2.0);
+ SphereMesh::create_mesh_array(arr, SphereMesh::SPHERE, sphere->get_radius(), sphere->get_radius() * 2.0, 0, 0);
p_source_geometry_data->add_mesh_array(arr, transform);
}
@@ -493,7 +493,7 @@ void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref
real_t radius = data;
Array arr;
arr.resize(RS::ARRAY_MAX);
- SphereMesh::create_mesh_array(arr, radius, radius * 2.0);
+ SphereMesh::create_mesh_array(arr, SphereMesh::SPHERE, radius, radius * 2.0, 0, 0);
p_source_geometry_data->add_mesh_array(arr, shapes[i]);
} break;
case PhysicsServer3D::SHAPE_BOX: {
diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp
index ee772f960a0..00460c10a16 100644
--- a/scene/resources/3d/primitive_meshes.cpp
+++ b/scene/resources/3d/primitive_meshes.cpp
@@ -1765,9 +1765,28 @@ void SphereMesh::_update_lightmap_size() {
float texel_size = get_lightmap_texel_size();
float padding = get_uv2_padding();
- float _width = radius * Math_TAU;
+ // Determine dimensions based on shape mode
+ float _width, _height;
+
+ switch (shape) {
+ case SPHERE:
+ _width = radius * Math_TAU;
+ _height = (is_hemisphere ? 1.0f : 0.5f) * radius * Math_PI;
+ break;
+ case SPHEROID:
+ _width = height * Math_TAU;
+ _height = (is_hemisphere ? height : height * 0.5f) * Math_PI;
+ break;
+ case ELLIPSOID:
+ _width = width * Math_TAU;
+ _height = (is_hemisphere ? height : height * 0.5f) * Math_PI;
+ break;
+ default:
+ _width = _height = 1.0f;
+ break;
+ }
+
_lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding);
- float _height = (is_hemisphere ? 1.0 : 0.5) * height * Math_PI; // note, with hemisphere height is our radius, while with a full sphere it is the diameter..
_lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding);
set_lightmap_size_hint(_lightmap_size_hint);
@@ -1779,21 +1798,41 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const {
float texel_size = get_lightmap_texel_size();
float _uv2_padding = get_uv2_padding() * texel_size;
- create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding);
+ create_mesh_array(p_arr, shape, radius, height, width, depth, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding);
}
-void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere, bool p_add_uv2, const float p_uv2_padding) {
+void SphereMesh::create_mesh_array(Array &p_arr, SphereMesh::Shape shape, float radius, float height, float width, float depth, int radial_segments, int rings, bool is_hemisphere, bool p_add_uv2, const float p_uv2_padding) {
int i, j, prevrow, thisrow, point;
float x, y, z;
- float scale = height * (is_hemisphere ? 1.0 : 0.5);
+ // Determine scales based on shape mode.
+ float scale_x, scale_y, scale_z;
+
+ switch (shape) {
+ case SPHERE:
+ scale_x = scale_y = scale_z = radius;
+ break;
+ case SPHEROID:
+ scale_x = radius;
+ scale_y = is_hemisphere ? height : height * 0.5;
+ scale_z = radius;
+ break;
+ case ELLIPSOID:
+ scale_x = width * 0.5;
+ scale_y = is_hemisphere ? height : height * 0.5;
+ scale_z = depth * 0.5;
+ break;
+ default:
+ scale_x = scale_y = scale_z = 1.0;
+ break;
+ }
// Only used if we calculate UV2
- float circumference = radius * Math_TAU;
+ float circumference = scale_x * Math_TAU;
float horizontal_length = circumference + p_uv2_padding;
float center_h = 0.5 * circumference / horizontal_length;
- float height_v = scale * Math_PI / ((scale * Math_PI) + p_uv2_padding);
+ float height_v = scale_y * Math_PI / ((scale_y * Math_PI) + p_uv2_padding);
// set our bounding box
@@ -1819,7 +1858,7 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int
v /= (rings + 1);
w = sin(Math_PI * v);
- y = scale * cos(Math_PI * v);
+ y = scale_y * cos(Math_PI * v);
for (i = 0; i <= radial_segments; i++) {
float u = i;
@@ -1829,12 +1868,18 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int
z = cos(u * Math_TAU);
if (is_hemisphere && y < 0.0) {
- points.push_back(Vector3(x * radius * w, 0.0, z * radius * w));
+ points.push_back(Vector3(x * scale_x * w, 0.0, z * scale_z * w));
normals.push_back(Vector3(0.0, -1.0, 0.0));
} else {
- Vector3 p = Vector3(x * radius * w, y, z * radius * w);
+ Vector3 p = Vector3(x * scale_x * w, y, z * scale_z * w);
points.push_back(p);
- Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale);
+
+ Vector3 normal;
+ if (shape == SPHERE || shape == SPHEROID) {
+ normal = Vector3(x * w * scale_x, radius * (y / scale_y), z * w * scale_z);
+ } else { // Ellipsoid.
+ normal = Vector3(x * w * scale_x, y / scale_y, z * w * scale_z);
+ }
normals.push_back(normal.normalized());
}
ADD_TANGENT(z, 0.0, -x, 1.0)
@@ -1871,10 +1916,17 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int
}
void SphereMesh::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_shape", "shape"), &SphereMesh::set_shape);
+ ClassDB::bind_method(D_METHOD("get_shape"), &SphereMesh::get_shape);
+
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereMesh::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &SphereMesh::get_radius);
ClassDB::bind_method(D_METHOD("set_height", "height"), &SphereMesh::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &SphereMesh::get_height);
+ ClassDB::bind_method(D_METHOD("set_width", "width"), &SphereMesh::set_width);
+ ClassDB::bind_method(D_METHOD("get_width"), &SphereMesh::get_width);
+ ClassDB::bind_method(D_METHOD("set_depth", "depth"), &SphereMesh::set_depth);
+ ClassDB::bind_method(D_METHOD("get_depth"), &SphereMesh::get_depth);
ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &SphereMesh::set_radial_segments);
ClassDB::bind_method(D_METHOD("get_radial_segments"), &SphereMesh::get_radial_segments);
@@ -1884,11 +1936,46 @@ void SphereMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere);
ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Sphere,Spheroid,Ellipsoid"), "set_shape", "get_shape");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_width", "get_width");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_depth", "get_depth");
ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere");
+
+ BIND_ENUM_CONSTANT(SPHERE);
+ BIND_ENUM_CONSTANT(SPHEROID);
+ BIND_ENUM_CONSTANT(ELLIPSOID);
+}
+
+void SphereMesh::_validate_property(PropertyInfo &p_property) const {
+ if (shape == SPHERE) {
+ if (p_property.name == "height") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ }
+ if (shape != ELLIPSOID) {
+ if (p_property.name == "width" || p_property.name == "depth") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ } else {
+ if (p_property.name == "radius") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ }
+}
+
+void SphereMesh::set_shape(SphereMesh::Shape p_shape_mode) {
+ shape = p_shape_mode;
+ _update_lightmap_size();
+ request_update();
+ notify_property_list_changed();
+}
+
+SphereMesh::Shape SphereMesh::get_shape() const {
+ return shape;
}
void SphereMesh::set_radius(const float p_radius) {
@@ -1910,6 +1997,24 @@ void SphereMesh::set_height(const float p_height) {
float SphereMesh::get_height() const {
return height;
}
+void SphereMesh::set_width(const float p_width) {
+ width = p_width;
+ _update_lightmap_size();
+ request_update();
+}
+
+float SphereMesh::get_width() const {
+ return width;
+}
+void SphereMesh::set_depth(const float p_depth) {
+ depth = p_depth;
+ _update_lightmap_size();
+ request_update();
+}
+
+float SphereMesh::get_depth() const {
+ return depth;
+}
void SphereMesh::set_radial_segments(const int p_radial_segments) {
radial_segments = p_radial_segments > 4 ? p_radial_segments : 4;
diff --git a/scene/resources/3d/primitive_meshes.h b/scene/resources/3d/primitive_meshes.h
index 4d2d0760b36..940253613d7 100644
--- a/scene/resources/3d/primitive_meshes.h
+++ b/scene/resources/3d/primitive_meshes.h
@@ -341,27 +341,47 @@ public:
class SphereMesh : public PrimitiveMesh {
GDCLASS(SphereMesh, PrimitiveMesh);
+public:
+ enum Shape {
+ SPHERE = 0,
+ SPHEROID = 1,
+ ELLIPSOID = 2,
+ };
+
private:
+ SphereMesh::Shape shape = SPHEROID;
float radius = 0.5;
float height = 1.0;
+ float width = 1.0;
+ float depth = 1.0;
int radial_segments = 64;
int rings = 32;
bool is_hemisphere = false;
protected:
static void _bind_methods();
+
+ void _validate_property(PropertyInfo &p_property) const;
+
virtual void _create_mesh_array(Array &p_arr) const override;
virtual void _update_lightmap_size() override;
public:
- static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0);
+ static void create_mesh_array(Array &p_arr, SphereMesh::Shape shape, float radius, float height, float width, float depth, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0);
+
+ void set_shape(SphereMesh::Shape p_shape_mode);
+ SphereMesh::Shape get_shape() const;
void set_radius(const float p_radius);
float get_radius() const;
void set_height(const float p_height);
float get_height() const;
+ void set_width(float p_width);
+ float get_width() const;
+ void set_depth(float p_depth);
+ float get_depth() const;
void set_radial_segments(const int p_radial_segments);
int get_radial_segments() const;
@@ -374,6 +394,7 @@ public:
SphereMesh();
};
+VARIANT_ENUM_CAST(SphereMesh::Shape);
/**
Big donut
diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h
index f105e1ac04f..cc8dc9ae226 100644
--- a/tests/scene/test_primitives.h
+++ b/tests/scene/test_primitives.h
@@ -546,7 +546,7 @@ TEST_CASE("[SceneTree][Primitive][Sphere] Sphere Primitive") {
real_t radius = 1.1f;
int radial_segments = 8;
int rings = 5;
- SphereMesh::create_mesh_array(data, radius, 2 * radius, radial_segments, rings);
+ SphereMesh::create_mesh_array(data, SphereMesh::SPHEROID, radius, 2 * radius, 0, 0, radial_segments, rings);
Vector points = data[RS::ARRAY_VERTEX];
Vector normals = data[RS::ARRAY_NORMAL];