Add shape_mode to SphereMesh to switch between Sphere, Spheroid, and Ellipsoid

This commit is contained in:
Yahkub-R 2024-08-16 12:24:17 -04:00
parent 33c30b9e63
commit 38e5a30589
5 changed files with 165 additions and 16 deletions

View File

@ -9,12 +9,18 @@
<tutorials>
</tutorials>
<members>
<member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0">
Depth of sphere.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="1.0">
Full height of the sphere.
</member>
<member name="is_hemisphere" type="bool" setter="set_is_hemisphere" getter="get_is_hemisphere" default="false">
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.
</member>
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="64">
Number of radial segments on the sphere.
@ -25,5 +31,22 @@
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="32">
Number of segments along the height of the sphere.
</member>
<member name="shape" type="int" setter="set_shape" getter="get_shape" enum="SphereMesh.Shape" default="1">
Sets the shape mode to one of [enum SphereMesh.Shape].
</member>
<member name="width" type="float" setter="set_width" getter="get_width" default="1.0">
Width of sphere.
</member>
</members>
<constants>
<constant name="SPHERE" value="0" enum="Shape">
A regular sphere controlled only by radius.
</constant>
<constant name="SPHEROID" value="1" enum="Shape">
A sphere with adjustable height and radius.
</constant>
<constant name="ELLIPSOID" value="2" enum="Shape">
A sphere with adjustable height, width, and depth.
</constant>
</constants>
</class>

View File

@ -370,7 +370,7 @@ void NavMeshGenerator3D::generator_parse_staticbody3d_node(const Ref<NavigationM
if (sphere) {
Array arr;
arr.resize(RS::ARRAY_MAX);
SphereMesh::create_mesh_array(arr, sphere->get_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<NavigationMesh>
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: {

View File

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

View File

@ -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

View File

@ -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<Vector3> points = data[RS::ARRAY_VERTEX];
Vector<Vector3> normals = data[RS::ARRAY_NORMAL];