Merge pull request #86195 from GreenCrowDev/curve3d_close

Add `closed` property to `Curve3D`
This commit is contained in:
Thaddeus Crews 2024-11-18 09:23:38 -06:00
commit 8e324c4589
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
5 changed files with 199 additions and 45 deletions

View File

@ -204,6 +204,9 @@
<member name="bake_interval" type="float" setter="set_bake_interval" getter="get_bake_interval" default="0.2"> <member name="bake_interval" type="float" setter="set_bake_interval" getter="get_bake_interval" default="0.2">
The distance in meters between two adjacent cached points. Changing it forces the cache to be recomputed the next time the [method get_baked_points] or [method get_baked_length] function is called. The smaller the distance, the more points in the cache and the more memory it will consume, so use with care. The distance in meters between two adjacent cached points. Changing it forces the cache to be recomputed the next time the [method get_baked_points] or [method get_baked_length] function is called. The smaller the distance, the more points in the cache and the more memory it will consume, so use with care.
</member> </member>
<member name="closed" type="bool" setter="set_closed" getter="is_closed" default="false">
If [code]true[/code], and the curve has more than 2 control points, the last point and the first one will be connected in a loop.
</member>
<member name="point_count" type="int" setter="set_point_count" getter="get_point_count" default="0"> <member name="point_count" type="int" setter="set_point_count" getter="get_point_count" default="0">
The number of points describing the curve. The number of points describing the curve.
</member> </member>

View File

