diff options
Diffstat (limited to 'scene/3d/path_3d.cpp')
-rw-r--r-- | scene/3d/path_3d.cpp | 287 |
1 files changed, 118 insertions, 169 deletions
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 25226ad384..04ac01a085 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* path_3d.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* path_3d.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ #include "path_3d.h" @@ -40,6 +40,7 @@ Path3D::Path3D() { } Path3D::~Path3D() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); if (debug_instance.is_valid()) { RS::get_singleton()->free(debug_instance); } @@ -182,125 +183,31 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { if (bl == 0.0) { return; } - real_t bi = c->get_bake_interval(); - real_t o_next = offset + bi; - real_t o_prev = offset - bi; - - if (loop) { - o_next = Math::fposmod(o_next, bl); - o_prev = Math::fposmod(o_prev, bl); - } else if (rotation_mode == ROTATION_ORIENTED) { - if (o_next >= bl) { - o_next = bl; - } - if (o_prev <= 0) { - o_prev = 0; - } - } - - Vector3 pos = c->interpolate_baked(offset, cubic); - Transform3D t = get_transform(); - // Vector3 pos_offset = Vector3(h_offset, v_offset, 0); not used in all cases - // will be replaced by "Vector3(h_offset, v_offset, 0)" where it was formerly used - - if (rotation_mode == ROTATION_ORIENTED) { - Vector3 forward = c->interpolate_baked(o_next, cubic) - pos; - - // Try with the previous position - if (forward.length_squared() < CMP_EPSILON2) { - forward = pos - c->interpolate_baked(o_prev, cubic); - } - - if (forward.length_squared() < CMP_EPSILON2) { - forward = Vector3(0, 0, 1); - } else { - forward.normalize(); - } - - Vector3 up = c->interpolate_baked_up_vector(offset, true); - if (o_next < offset) { - Vector3 up1 = c->interpolate_baked_up_vector(o_next, true); - Vector3 axis = up.cross(up1); - - if (axis.length_squared() < CMP_EPSILON2) { - axis = forward; - } else { - axis.normalize(); - } - - up.rotate(axis, up.angle_to(up1) * 0.5f); - } - - Vector3 scale = t.basis.get_scale(); - Vector3 sideways = up.cross(forward).normalized(); - up = forward.cross(sideways).normalized(); - - t.basis.set_columns(sideways, up, forward); - t.basis.scale_local(scale); - - t.origin = pos + sideways * h_offset + up * v_offset; - } else if (rotation_mode != ROTATION_NONE) { - // perform parallel transport - // - // see C. Dougan, The Parallel Transport Frame, Game Programming Gems 2 for example - // for a discussion about why not Frenet frame. + Transform3D t; + if (rotation_mode == ROTATION_NONE) { + Vector3 pos = c->sample_baked(progress, cubic); t.origin = pos; - if (p_update_xyz_rot && prev_offset != offset) { // Only update rotation if some parameter has changed - i.e. not on addition to scene tree. - real_t sample_distance = bi * 0.01; - Vector3 t_prev_pos_a = c->interpolate_baked(prev_offset - sample_distance, cubic); - Vector3 t_prev_pos_b = c->interpolate_baked(prev_offset + sample_distance, cubic); - Vector3 t_cur_pos_a = c->interpolate_baked(offset - sample_distance, cubic); - Vector3 t_cur_pos_b = c->interpolate_baked(offset + sample_distance, cubic); - Vector3 t_prev = (t_prev_pos_a - t_prev_pos_b).normalized(); - Vector3 t_cur = (t_cur_pos_a - t_cur_pos_b).normalized(); - - Vector3 axis = t_prev.cross(t_cur); - real_t dot = t_prev.dot(t_cur); - real_t angle = Math::acos(CLAMP(dot, -1, 1)); - - if (likely(!Math::is_zero_approx(angle))) { - if (rotation_mode == ROTATION_Y) { - // assuming we're referring to global Y-axis. is this correct? - axis.x = 0; - axis.z = 0; - } else if (rotation_mode == ROTATION_XY) { - axis.z = 0; - } else if (rotation_mode == ROTATION_XYZ) { - // all components are allowed - } + } else { + t = c->sample_baked_with_rotation(progress, cubic, false); + Vector3 forward = t.basis.get_column(2); // Retain tangent for applying tilt + t = PathFollow3D::correct_posture(t, rotation_mode); - if (likely(!Math::is_zero_approx(axis.length()))) { - t.rotate_basis(axis.normalized(), angle); - } - } + // Apply tilt *after* correct_posture + if (tilt_enabled) { + const real_t tilt = c->sample_baked_tilt(progress); - // do the additional tilting - real_t tilt_angle = c->interpolate_baked_tilt(offset); - Vector3 tilt_axis = t_cur; // not sure what tilt is supposed to do, is this correct?? - - if (likely(!Math::is_zero_approx(Math::abs(tilt_angle)))) { - if (rotation_mode == ROTATION_Y) { - tilt_axis.x = 0; - tilt_axis.z = 0; - } else if (rotation_mode == ROTATION_XY) { - tilt_axis.z = 0; - } else if (rotation_mode == ROTATION_XYZ) { - // all components are allowed - } - - if (likely(!Math::is_zero_approx(tilt_axis.length()))) { - t.rotate_basis(tilt_axis.normalized(), tilt_angle); - } - } + const Basis twist(forward, tilt); + t.basis = twist * t.basis; } - - t.translate_local(Vector3(h_offset, v_offset, 0)); - } else { - t.origin = pos + Vector3(h_offset, v_offset, 0); } + Vector3 scale = get_transform().basis.get_scale(); + + t.translate_local(Vector3(h_offset, v_offset, 0)); + t.basis.scale_local(scale); + set_transform(t); } @@ -330,27 +237,26 @@ bool PathFollow3D::get_cubic_interpolation() const { return cubic; } -void PathFollow3D::_validate_property(PropertyInfo &property) const { - if (property.name == "offset") { +void PathFollow3D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "offset") { real_t max = 10000; if (path && path->get_curve().is_valid()) { max = path->get_curve()->get_baked_length(); } - property.hint_string = "0," + rtos(max) + ",0.01,or_lesser,or_greater"; + p_property.hint_string = "0," + rtos(max) + ",0.01,or_less,or_greater"; } - Node3D::_validate_property(property); } -TypedArray<String> PathFollow3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray PathFollow3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path3D>(get_parent())) { warnings.push_back(RTR("PathFollow3D only works when set as a child of a Path3D node.")); } else { - Path3D *path = Object::cast_to<Path3D>(get_parent()); - if (path->get_curve().is_valid() && !path->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) { + Path3D *p = Object::cast_to<Path3D>(get_parent()); + if (p->get_curve().is_valid() && !p->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) { warnings.push_back(RTR("PathFollow3D's ROTATION_ORIENTED requires \"Up Vector\" to be enabled in its parent Path3D's Curve resource.")); } } @@ -359,9 +265,41 @@ TypedArray<String> PathFollow3D::get_configuration_warnings() const { return warnings; } +Transform3D PathFollow3D::correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode) { + Transform3D t = p_transform; + + // Modify frame according to rotation mode. + if (p_rotation_mode == PathFollow3D::ROTATION_NONE) { + // Clear rotation. + t.basis = Basis(); + } else if (p_rotation_mode == PathFollow3D::ROTATION_ORIENTED) { + // Y-axis always straight up. + Vector3 up(0.0, 1.0, 0.0); + Vector3 forward = t.basis.get_column(2); + + t.basis = Basis::looking_at(-forward, up); + } else { + // Lock some euler axes. + Vector3 euler = t.basis.get_euler_normalized(EulerOrder::YXZ); + if (p_rotation_mode == PathFollow3D::ROTATION_Y) { + // Only Y-axis allowed. + euler[0] = 0; + euler[2] = 0; + } else if (p_rotation_mode == PathFollow3D::ROTATION_XY) { + // XY allowed. + euler[2] = 0; + } + + Basis locked = Basis::from_euler(euler, EulerOrder::YXZ); + t.basis = locked; + } + + return t; +} + void PathFollow3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_offset", "offset"), &PathFollow3D::set_offset); - ClassDB::bind_method(D_METHOD("get_offset"), &PathFollow3D::get_offset); + ClassDB::bind_method(D_METHOD("set_progress", "progress"), &PathFollow3D::set_progress); + ClassDB::bind_method(D_METHOD("get_progress"), &PathFollow3D::get_progress); ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &PathFollow3D::set_h_offset); ClassDB::bind_method(D_METHOD("get_h_offset"), &PathFollow3D::get_h_offset); @@ -369,8 +307,8 @@ void PathFollow3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &PathFollow3D::set_v_offset); ClassDB::bind_method(D_METHOD("get_v_offset"), &PathFollow3D::get_v_offset); - ClassDB::bind_method(D_METHOD("set_unit_offset", "unit_offset"), &PathFollow3D::set_unit_offset); - ClassDB::bind_method(D_METHOD("get_unit_offset"), &PathFollow3D::get_unit_offset); + ClassDB::bind_method(D_METHOD("set_progress_ratio", "ratio"), &PathFollow3D::set_progress_ratio); + ClassDB::bind_method(D_METHOD("get_progress_ratio"), &PathFollow3D::get_progress_ratio); ClassDB::bind_method(D_METHOD("set_rotation_mode", "rotation_mode"), &PathFollow3D::set_rotation_mode); ClassDB::bind_method(D_METHOD("get_rotation_mode"), &PathFollow3D::get_rotation_mode); @@ -381,13 +319,19 @@ void PathFollow3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow3D::set_loop); ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow3D::has_loop); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser,or_greater,suffix:m"), "set_offset", "get_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); + ClassDB::bind_method(D_METHOD("set_tilt_enabled", "enabled"), &PathFollow3D::set_tilt_enabled); + ClassDB::bind_method(D_METHOD("is_tilt_enabled"), &PathFollow3D::is_tilt_enabled); + + ClassDB::bind_static_method("PathFollow3D", D_METHOD("correct_posture", "transform", "rotation_mode"), &PathFollow3D::correct_posture); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress", PROPERTY_HINT_RANGE, "0,10000,0.01,or_less,or_greater,suffix:m"), "set_progress", "get_progress"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001,or_less,or_greater", PROPERTY_USAGE_EDITOR), "set_progress_ratio", "get_progress_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_mode", PROPERTY_HINT_ENUM, "None,Y,XY,XYZ,Oriented"), "set_rotation_mode", "get_rotation_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tilt_enabled"), "set_tilt_enabled", "is_tilt_enabled"); BIND_ENUM_CONSTANT(ROTATION_NONE); BIND_ENUM_CONSTANT(ROTATION_Y); @@ -396,22 +340,19 @@ void PathFollow3D::_bind_methods() { BIND_ENUM_CONSTANT(ROTATION_ORIENTED); } -void PathFollow3D::set_offset(real_t p_offset) { - ERR_FAIL_COND(!isfinite(p_offset)); - prev_offset = offset; - offset = p_offset; +void PathFollow3D::set_progress(real_t p_progress) { + ERR_FAIL_COND(!isfinite(p_progress)); + progress = p_progress; if (path) { if (path->get_curve().is_valid()) { real_t path_length = path->get_curve()->get_baked_length(); if (loop && path_length) { - offset = Math::fposmod(offset, path_length); - if (!Math::is_zero_approx(p_offset) && Math::is_zero_approx(offset)) { - offset = path_length; + progress = Math::fposmod(progress, path_length); + if (!Math::is_zero_approx(p_progress) && Math::is_zero_approx(progress)) { + progress = path_length; } - } else { - offset = CLAMP(offset, 0, path_length); } } @@ -441,19 +382,19 @@ real_t PathFollow3D::get_v_offset() const { return v_offset; } -real_t PathFollow3D::get_offset() const { - return offset; +real_t PathFollow3D::get_progress() const { + return progress; } -void PathFollow3D::set_unit_offset(real_t p_unit_offset) { +void PathFollow3D::set_progress_ratio(real_t p_ratio) { if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { - set_offset(p_unit_offset * path->get_curve()->get_baked_length()); + set_progress(p_ratio * path->get_curve()->get_baked_length()); } } -real_t PathFollow3D::get_unit_offset() const { +real_t PathFollow3D::get_progress_ratio() const { if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { - return get_offset() / path->get_curve()->get_baked_length(); + return get_progress() / path->get_curve()->get_baked_length(); } else { return 0; } @@ -477,3 +418,11 @@ void PathFollow3D::set_loop(bool p_loop) { bool PathFollow3D::has_loop() const { return loop; } + +void PathFollow3D::set_tilt_enabled(bool p_enable) { + tilt_enabled = p_enable; +} + +bool PathFollow3D::is_tilt_enabled() const { + return tilt_enabled; +} |