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

refactor rotation interpolation logic for PathFollower3D #64043

Closed
Closed
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
18 changes: 18 additions & 0 deletions doc/classes/Curve3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@
If [code]idx[/code] is out of bounds it is truncated to the first or last vertex, and [code]t[/code] is ignored. If the curve has no points, the function sends an error to the console, and returns [code](0, 0, 0)[/code].
</description>
</method>
<method name="interpolate_backed_with_rotation" qualifiers="const">
<return type="Transform3D" />
<argument index="0" name="offset" type="float" />
<argument index="1" name="cubic" type="bool" default="false" />
<argument index="2" name="loop" type="bool" default="true" />
<argument index="3" name="rotation_mode" type="int" enum="Curve3D.RotationMode" default="4" />
<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. However when [code]get_backed_length() == 0[/code] the return value is a blank, as there is no reasonable way to caculate the rotation.
[code]loop[/code] smooth trangent at the end of the curve, it only works when the begin and end of the curve meet. [code]cubic[/code] enable more accurate cubic interpolation.
[code]rotation_mode[/code] currently only support two modes: [code]ROTATION_NONE[/code] and [code]ROTATION_ORIENTED[/code].
</description>
</method>
<method name="interpolate_baked" qualifiers="const">
<return type="Vector3" />
<argument index="0" name="offset" type="float" />
Expand Down Expand Up @@ -195,4 +207,10 @@
If [code]true[/code], the curve will bake up vectors used for orientation. This is used when [member PathFollow3D.rotation_mode] is set to [constant PathFollow3D.ROTATION_ORIENTED]. Changing it forces the cache to be recomputed.
</member>
</members>
<constants>
<constant name="ROTATION_NONE" value="0" enum="RotationMode">
</constant>
<constant name="ROTATION_ORIENTED" value="4" enum="RotationMode">
</constant>
</constants>
</class>
70 changes: 11 additions & 59 deletions scene/3d/path_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,70 +182,24 @@ 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 (rotation_mode == ROTATION_ORIENTED || rotation_mode == ROTATION_NONE) {
Transform3D t = c->interpolate_backed_with_rotation(offset, cubic, loop, (Curve3D::RotationMode)rotation_mode);
Vector3 scale = get_transform().basis.get_scale();

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.translate_local(Vector3(h_offset, v_offset, 0));
t.basis.scale_local(scale);

t.origin = pos + sideways * h_offset + up * v_offset;
} else if (rotation_mode != ROTATION_NONE) {
set_transform(t);
} else {
// 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 = get_transform();
real_t bi = c->get_bake_interval();
Vector3 pos = c->interpolate_baked(offset, 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;
Expand Down Expand Up @@ -297,11 +251,9 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) {
}

t.translate_local(Vector3(h_offset, v_offset, 0));
} else {
t.origin = pos + Vector3(h_offset, v_offset, 0);
}

set_transform(t);
set_transform(t);
}
}

