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];