class_name Wheel extends RayCast3D @export var wheel_radius: float = 0.316 @export var flipped: bool = false @export var on_drivetrain: bool = false @export var rolling_resistance: float = 2 @export_group("Suspension") @export var suspension_rest_length: float = 0.1 @export var suspension_stiffness: float = 30_000 @export var suspension_damping: float = 2000 @export_group("Friction") @export var static_friction: float = 1.1 var car: Car var acceleration: Vector3 var _last_velocity: Vector3 func _ready() -> void: target_position *= suspension_rest_length + wheel_radius if flipped: $"RX-Wheel".rotate_y(PI) func set_new_car(new_car: Car) -> void: car = new_car # Little bit freaky but we put up with it _last_velocity = car.get_velocity_at_point(get_collision_point()) func _physics_process(delta: float) -> void: var velocity: Vector3 = car.get_velocity_at_point(get_collision_point()) acceleration = velocity - _last_velocity / delta force_raycast_update() apply_suspension_force() apply_rolling_resistance() apply_friction_force() _last_velocity = car.get_velocity_at_point(get_collision_point()) func apply_suspension_force() -> void: var up: Vector3 = global_basis.y if not is_colliding(): $"RX-Wheel".position = -up * suspension_rest_length return var wheel_center: Vector3 = get_wheel_center() $"RX-Wheel".position = wheel_center var suspension_length: float = -up.dot(wheel_center) var suspension_displacement: float = suspension_rest_length - suspension_length #https://en.wikipedia.org/wiki/Hookes_law var hookes_force_scalar: float = suspension_stiffness * suspension_displacement var wheel_velocity: Vector3 = car.get_velocity_at_point(to_global(wheel_center)) var wheel_up_velocity: float = up.dot(wheel_velocity) var damping_force_scalar: float = suspension_damping * wheel_up_velocity var y_force_scalar: float = hookes_force_scalar - damping_force_scalar var force: Vector3 = up * y_force_scalar DebugDraw3D.draw_arrow( to_global(wheel_center), to_global(wheel_center + force / 1000), Color.RED, .2 ) car.apply_force(force, car.to_local(to_global(wheel_center))) func apply_rolling_resistance() -> void: if not is_colliding(): return var normal: Vector3 = get_collision_normal() var velocity: Vector3 = car.get_velocity_at_point(get_collision_point()) velocity -= normal * normal.dot(velocity) var force: Vector3 = -rolling_resistance * velocity var arrow_tail: Vector3 = to_global(get_wheel_center()) car.apply_force(force, car.to_local(get_collision_point())) DebugDraw3D.draw_arrow( arrow_tail, arrow_tail + force / 250, Color.ORANGE, .2 ) func apply_friction_force() -> void: if not on_drivetrain: return if not is_colliding(): return var forwards: Vector3 = -global_basis.z var backwards: Vector3 = global_basis.z var normal: Vector3 = get_collision_normal() var total_weight: Vector3 = car.mass * car.get_gravity() var wheel_to_com: float = abs( forwards.dot(car.center_of_mass) - \ forwards.dot(car.to_local(get_collision_point())) ) var weight: Vector3 = wheel_to_com / car.wheelbase * total_weight var normal_force: Vector3 = -weight # Intern please fix var surface_static_friction: float = 1 var static_friction_coeff: float = static_friction * surface_static_friction var maximum_static_friction: float = static_friction_coeff * normal_force.length() var torque: float = car.get_torque_per_wheel() var applied_force: Vector3 = backwards * torque / wheel_radius #Restricts applied force to plane parralel to surface applied_force -= normal * applied_force.dot(normal) var friction_force: Vector3 = Vector3.ZERO if applied_force.length() <= maximum_static_friction: friction_force = -applied_force var arrow_tail: Vector3 = to_global(get_wheel_center()) car.apply_force(friction_force, car.to_local(get_collision_point())) DebugDraw3D.draw_arrow( arrow_tail, arrow_tail + friction_force / 250, Color.BLUE, .2 ) func get_wheel_center() -> Vector3: var up: Vector3 = global_basis.y var collision_point: Vector3 = to_local(get_collision_point()) return collision_point + (up * wheel_radius)