void PathFollow3D::_notification(int p_what) {
Expand Down
10 changes: 5 additions & 5 deletions scene/3d/path_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ class PathFollow3D : public Node3D {

public:
enum RotationMode {
ROTATION_NONE,
ROTATION_Y,
ROTATION_XY,
ROTATION_XYZ,
ROTATION_ORIENTED
ROTATION_NONE = Curve3D::ROTATION_NONE,
ROTATION_Y = Curve3D::ROTATION_Y,
ROTATION_XY = Curve3D::ROTATION_XY,
ROTATION_XYZ = Curve3D::ROTATION_XYZ,
ROTATION_ORIENTED = Curve3D::ROTATION_ORIENTED
};

private:
Expand Down
97 changes: 97 additions & 0 deletions scene/resources/curve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,96 @@ Vector3 Curve3D::interpolate_baked(real_t p_offset, bool p_cubic) const {
}
}

Transform3D Curve3D::interpolate_backed_with_rotation(real_t p_offset, bool p_cubic, bool p_loop, Curve3D::RotationMode p_rotation_mode) const {
real_t bl = get_baked_length();
ERR_FAIL_COND_V_MSG(bl == 0.0, Transform3D(), "length of Curve3D is 0.");

Vector3 pos = interpolate_baked(p_offset, p_cubic);

if (p_rotation_mode == ROTATION_NONE) {
Transform3D t;
t.origin = pos;
return t;
}

real_t bi = get_bake_interval();
real_t o_next = p_offset + bi;
real_t o_prev = p_offset - bi;

// Keep in sync with Curve2D. The two ends must meet for an looping curve.
if (p_loop && (o_next >= bl || o_prev <= 0.0)) {
// If our lookahead will loop, we need to check if the path is closed.
int point_count = get_point_count();
if (point_count > 0) {
Vector3 start_point = get_point_position(0);
Vector3 end_point = get_point_position(point_count - 1);
if (start_point == end_point) {
// Since the path is closed we want to 'smooth off'
// the corner at the start/end.
// So we wrap the lookahead back round.
o_next = Math::fposmod(o_next, bl);
o_prev = Math::fposmod(o_prev, bl);
}
}
} else if (p_rotation_mode == ROTATION_ORIENTED) {
if (o_next >= bl) {
o_next = bl;
}
if (o_prev <= 0) {
o_prev = 0;
}
}

// 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 (p_rotation_mode == ROTATION_ORIENTED) {
Vector3 forward = interpolate_baked(o_next, p_cubic) - pos;

// Try with the previous position
if (forward.length_squared() < CMP_EPSILON2) {
forward = pos - interpolate_baked(o_prev, p_cubic);
}

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

Vector3 up = interpolate_baked_up_vector(p_offset, true);

if (o_next < p_offset) {
Vector3 up1 = 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 sideways = up.cross(forward).normalized();
up = forward.cross(sideways).normalized();

Transform3D t;
t.basis.set_columns(sideways, up, forward);
t.origin = pos;
return t;
}

// TODO: current Parallel Transport Frame(PTF) code is left in PathFollower3D,
// as it needs to maintain state. Here on Curve3D, interpolation should be
// stateless.
// To implement stateless interpolation for other RoatationModels require
// moving the PTF code to the bake stage on the Curve3D, which requires
// a more substantial code refactor.
ERR_FAIL_V_MSG(Transform3D(), "RotationModel is not implemented yet");
}

real_t Curve3D::interpolate_baked_tilt(real_t p_offset) const {
if (baked_cache_dirty) {
_bake();
Expand Down Expand Up @@ -1993,6 +2083,7 @@ void Curve3D::_bind_methods() {

ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve3D::get_baked_length);
ClassDB::bind_method(D_METHOD("interpolate_baked", "offset", "cubic"), &Curve3D::interpolate_baked, DEFVAL(false));
ClassDB::bind_method(D_METHOD("interpolate_backed_with_rotation", "offset", "cubic", "loop", "rotation_mode"), &Curve3D::interpolate_backed_with_rotation, DEFVAL(false), DEFVAL(true), DEFVAL(ROTATION_ORIENTED));
ClassDB::bind_method(D_METHOD("interpolate_baked_up_vector", "offset", "apply_tilt"), &Curve3D::interpolate_baked_up_vector, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve3D::get_baked_points);
ClassDB::bind_method(D_METHOD("get_baked_tilts"), &Curve3D::get_baked_tilts);
Expand All @@ -2010,6 +2101,12 @@ void Curve3D::_bind_methods() {

ADD_GROUP("Up Vector", "up_vector_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled");

BIND_ENUM_CONSTANT(ROTATION_NONE);
//BIND_ENUM_CONSTANT(ROTATION_Y);
//BIND_ENUM_CONSTANT(ROTATION_XY);
//BIND_ENUM_CONSTANT(ROTATION_XYZ);
BIND_ENUM_CONSTANT(ROTATION_ORIENTED);
}

Curve3D::Curve3D() {}
11 changes: 11 additions & 0 deletions scene/resources/curve.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@ class Curve3D : public Resource {
static void _bind_methods();

public:
enum RotationMode {
ROTATION_NONE,
ROTATION_Y,
ROTATION_XY,
ROTATION_XYZ,
ROTATION_ORIENTED
};

int get_point_count() const;
void set_point_count(int p_count);
void add_point(const Vector3 &p_position, const Vector3 &p_in = Vector3(), const Vector3 &p_out = Vector3(), int p_atpos = -1);
Expand All @@ -295,6 +303,7 @@ class Curve3D : public Resource {

real_t get_baked_length() const;
Vector3 interpolate_baked(real_t p_offset, bool p_cubic = false) const;
Transform3D interpolate_backed_with_rotation(real_t p_offset, bool p_cubic = false, bool p_loop = true, Curve3D::RotationMode p_rotation_mode = ROTATION_ORIENTED) const;
real_t interpolate_baked_tilt(real_t p_offset) const;
Vector3 interpolate_baked_up_vector(real_t p_offset, bool p_apply_tilt = false) const;
PackedVector3Array get_baked_points() const; //useful for going through
Expand All @@ -308,4 +317,6 @@ class Curve3D : public Resource {
Curve3D();
};

VARIANT_ENUM_CAST(Curve3D::RotationMode);

#endif // CURVE_H