Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move rotation interpolation to Curve3d and refactor baking #64212

Merged
merged 1 commit into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/classes/Curve3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@
If the curve has no up vectors, the function sends an error to the console, and returns [code](0, 1, 0)[/code].
</description>
</method>
<method name="sample_baked_with_rotation" qualifiers="const">
<return type="Transform3D" />
<param index="0" name="offset" type="float" />
<param index="1" name="cubic" type="bool" default="false" />
<param index="2" name="apply_tilt" type="bool" default="false" />
<description>
Similar with [code]interpolate_baked()[/code]. The the return value is [code]Transform3D[/code], with [code]origin[/code] as point position, [code]basis.x[/code] as sideway vector, [code]basis.y[/code] as up vector, [code]basis.z[/code] as forward vector. When the curve length is 0, there is no reasonable way to caculate the rotation, all vectors aligned with global space axes.
</description>
</method>
<method name="samplef" qualifiers="const">
<return type="Vector3" />
<param index="0" name="fofs" type="float" />
Expand Down
13 changes: 13 additions & 0 deletions doc/classes/PathFollow3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
</description>
<tutorials>
</tutorials>
<methods>
<method name="correct_posture" qualifiers="static">
<return type="Transform3D" />
<param index="0" name="transform" type="Transform3D" />
<param index="1" name="rotation_mode" type="int" enum="PathFollow3D.RotationMode" />
<description>
Correct the [code]transform[/code]. [code]rotation_mode[/code] implicitly specifies how posture (forward, up and sideway direction) is caculated.
</description>
</method>
</methods>
<members>
<member name="cubic_interp" type="bool" setter="set_cubic_interpolation" getter="get_cubic_interpolation" default="true">
If [code]true[/code], the position between two cached points is interpolated cubically, and linearly otherwise.
Expand All @@ -30,6 +40,9 @@
<member name="rotation_mode" type="int" setter="set_rotation_mode" getter="get_rotation_mode" enum="PathFollow3D.RotationMode" default="3">
Allows or forbids rotation on one or more axes, depending on the [enum RotationMode] constants being used.
</member>
<member name="tilt_enabled" type="bool" setter="set_tilt_enabled" getter="is_tilt_enabled" default="true">
If [code]true[/code], the tilt property of [Curve3D] takes effect.
</member>
<member name="v_offset" type="float" setter="set_v_offset" getter="get_v_offset" default="0.0">
The node's offset perpendicular to the curve.
</member>
Expand Down
177 changes: 63 additions & 114 deletions scene/3d/path_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,125 +182,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 = progress + bi;
real_t o_prev = progress - 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->sample_baked(progress, 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->sample_baked(o_next, cubic) - pos;

// Try with the previous position
if (forward.length_squared() < CMP_EPSILON2) {
forward = pos - c->sample_baked(o_prev, cubic);
}

if (forward.length_squared() < CMP_EPSILON2) {
forward = Vector3(0, 0, 1);
} else {
forward.normalize();
}

Vector3 up = c->sample_baked_up_vector(progress, true);

if (o_next < progress) {
Vector3 up1 = c->sample_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 != progress) { // 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->sample_baked(prev_offset - sample_distance, cubic);
Vector3 t_prev_pos_b = c->sample_baked(prev_offset + sample_distance, cubic);
Vector3 t_cur_pos_a = c->sample_baked(progress - sample_distance, cubic);
Vector3 t_cur_pos_b = c->sample_baked(progress + 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->sample_baked_tilt(progress);
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);
}

Expand Down Expand Up @@ -358,6 +264,38 @@ PackedStringArray 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_progress", "progress"), &PathFollow3D::set_progress);
ClassDB::bind_method(D_METHOD("get_progress"), &PathFollow3D::get_progress);
Expand All @@ -380,13 +318,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);

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);
Expand All @@ -397,7 +341,6 @@ void PathFollow3D::_bind_methods() {

void PathFollow3D::set_progress(real_t p_progress) {
ERR_FAIL_COND(!isfinite(p_progress));
prev_offset = progress;
progress = p_progress;

if (path) {
Expand All @@ -409,8 +352,6 @@ void PathFollow3D::set_progress(real_t p_progress) {
if (!Math::is_zero_approx(p_progress) && Math::is_zero_approx(progress)) {
progress = path_length;
}
} else {
progress = CLAMP(progress, 0, path_length);
}
}

Expand Down Expand Up @@ -476,3 +417,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;
}
7 changes: 6 additions & 1 deletion scene/3d/path_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,16 @@ class PathFollow3D : public Node3D {
ROTATION_ORIENTED
};

static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);

private:
Path3D *path = nullptr;
real_t prev_offset = 0.0; // Offset during the last _update_transform.
real_t progress = 0.0;
real_t h_offset = 0.0;
real_t v_offset = 0.0;
bool cubic = true;
bool loop = true;
bool tilt_enabled = true;
RotationMode rotation_mode = ROTATION_XYZ;

void _update_transform(bool p_update_xyz_rot = true);
Expand All @@ -106,6 +108,9 @@ class PathFollow3D : public Node3D {
void set_loop(bool p_loop);
bool has_loop() const;

void set_tilt_enabled(bool p_enable);
bool is_tilt_enabled() const;

void set_rotation_mode(RotationMode p_rotation_mode);
RotationMode get_rotation_mode() const;

Expand Down
Loading