@ -277,8 +277,15 @@ void Path3DGizmo::redraw() {
Ref<StandardMaterial3D> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this); Ref<StandardMaterial3D> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this);
Ref<StandardMaterial3D> path_tilt_muted_material = gizmo_plugin->get_material("path_tilt_muted_material", this); Ref<StandardMaterial3D> path_tilt_muted_material = gizmo_plugin->get_material("path_tilt_muted_material", this);
Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles"); Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles");
Ref<StandardMaterial3D> first_pt_handle_material = gizmo_plugin->get_material("first_pt_handle");
Ref<StandardMaterial3D> last_pt_handle_material = gizmo_plugin->get_material("last_pt_handle");
Ref<StandardMaterial3D> closed_pt_handle_material = gizmo_plugin->get_material("closed_pt_handle");
Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles"); Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles");
first_pt_handle_material->set_albedo(Color(0.2, 1.0, 0.0));
last_pt_handle_material->set_albedo(Color(1.0, 0.2, 0.0));
closed_pt_handle_material->set_albedo(Color(1.0, 0.8, 0.0));
Ref<Curve3D> c = path->get_curve(); Ref<Curve3D> c = path->get_curve();
if (c.is_null()) { if (c.is_null()) {
return; return;
@ -369,7 +376,7 @@ void Path3DGizmo::redraw() {
info.point_idx = idx; info.point_idx = idx;
// Collect in-handles except for the first point. // Collect in-handles except for the first point.
if (idx > 0 && Path3DEditorPlugin::singleton->curve_edit_curve->is_pressed()) { if (idx > (c->is_closed() ? -1 : 0) && Path3DEditorPlugin::singleton->curve_edit_curve->is_pressed()) {
const Vector3 in = c->get_point_in(idx); const Vector3 in = c->get_point_in(idx);
info.type = HandleType::HANDLE_TYPE_IN; info.type = HandleType::HANDLE_TYPE_IN;
@ -383,7 +390,7 @@ void Path3DGizmo::redraw() {
} }
// Collect out-handles except for the last point. // Collect out-handles except for the last point.
if (idx < c->get_point_count() - 1 && Path3DEditorPlugin::singleton->curve_edit_curve->is_pressed()) { if (idx < (c->is_closed() ? c->get_point_count() : c->get_point_count() - 1) && Path3DEditorPlugin::singleton->curve_edit_curve->is_pressed()) {
const Vector3 out = c->get_point_out(idx); const Vector3 out = c->get_point_out(idx);
info.type = HandleType::HANDLE_TYPE_OUT; info.type = HandleType::HANDLE_TYPE_OUT;
@ -441,7 +448,42 @@ void Path3DGizmo::redraw() {
} }
if (!Path3DEditorPlugin::singleton->curve_edit->is_pressed() && primary_handle_points.size()) { if (!Path3DEditorPlugin::singleton->curve_edit->is_pressed() && primary_handle_points.size()) {
add_handles(primary_handle_points, handles_material); // Need to define indices separately.
// Point count.
const int pc = primary_handle_points.size();
Vector<int> idx;
idx.resize(pc);
int *idx_ptr = idx.ptrw();
for (int j = 0; j < pc; j++) {
idx_ptr[j] = j;
}
// Initialize arrays for first point.
PackedVector3Array first_pt_handle_point;
Vector<int> first_pt_id;
first_pt_handle_point.append(primary_handle_points[0]);
first_pt_id.append(idx[0]);
// Initialize arrays and add handle for last point if needed.
if (pc > 1) {
PackedVector3Array last_pt_handle_point;
Vector<int> last_pt_id;
last_pt_handle_point.append(primary_handle_points[pc - 1]);
last_pt_id.append(idx[pc - 1]);
primary_handle_points.remove_at(pc - 1);
idx.remove_at(pc - 1);
add_handles(last_pt_handle_point, c->is_closed() ? handles_material : last_pt_handle_material, last_pt_id);
}
// Add handle for first point.
primary_handle_points.remove_at(0);
idx.remove_at(0);
add_handles(first_pt_handle_point, c->is_closed() ? closed_pt_handle_material : first_pt_handle_material, first_pt_id);
// Add handles for remaining intermediate points.
if (!primary_handle_points.is_empty()) {
add_handles(primary_handle_points, handles_material, idx);
}
} }
if (secondary_handle_points.size()) { if (secondary_handle_points.size()) {
add_handles(secondary_handle_points, sec_handles_material, collected_secondary_handle_ids, false, true); add_handles(secondary_handle_points, sec_handles_material, collected_secondary_handle_ids, false, true);
@ -469,7 +511,7 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path, float p_disk_size) {
Path3DEditorPlugin::singleton->curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw)); Path3DEditorPlugin::singleton->curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw)); Path3DEditorPlugin::singleton->curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw)); Path3DEditorPlugin::singleton->curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw)); Path3DEditorPlugin::singleton->curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DGizmo::redraw));
} }
EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
@ -696,7 +738,7 @@ void Path3DEditorPlugin::_mode_changed(int p_mode) {
Node3DEditor::get_singleton()->clear_subgizmo_selection(); Node3DEditor::get_singleton()->clear_subgizmo_selection();
} }
void Path3DEditorPlugin::_close_curve() { void Path3DEditorPlugin::_toggle_closed_curve() {
Ref<Curve3D> c = path->get_curve(); Ref<Curve3D> c = path->get_curve();
if (c.is_null()) { if (c.is_null()) {
return; return;
@ -704,13 +746,10 @@ void Path3DEditorPlugin::_close_curve() {
if (c->get_point_count() < 2) { if (c->get_point_count() < 2) {
return; return;
} }
if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) {
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Close Curve")); ur->create_action(TTR("Toggle Open/Closed Curve"));
ur->add_do_method(c.ptr(), "add_point", c->get_point_position(0), c->get_point_in(0), c->get_point_out(0), -1); ur->add_do_method(c.ptr(), "set_closed", !c.ptr()->is_closed());
ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); ur->add_undo_method(c.ptr(), "set_closed", c.ptr()->is_closed());
ur->commit_action(); ur->commit_action();
} }
@ -771,6 +810,7 @@ void Path3DEditorPlugin::_clear_curve_points() {
return; return;
} }
Ref<Curve3D> curve = path->get_curve(); Ref<Curve3D> curve = path->get_curve();
curve->set_closed(false);
curve->clear_points(); curve->clear_points();
} }
@ -795,7 +835,7 @@ void Path3DEditorPlugin::_update_theme() {
curve_edit_tilt->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveTilt"))); curve_edit_tilt->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveTilt")));
curve_create->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCreate"))); curve_create->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCreate")));
curve_del->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete"))); curve_del->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete")));
curve_close->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); curve_closed->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose")));
curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); curve_clear_points->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear")));
create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); create_curve_button->set_button_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D")));
} }
@ -872,12 +912,12 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
toolbar->add_child(curve_del); toolbar->add_child(curve_del);
curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE)); curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE));
curve_close = memnew(Button); curve_closed = memnew(Button);
curve_close->set_theme_type_variation("FlatButton"); curve_closed->set_theme_type_variation("FlatButton");
curve_close->set_focus_mode(Control::FOCUS_NONE); curve_closed->set_focus_mode(Control::FOCUS_NONE);
curve_close->set_tooltip_text(TTR("Close Curve")); curve_closed->set_tooltip_text(TTR("Close Curve"));
toolbar->add_child(curve_close); toolbar->add_child(curve_closed);
curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_close_curve)); curve_closed->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_toggle_closed_curve));
curve_clear_points = memnew(Button); curve_clear_points = memnew(Button);
curve_clear_points->set_theme_type_variation("FlatButton"); curve_clear_points->set_theme_type_variation("FlatButton");
@ -943,6 +983,14 @@ void Path3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<Curve3D> curve = path->get_curve(); Ref<Curve3D> curve = path->get_curve();
Ref<StandardMaterial3D> handle_material = get_material("handles", p_gizmo); Ref<StandardMaterial3D> handle_material = get_material("handles", p_gizmo);
Ref<StandardMaterial3D> first_pt_handle_material = get_material("first_pt_handle", p_gizmo);
Ref<StandardMaterial3D> last_pt_handle_material = get_material("last_pt_handle", p_gizmo);
Ref<StandardMaterial3D> closed_pt_handle_material = get_material("closed_pt_handle", p_gizmo);
first_pt_handle_material->set_albedo(Color(0.2, 1.0, 0.0));
last_pt_handle_material->set_albedo(Color(1.0, 0.2, 0.0));
closed_pt_handle_material->set_albedo(Color(1.0, 0.8, 0.0));
PackedVector3Array handles; PackedVector3Array handles;
if (Path3DEditorPlugin::singleton->curve_edit->is_pressed()) { if (Path3DEditorPlugin::singleton->curve_edit->is_pressed()) {
@ -955,7 +1003,37 @@ void Path3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
} }
if (handles.size()) { if (handles.size()) {
p_gizmo->add_vertices(handles, handle_material, Mesh::PRIMITIVE_POINTS); // Point count.
const int pc = handles.size();
// Initialize arrays for first point.
PackedVector3Array first_pt;
first_pt.append(handles[0]);
// Initialize arrays and add handle for last point if needed.
if (pc > 1) {
PackedVector3Array last_pt;
last_pt.append(handles[handles.size() - 1]);
handles.remove_at(handles.size() - 1);
if (curve->is_closed()) {
p_gizmo->add_vertices(last_pt, handle_material, Mesh::PRIMITIVE_POINTS);
} else {
p_gizmo->add_vertices(last_pt, last_pt_handle_material, Mesh::PRIMITIVE_POINTS);
}
}
// Add handle for first point.
handles.remove_at(0);
if (curve->is_closed()) {
p_gizmo->add_vertices(first_pt, closed_pt_handle_material, Mesh::PRIMITIVE_POINTS);
} else {
p_gizmo->add_vertices(first_pt, first_pt_handle_material, Mesh::PRIMITIVE_POINTS);
}
// Add handles for remaining intermediate points.
if (!handles.is_empty()) {
p_gizmo->add_vertices(handles, handle_material, Mesh::PRIMITIVE_POINTS);
}
} }
} }
@ -1072,5 +1150,8 @@ Path3DGizmoPlugin::Path3DGizmoPlugin(float p_disk_size) {
create_material("path_tilt_material", path_tilt_color); create_material("path_tilt_material", path_tilt_color);
create_material("path_tilt_muted_material", path_tilt_color * 0.7); create_material("path_tilt_muted_material", path_tilt_color * 0.7);
create_handle_material("handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons))); create_handle_material("handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
create_handle_material("first_pt_handle", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
create_handle_material("last_pt_handle", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
create_handle_material("closed_pt_handle", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
create_handle_material("sec_handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorCurveHandle"), EditorStringName(EditorIcons))); create_handle_material("sec_handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorCurveHandle"), EditorStringName(EditorIcons)));
} }

