Skip to content

Commit

Permalink
refactor rotation interpolation logic for PathFollower3D
Browse files Browse the repository at this point in the history
Move logic for `ROTAION_NONE` and `ROTATION_ORIENTED` from PathFollower3D
to Curve3D. There is *no* functionality change.

To move other modes require more substantial refactor, as
the current Parallel Transform Frame algorithm keep internal state, and
interpolation on Curve3D should be stateless.
  • Loading branch information
xiongyaohua committed Aug 7, 2022
1 parent 836fe9a commit 299df9a
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 64 deletions.
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

0 comments on commit 299df9a

Please sign in to comment.