From dd7bbcc837ed1148bc40021deab5215579c8c6f4 Mon Sep 17 00:00:00 2001 From: Juan Pablo Arce Date: Wed, 17 Jul 2024 14:16:51 -0300 Subject: [PATCH] Add flag to enable use of accurate path tangents for polygon rotation in `CSGPolygon3D` The new property is called `path_rotation_accurate`. --- modules/csg/csg_shape.cpp | 84 +++++++++++------ modules/csg/csg_shape.h | 4 + modules/csg/doc_classes/CSGPolygon3D.xml | 3 + modules/csg/tests/test_csg.h | 114 +++++++++++++++++++++++ 4 files changed, 179 insertions(+), 26 deletions(-) create mode 100644 modules/csg/tests/test_csg.h diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 3ded5f73fd7..b9f33dc7571 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -2234,24 +2234,35 @@ CSGBrush *CSGPolygon3D::_build_brush() { base_xform = path->get_global_transform(); } - Vector3 current_point = curve->sample_baked(0); - Vector3 next_point = curve->sample_baked(extrusion_step); + Vector3 current_point; Vector3 current_up = Vector3(0, 1, 0); - Vector3 direction = next_point - current_point; - - if (path_joined) { - Vector3 last_point = curve->sample_baked(curve->get_baked_length()); - direction = next_point - last_point; - } + Vector3 direction; switch (path_rotation) { case PATH_ROTATION_POLYGON: + current_point = curve->sample_baked(0); direction = Vector3(0, 0, -1); break; case PATH_ROTATION_PATH: - break; case PATH_ROTATION_PATH_FOLLOW: - current_up = curve->sample_baked_up_vector(0, true); + if (!path_rotation_accurate) { + current_point = curve->sample_baked(0); + Vector3 next_point = curve->sample_baked(extrusion_step); + direction = next_point - current_point; + + if (path_joined) { + Vector3 last_point = curve->sample_baked(curve->get_baked_length()); + direction = next_point - last_point; + } + } else { + Transform3D current_sample_xform = curve->sample_baked_with_rotation(0); + current_point = current_sample_xform.get_origin(); + direction = current_sample_xform.get_basis().xform(Vector3(0, 0, -1)); + } + + if (path_rotation == PATH_ROTATION_PATH_FOLLOW) { + current_up = curve->sample_baked_up_vector(0, true); + } break; } @@ -2307,32 +2318,26 @@ CSGBrush *CSGPolygon3D::_build_brush() { case MODE_PATH: { double previous_offset = x0 * extrusion_step; double current_offset = (x0 + 1) * extrusion_step; - double next_offset = (x0 + 2) * extrusion_step; - if (x0 == extrusions - 1) { - if (path_joined) { - current_offset = 0; - next_offset = extrusion_step; - } else { - next_offset = current_offset; - } + if (path_joined && x0 == extrusions - 1) { + current_offset = 0; } Vector3 previous_point = curve->sample_baked(previous_offset); - Vector3 current_point = curve->sample_baked(current_offset); - Vector3 next_point = curve->sample_baked(next_offset); + Transform3D current_sample_xform = curve->sample_baked_with_rotation(current_offset); + Vector3 current_point = current_sample_xform.get_origin(); Vector3 current_up = Vector3(0, 1, 0); - Vector3 direction = next_point - previous_point; - Vector3 current_dir = (current_point - previous_point).normalized(); + Vector3 current_extrusion_dir = (current_point - previous_point).normalized(); + Vector3 direction; // If the angles are similar, remove the previous face and replace it with this one. - if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_dir) > angle_simplify_dot) { + if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_extrusion_dir) > angle_simplify_dot) { faces_combined += 1; previous_xform = previous_previous_xform; face -= extrusion_face_count; faces_removed += extrusion_face_count; } else { faces_combined = 0; - previous_simplify_dir = current_dir; + previous_simplify_dir = current_extrusion_dir; } switch (path_rotation) { @@ -2340,9 +2345,21 @@ CSGBrush *CSGPolygon3D::_build_brush() { direction = Vector3(0, 0, -1); break; case PATH_ROTATION_PATH: - break; case PATH_ROTATION_PATH_FOLLOW: - current_up = curve->sample_baked_up_vector(current_offset, true); + if (!path_rotation_accurate) { + double next_offset = (x0 + 2) * extrusion_step; + if (x0 == extrusions - 1) { + next_offset = path_joined ? extrusion_step : current_offset; + } + Vector3 next_point = curve->sample_baked(next_offset); + direction = next_point - previous_point; + } else { + direction = current_sample_xform.get_basis().xform(Vector3(0, 0, -1)); + } + + if (path_rotation == PATH_ROTATION_PATH_FOLLOW) { + current_up = curve->sample_baked_up_vector(current_offset, true); + } break; } @@ -2512,6 +2529,9 @@ void CSGPolygon3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_rotation", "path_rotation"), &CSGPolygon3D::set_path_rotation); ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon3D::get_path_rotation); + ClassDB::bind_method(D_METHOD("set_path_rotation_accurate", "enable"), &CSGPolygon3D::set_path_rotation_accurate); + ClassDB::bind_method(D_METHOD("get_path_rotation_accurate"), &CSGPolygon3D::get_path_rotation_accurate); + ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon3D::set_path_local); ClassDB::bind_method(D_METHOD("is_path_local"), &CSGPolygon3D::is_path_local); @@ -2543,6 +2563,7 @@ void CSGPolygon3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.01,1.0,0.01,exp,or_greater"), "set_path_interval", "get_path_interval"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_simplify_angle", PROPERTY_HINT_RANGE, "0.0,180.0,0.1"), "set_path_simplify_angle", "get_path_simplify_angle"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_rotation_accurate"), "set_path_rotation_accurate", "get_path_rotation_accurate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_u_distance", PROPERTY_HINT_RANGE, "0.0,10.0,0.01,or_greater,suffix:m"), "set_path_u_distance", "get_path_u_distance"); @@ -2685,6 +2706,16 @@ CSGPolygon3D::PathRotation CSGPolygon3D::get_path_rotation() const { return path_rotation; } +void CSGPolygon3D::set_path_rotation_accurate(bool p_enabled) { + path_rotation_accurate = p_enabled; + _make_dirty(); + update_gizmos(); +} + +bool CSGPolygon3D::get_path_rotation_accurate() const { + return path_rotation_accurate; +} + void CSGPolygon3D::set_path_local(bool p_enable) { path_local = p_enable; _make_dirty(); @@ -2746,6 +2777,7 @@ CSGPolygon3D::CSGPolygon3D() { path_interval = 1.0; path_simplify_angle = 0.0; path_rotation = PATH_ROTATION_PATH_FOLLOW; + path_rotation_accurate = false; path_local = false; path_continuous_u = true; path_u_distance = 1.0; diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 0f6ebb2f1a9..39b42afc085 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -401,6 +401,7 @@ private: float path_interval; float path_simplify_angle; PathRotation path_rotation; + bool path_rotation_accurate; bool path_local; Path3D *path = nullptr; @@ -452,6 +453,9 @@ public: void set_path_rotation(PathRotation p_rotation); PathRotation get_path_rotation() const; + void set_path_rotation_accurate(bool p_enable); + bool get_path_rotation_accurate() const; + void set_path_local(bool p_enable); bool is_path_local() const; diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index 5d35c04e255..2f26f13aa1a 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -41,6 +41,9 @@ When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded. + + When [member mode] is [constant MODE_PATH], if [code]true[/code] the polygon will be rotated according to the proper tangent of the path at the sampled points. If [code]false[/code] an approximation is used, which decreases in accuracy as the number of subdivisions decreases. + When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count. diff --git a/modules/csg/tests/test_csg.h b/modules/csg/tests/test_csg.h new file mode 100644 index 00000000000..b93714a5ab1 --- /dev/null +++ b/modules/csg/tests/test_csg.h @@ -0,0 +1,114 @@ +/**************************************************************************/ +/* test_csg.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 TEST_CSG_H +#define TEST_CSG_H + +#include "../csg.h" +#include "../csg_shape.h" + +#include "tests/test_macros.h" + +namespace TestCSG { + +TEST_CASE("[SceneTree][CSG] CSGPolygon3D") { + SUBCASE("[SceneTree][CSG] CSGPolygon3D: using accurate path tangent for polygon rotation") { + const float polygon_radius = 10.0f; + + const Vector3 expected_min_bounds = Vector3(-polygon_radius, -polygon_radius, 0); + const Vector3 expected_max_bounds = Vector3(100 + polygon_radius, polygon_radius, 100); + const AABB expected_aabb = AABB(expected_min_bounds, expected_max_bounds - expected_min_bounds); + + Ref curve; + curve.instantiate(); + curve->add_point( + // p_position + Vector3(0, 0, 0), + // p_in + Vector3(), + // p_out + Vector3(0, 0, 60)); + curve->add_point( + // p_position + Vector3(100, 0, 100), + // p_in + Vector3(0, 0, -60), + // p_out + Vector3()); + + Path3D *path = memnew(Path3D); + path->set_curve(curve); + + CSGPolygon3D *csg_polygon_3d = memnew(CSGPolygon3D); + SceneTree::get_singleton()->get_root()->add_child(csg_polygon_3d); + + csg_polygon_3d->add_child(path); + csg_polygon_3d->set_path_node(csg_polygon_3d->get_path_to(path)); + csg_polygon_3d->set_mode(CSGPolygon3D::Mode::MODE_PATH); + + PackedVector2Array polygon; + polygon.append(Vector2(-polygon_radius, 0)); + polygon.append(Vector2(0, polygon_radius)); + polygon.append(Vector2(polygon_radius, 0)); + polygon.append(Vector2(0, -polygon_radius)); + csg_polygon_3d->set_polygon(polygon); + + csg_polygon_3d->set_path_rotation(CSGPolygon3D::PathRotation::PATH_ROTATION_PATH); + csg_polygon_3d->set_path_rotation_accurate(true); + + // Minimize the number of extrusions. + // This decreases the number of samples taken from the curve. + // Having fewer samples increases the inaccuracy of the line between samples as an approximation of the tangent of the curve. + // With correct polygon orientation, the bounding box for the given curve should be independent of the number of extrusions. + csg_polygon_3d->set_path_interval_type(CSGPolygon3D::PathIntervalType::PATH_INTERVAL_DISTANCE); + csg_polygon_3d->set_path_interval(1000.0f); + + // Call get_brush_faces to force the bounding box to update. + csg_polygon_3d->get_brush_faces(); + + CHECK(csg_polygon_3d->get_aabb().is_equal_approx(expected_aabb)); + + // Perform the bounding box check again with a greater number of extrusions. + csg_polygon_3d->set_path_interval(1.0f); + csg_polygon_3d->get_brush_faces(); + + CHECK(csg_polygon_3d->get_aabb().is_equal_approx(expected_aabb)); + + csg_polygon_3d->remove_child(path); + SceneTree::get_singleton()->get_root()->remove_child(csg_polygon_3d); + + memdelete(csg_polygon_3d); + memdelete(path); + } +} + +} // namespace TestCSG + +#endif // TEST_CSG_H