View File

@ -120,7 +120,7 @@ class Path3DEditorPlugin : public EditorPlugin {
Button *curve_edit_curve = nullptr; Button *curve_edit_curve = nullptr;
Button *curve_edit_tilt = nullptr; Button *curve_edit_tilt = nullptr;
Button *curve_del = nullptr; Button *curve_del = nullptr;
Button *curve_close = nullptr; Button *curve_closed = nullptr;
Button *curve_clear_points = nullptr; Button *curve_clear_points = nullptr;
MenuButton *handle_menu = nullptr; MenuButton *handle_menu = nullptr;
@ -144,7 +144,7 @@ class Path3DEditorPlugin : public EditorPlugin {
void _update_toolbar(); void _update_toolbar();
void _mode_changed(int p_mode); void _mode_changed(int p_mode);
void _close_curve(); void _toggle_closed_curve();
void _handle_option_pressed(int p_option); void _handle_option_pressed(int p_option);
bool handle_clicked = false; bool handle_clicked = false;
bool mirror_handle_angle = true; bool mirror_handle_angle = true;

View File

@ -1463,6 +1463,9 @@ void Curve3D::_remove_point(int p_index) {
void Curve3D::remove_point(int p_index) { void Curve3D::remove_point(int p_index) {
_remove_point(p_index); _remove_point(p_index);
if (closed && points.size() < 2) {
set_closed(false);
}
notify_property_list_changed(); notify_property_list_changed();
} }
@ -1479,15 +1482,25 @@ Vector3 Curve3D::sample(int p_index, real_t p_offset) const {
ERR_FAIL_COND_V(pc == 0, Vector3()); ERR_FAIL_COND_V(pc == 0, Vector3());
if (p_index >= pc - 1) { if (p_index >= pc - 1) {
return points[pc - 1].position; if (!closed) {
return points[pc - 1].position;
} else {
p_index = pc - 1;
}
} else if (p_index < 0) { } else if (p_index < 0) {
return points[0].position; return points[0].position;
} }
Vector3 p0 = points[p_index].position; Vector3 p0 = points[p_index].position;
Vector3 p1 = p0 + points[p_index].out; Vector3 p1 = p0 + points[p_index].out;
Vector3 p3 = points[p_index + 1].position; Vector3 p3, p2;
Vector3 p2 = p3 + points[p_index + 1].in; if (!closed || p_index < pc - 1) {
p3 = points[p_index + 1].position;
p2 = p3 + points[p_index + 1].in;
} else {
p3 = points[0].position;
p2 = p3 + points[0].in;
}
return p0.bezier_interpolate(p1, p2, p3, p_offset); return p0.bezier_interpolate(p1, p2, p3, p_offset);
} }
@ -1605,13 +1618,16 @@ void Curve3D::_bake() const {
{ {
Vector<RBMap<real_t, Vector3>> midpoints = _tessellate_even_length(10, bake_interval); Vector<RBMap<real_t, Vector3>> midpoints = _tessellate_even_length(10, bake_interval);
const int num_intervals = closed ? points.size() : points.size() - 1;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
points_in_cache.resize(points.size()); points_in_cache.resize(closed ? (points.size() + 1) : points.size());
points_in_cache.set(0, 0); points_in_cache.set(0, 0);
#endif #endif
// Point Count: Begins at 1 to account for the last point.
int pc = 1; int pc = 1;
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
pc++; pc++;
pc += midpoints[i].size(); pc += midpoints[i].size();
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
@ -1634,18 +1650,29 @@ void Curve3D::_bake() const {
btw[0] = points[0].tilt; btw[0] = points[0].tilt;
int pidx = 0; int pidx = 0;
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
for (const KeyValue<real_t, Vector3> &E : midpoints[i]) { for (const KeyValue<real_t, Vector3> &E : midpoints[i]) {
pidx++; pidx++;
bpw[pidx] = E.value; bpw[pidx] = E.value;
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, E.key); if (!closed || i < num_intervals - 1) {
btw[pidx] = Math::lerp(points[i].tilt, points[i + 1].tilt, E.key); bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, E.key);
btw[pidx] = Math::lerp(points[i].tilt, points[i + 1].tilt, E.key);
} else {
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[0].position + points[0].in, points[0].position, E.key);
btw[pidx] = Math::lerp(points[i].tilt, points[0].tilt, E.key);
}
} }
pidx++; pidx++;
bpw[pidx] = points[i + 1].position; if (!closed || i < num_intervals - 1) {
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, 1.0); bpw[pidx] = points[i + 1].position;
btw[pidx] = points[i + 1].tilt; bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, 1.0);
btw[pidx] = points[i + 1].tilt;
} else {
bpw[pidx] = points[0].position;
bfw[pidx] = _calculate_tangent(points[i].position, points[i].position + points[i].out, points[0].position + points[0].in, points[0].position, 1.0);
btw[pidx] = points[0].tilt;
}
} }
// Recalculate the baked distances. // Recalculate the baked distances.
@ -2096,6 +2123,20 @@ real_t Curve3D::get_closest_offset(const Vector3 &p_to_point) const {
return nearest; return nearest;
} }
void Curve3D::set_closed(bool p_closed) {
if (closed == p_closed) {
return;
}
closed = p_closed;
mark_dirty();
notify_property_list_changed();
}
bool Curve3D::is_closed() const {
return closed;
}
void Curve3D::set_bake_interval(real_t p_tolerance) { void Curve3D::set_bake_interval(real_t p_tolerance) {
bake_interval = p_tolerance; bake_interval = p_tolerance;
mark_dirty(); mark_dirty();
@ -2174,11 +2215,17 @@ PackedVector3Array Curve3D::tessellate(int p_max_stages, real_t p_tolerance) con
} }
Vector<RBMap<real_t, Vector3>> midpoints; Vector<RBMap<real_t, Vector3>> midpoints;
midpoints.resize(points.size() - 1); const int num_intervals = closed ? points.size() : points.size() - 1;
midpoints.resize(num_intervals);
// Point Count: Begins at 1 to account for the last point.
int pc = 1; int pc = 1;
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
_bake_segment3d(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_tolerance); if (!closed || i < num_intervals - 1) {
_bake_segment3d(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_tolerance);
} else {
_bake_segment3d(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[0].position, points[0].in, 0, p_max_stages, p_tolerance);
}
pc++; pc++;
pc += midpoints[i].size(); pc += midpoints[i].size();
} }
@ -2188,14 +2235,18 @@ PackedVector3Array Curve3D::tessellate(int p_max_stages, real_t p_tolerance) con
bpw[0] = points[0].position; bpw[0] = points[0].position;
int pidx = 0; int pidx = 0;
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
for (const KeyValue<real_t, Vector3> &E : midpoints[i]) { for (const KeyValue<real_t, Vector3> &E : midpoints[i]) {
pidx++; pidx++;
bpw[pidx] = E.value; bpw[pidx] = E.value;
} }
pidx++; pidx++;
bpw[pidx] = points[i + 1].position; if (!closed || i < num_intervals - 1) {
bpw[pidx] = points[i + 1].position;
} else {
bpw[pidx] = points[0].position;
}
} }
return tess; return tess;
@ -2205,10 +2256,15 @@ Vector<RBMap<real_t, Vector3>> Curve3D::_tessellate_even_length(int p_max_stages
Vector<RBMap<real_t, Vector3>> midpoints; Vector<RBMap<real_t, Vector3>> midpoints;
ERR_FAIL_COND_V_MSG(points.size() < 2, midpoints, "Curve must have at least 2 control point"); ERR_FAIL_COND_V_MSG(points.size() < 2, midpoints, "Curve must have at least 2 control point");
midpoints.resize(points.size() - 1); const int num_intervals = closed ? points.size() : points.size() - 1;
midpoints.resize(num_intervals);
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
_bake_segment3d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_length); if (!closed || i < num_intervals - 1) {
_bake_segment3d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_length);
} else {
_bake_segment3d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[0].position, points[0].in, 0, p_max_stages, p_length);
}
} }
return midpoints; return midpoints;
} }
@ -2221,8 +2277,10 @@ PackedVector3Array Curve3D::tessellate_even_length(int p_max_stages, real_t p_le
return tess; return tess;
} }
const int num_intervals = closed ? points.size() : points.size() - 1;
// Point Count: Begins at 1 to account for the last point.
int pc = 1; int pc = 1;
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
pc++; pc++;
pc += midpoints[i].size(); pc += midpoints[i].size();
} }
@ -2232,14 +2290,18 @@ PackedVector3Array Curve3D::tessellate_even_length(int p_max_stages, real_t p_le
bpw[0] = points[0].position; bpw[0] = points[0].position;
int pidx = 0; int pidx = 0;
for (int i = 0; i < points.size() - 1; i++) { for (int i = 0; i < num_intervals; i++) {
for (const KeyValue<real_t, Vector3> &E : midpoints[i]) { for (const KeyValue<real_t, Vector3> &E : midpoints[i]) {
pidx++; pidx++;
bpw[pidx] = E.value; bpw[pidx] = E.value;
} }
pidx++; pidx++;
bpw[pidx] = points[i + 1].position; if (!closed || i < num_intervals - 1) {
bpw[pidx] = points[i + 1].position;
} else {
bpw[pidx] = points[0].position;
}
} }
return tess; return tess;
@ -2295,13 +2357,13 @@ void Curve3D::_get_property_list(List<PropertyInfo> *p_list) const {
pi.usage &= ~PROPERTY_USAGE_STORAGE; pi.usage &= ~PROPERTY_USAGE_STORAGE;
p_list->push_back(pi); p_list->push_back(pi);
if (i != 0) { if (closed || i != 0) {
pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/in", i)); pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/in", i));
pi.usage &= ~PROPERTY_USAGE_STORAGE; pi.usage &= ~PROPERTY_USAGE_STORAGE;
p_list->push_back(pi); p_list->push_back(pi);
} }
if (i != points.size() - 1) { if (closed || i != points.size() - 1) {
pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/out", i)); pi = PropertyInfo(Variant::VECTOR3, vformat("point_%d/out", i));
pi.usage &= ~PROPERTY_USAGE_STORAGE; pi.usage &= ~PROPERTY_USAGE_STORAGE;
p_list->push_back(pi); p_list->push_back(pi);
@ -2329,6 +2391,8 @@ void Curve3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_points"), &Curve3D::clear_points); ClassDB::bind_method(D_METHOD("clear_points"), &Curve3D::clear_points);
ClassDB::bind_method(D_METHOD("sample", "idx", "t"), &Curve3D::sample); ClassDB::bind_method(D_METHOD("sample", "idx", "t"), &Curve3D::sample);
ClassDB::bind_method(D_METHOD("samplef", "fofs"), &Curve3D::samplef); ClassDB::bind_method(D_METHOD("samplef", "fofs"), &Curve3D::samplef);
ClassDB::bind_method(D_METHOD("set_closed", "closed"), &Curve3D::set_closed);
ClassDB::bind_method(D_METHOD("is_closed"), &Curve3D::is_closed);
//ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve3D::bake,DEFVAL(10)); //ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve3D::bake,DEFVAL(10));
ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve3D::set_bake_interval); ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve3D::set_bake_interval);
ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve3D::get_bake_interval); ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve3D::get_bake_interval);
@ -2350,6 +2414,8 @@ void Curve3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_data"), &Curve3D::_get_data); ClassDB::bind_method(D_METHOD("_get_data"), &Curve3D::_get_data);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve3D::_set_data); ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve3D::_set_data);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed"), "set_closed", "is_closed");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_"); ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_");

View File

@ -264,6 +264,8 @@ class Curve3D : public Resource {
mutable Vector<size_t> points_in_cache; mutable Vector<size_t> points_in_cache;
#endif #endif
bool closed = false;
mutable bool baked_cache_dirty = false; mutable bool baked_cache_dirty = false;
mutable PackedVector3Array baked_point_cache; mutable PackedVector3Array baked_point_cache;
mutable Vector<real_t> baked_tilt_cache; mutable Vector<real_t> baked_tilt_cache;
@ -330,6 +332,8 @@ public:
Vector3 sample(int p_index, real_t p_offset) const; Vector3 sample(int p_index, real_t p_offset) const;
Vector3 samplef(real_t p_findex) const; Vector3 samplef(real_t p_findex) const;
void set_closed(bool p_closed);
bool is_closed() const;
void set_bake_interval(real_t p_tolerance); void set_bake_interval(real_t p_tolerance);
real_t get_bake_interval() const; real_t get_bake_interval() const;
void set_up_vector_enabled(bool p_enable); void set_up_vector_enabled(bool p_enable);