I've a Rotation<Real> from rapier2d and want to keep it between 0 and 360:

/// Structure used to rotate a car automatically.
pub struct CarTurn {
rotating: bool,
target_degrees: Rotation<Real>,
}
impl CarTurn {
pub fn turn(&mut self, direction: CarDirection) {
self.rotating = true;
self.target_degrees = Rotation::new(direction.rotation_degrees());
self.target_degrees = Rotation::new(Real::round(angle_util::zero_to_360(self.target_degrees.into_inner())) % 360.0);
}
}
mod angle_util {
use rapier2d::prelude::*;
use nalgebra::Complex;
pub fn zero_to_360(angle: Complex<Real>) -> Complex<Real> {
if angle < Complex::from(0.0) { Complex::from(360.0) - (-angle % Complex::from(360.0)) } else { angle % Complex::from(360.0) }
}
pub fn minimum_delta(a: Real, b: Real) -> Real {
let mut ab = a - b;
let mut ba = b - a;
ab = if ab < 0.0 { ab + 360.0 } else { ab };
ab = if ba < 0.0 { ba + 360.0 } else { ba };
if ab < ba { ab } else { ba }
}
}

The Complex type doesn't implement traits like Lt, Rem and so on. I've tried converting at all costs (TryInto and unwrap, Into, .into_inner(), .as_...()).

Why are you trying to use a complex number for an angle? That seems likely to be a mistake.

(Complex numbers can be used to represent directions, but when you do that, you do it by interpreting them as unit vectors, not the complex number itself being an angle number.)

The goal is to simply rotate a car to a target angle (in degrees) every frame, but I've 8 direction variants (up, up-left etc.) and they always return fixed angles.

This is a unit-magnitude Complex used to express rotation. It is not an angle. You need to work in terms of vectors, not angles. The number 360 will not appear in your code (nor 2π), and you don't need to wrap to 360. To change direction, multiply another unit complex with it (which is equivalent to adding angles).

This is the GDScript I'm trying to rewrite in Rust:

class_name CarTurn
# Turn radius in degrees
var turn_radius: float
var _rigid_body: RigidBody2D = null
var _running: bool = false
var _increment_scale: float = 1
var _final_degrees: float = 0
# (This is a tuple I can't express in GDScript)
var _route_result_go_clockwise: bool = false
var _route_result_delta: float = 0
var _current_rotation: float:
get:
return fmod(Angle.zero_to_360(self._rigid_body.rotation_degrees), 360)
func _init(rigid_body: RigidBody2D, turn_radius: float):
self._rigid_body = rigid_body
self.turn_radius = turn_radius
func is_running() -> bool:
return self._running
func turn(final_degrees: float) -> void:
if self.is_running():
self.stop()
self._final_degrees = fmod(roundf(Angle.zero_to_360(final_degrees)), 360.0)
self._running = true
func stop() -> void:
self._running = false
func integrate_forces(state: PhysicsDirectBodyState2D) -> void:
if not self._running:
return
var current_rotation = self._current_rotation
if current_rotation == self._final_degrees:
self._running = false
state.angular_velocity = 0
return
self._update_route(current_rotation)
var route_delta = self._route_result_delta
state.angular_velocity = (1.0 if (route_delta <= 3 and self.turn_radius > 1.0) else self.turn_radius) * self._increment_scale
func _update_route(current_rotation: float) -> void:
var a := self._final_degrees
var b := current_rotation
var ab := a - b
var ba := b - a
ab = ab + 360 if ab < 0 else ab
ba = ba + 360 if ba < 0 else ba
var go_clockwise := ab < ba
self._increment_scale = 1 if go_clockwise else -1
self._route_result_go_clockwise = go_clockwise
self._route_result_delta = roundf(ab if go_clockwise else ba)

If you're looking to snap angles for a better user experience, a better implementation than "snap to nearest degree unit" would be one that explicitly snaps to relevant angles.

For example, this should snap angles to the 4 cardinal directions:

if angle.realpart().abs() < SMALL_ANGLE {
angle = Angle(0, angle.imagpart().signum()); // signum returns -1 or 1 (or NaN)
} else if angle.imagpart().abs() < SMALL_ANGLE {
angle = Angle(angle.realpart().signum(), 0);
}

A force is applied every frame so that the car's rigid body goes forward. For example, if you're facing right and keep moving right, it'll move straight... but if you're facing right and suddenly move to left a drift or circunference move will occur.

Yes, adding is not the right operation, and it's not available here because it would make the number no longer unit. As I wrote in my previous post, multiplying unit complexes has the same effect as adding angles. (And, relatedly, powf() is analogous to multiples or fractions of an angle: for example, some_rotation.powf(2.) will rotate twice as far.)

I never used complex numbers; does that mean I'll have to use the re and im fields together?

You should generally not need to mention the re and im fields. They do have a useful property: their values are the cosine and sine of the angle, respectively. However, for manipulating the rotation you should generally stick to operations on the complex number as a whole.

And, again, if you need to convert to angle (e.g. to determine how different two rotations are), call .to_polar().