diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 9606564..f111e4c 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,4 +1,5 @@ # Summary +- [Brain Dump](./brain_dump.md) - [2D Vehicle Physics](./2d_vehicle_physics.md) - [Multiplayer](./multiplayer.md) diff --git a/docs/src/brain_dump.md b/docs/src/brain_dump.md new file mode 100644 index 0000000..2d50b1c --- /dev/null +++ b/docs/src/brain_dump.md @@ -0,0 +1,6 @@ +# Brain Dump + +Need to implement weight shift on car so front wheel steering has more effect when breaking. +Otherwise the breaking force prevents the steering. + +Implement a CarDebugger node that displays force vectors. diff --git a/godot/assets/police_car.png b/godot/assets/police_car.png new file mode 100644 index 0000000..99d5373 Binary files /dev/null and b/godot/assets/police_car.png differ diff --git a/godot/assets/police_car.png.import b/godot/assets/police_car.png.import new file mode 100644 index 0000000..19bffc7 --- /dev/null +++ b/godot/assets/police_car.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cds01w4trqeei" +path="res://.godot/imported/police_car.png-f484def1c543f947419256a0decbc9b1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/police_car.png" +dest_files=["res://.godot/imported/police_car.png-f484def1c543f947419256a0decbc9b1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/godot/camera_2d.gd b/godot/camera_2d.gd new file mode 100644 index 0000000..e69de29 diff --git a/godot/camera_2d.gd.uid b/godot/camera_2d.gd.uid new file mode 100644 index 0000000..6a93ac8 --- /dev/null +++ b/godot/camera_2d.gd.uid @@ -0,0 +1 @@ +uid://duwspluoy4xuu diff --git a/godot/car.gd b/godot/car.gd new file mode 100644 index 0000000..4787826 --- /dev/null +++ b/godot/car.gd @@ -0,0 +1,13 @@ +extends RigidBody2D + +@onready var cam = $Camera2D + +# zoom range: from close-in to far-out +var min_zoom = Vector2(1.0, 1.0) +var max_zoom = Vector2(0.2, 0.2) +var max_speed = 5000.0 # max expected speed + +func _process(delta): + var speed = linear_velocity.length() + var t = clamp(speed / max_speed, 0.0, 1.0) + cam.zoom = min_zoom.lerp(max_zoom, t) diff --git a/godot/car.gd.uid b/godot/car.gd.uid new file mode 100644 index 0000000..c862e99 --- /dev/null +++ b/godot/car.gd.uid @@ -0,0 +1 @@ +uid://bic64vi6lpelp diff --git a/godot/car.tscn b/godot/car.tscn index 06bf369..7dbd2fb 100644 --- a/godot/car.tscn +++ b/godot/car.tscn @@ -1,17 +1,19 @@ [gd_scene load_steps=3 format=3 uid="uid://ukvcgdjxtkqw"] -[ext_resource type="Texture2D" uid="uid://cl4nm8ajleyjy" path="res://assets/police_car_white.png" id="1_7822p"] +[ext_resource type="Texture2D" uid="uid://cds01w4trqeei" path="res://assets/police_car.png" id="1_7822p"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_37kl0"] -size = Vector2(194, 493) +size = Vector2(631, 1429) [node name="Car" type="Car"] mass = 1300.0 inertia = 5e+07 [node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(0.5, -0.5) +scale = Vector2(0.5, 0.5) shape = SubResource("RectangleShape2D_37kl0") [node name="Sprite2D" type="Sprite2D" parent="."] -scale = Vector2(0.265446, 0.265446) +scale = Vector2(0.5, 0.5) texture = ExtResource("1_7822p") diff --git a/godot/game.tscn b/godot/game.tscn index f73713e..f26440b 100644 --- a/godot/game.tscn +++ b/godot/game.tscn @@ -1,30 +1,34 @@ -[gd_scene load_steps=5 format=4 uid="uid://7713s03g7nxw"] +[gd_scene load_steps=5 format=3 uid="uid://7713s03g7nxw"] [ext_resource type="PackedScene" uid="uid://ukvcgdjxtkqw" path="res://car.tscn" id="1_80nbo"] -[ext_resource type="Texture2D" uid="uid://dd3nv8l28i3qg" path="res://atlases/test.png" id="1_e2o6t"] - -[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_feb5d"] -texture = ExtResource("1_e2o6t") -texture_region_size = Vector2i(256, 256) -0:0/0 = 0 -1:0/0 = 0 - -[sub_resource type="TileSet" id="TileSet_fc0e3"] -tile_size = Vector2i(256, 256) -sources/6 = SubResource("TileSetAtlasSource_feb5d") +[ext_resource type="PackedScene" uid="uid://drkq82mmv8ofa" path="res://test_track.tscn" id="1_e2o6t"] +[ext_resource type="Script" uid="uid://bic64vi6lpelp" path="res://car.gd" id="4_7jktm"] +[ext_resource type="Texture2D" uid="uid://bxwhirvk4jjpl" path="res://assets/textures/curb_1.png" id="4_fc0e3"] [node name="Node2D" type="Node2D"] -[node name="Camera2D" type="Camera2D" parent="."] -position = Vector2(0, -1) -zoom = Vector2(0.077, 0.077) +[node name="Node2D" parent="." instance=ExtResource("1_e2o6t")] +position = Vector2(1024, -7680) +rotation = 1.57079 -[node name="Roads" type="TileMapLayer" parent="."] -tile_map_data = PackedByteArray("AAD+/wAAAwAAAAAAAAD+////AwAAAAAAAAD9/wAAAgAAAAAAAFD9////AgAAAAAAAFD9/wEAAgAAAAAAAFD+/wIAAgAAAAAAAAD//wIAAgAAAAAAAAAAAAIAAgAAAAAAAAABAAIAAgAAAAAAAAD+/wEAAwAAAAAAAAD//wEAAwAAAAAAAAA=") -tile_set = SubResource("TileSet_fc0e3") +[node name="Node2D2" parent="." instance=ExtResource("1_e2o6t")] +position = Vector2(1024, -17920) +rotation = 1.57079 -[node name="Roads2" type="TileMapLayer" parent="."] -tile_map_data = PackedByteArray("AAD+/wAABgAAAAAAAAD+////BgAAAAAAAAD9/wAABgABAAAAAAD9////BgABAAAAAAD9/wEABgABAAAAAAD+/wIABgABAAAAAAD//wIABgABAAAAAAAAAAIABgABAAAAAAABAAIABgABAAAAAAD+/wEABgAAAAAAAAD//wEABgAAAAAAAAA=") -tile_set = SubResource("TileSet_fc0e3") +[node name="Node2D3" parent="." instance=ExtResource("1_e2o6t")] +position = Vector2(1024, -28160) +rotation = 1.57079 + +[node name="Curb1" type="Sprite2D" parent="."] +position = Vector2(-1024, 1022.44) +rotation = 1.57079 +scale = Vector2(1.99747, 0.147406) +texture = ExtResource("4_fc0e3") [node name="Car" parent="." instance=ExtResource("1_80nbo")] +script = ExtResource("4_7jktm") + +[node name="Camera2D" type="Camera2D" parent="Car"] +position = Vector2(0, -1) +scale = Vector2(0.1, 0.1) +zoom = Vector2(0.36, 0.36) diff --git a/godot/test_track.tscn b/godot/test_track.tscn new file mode 100644 index 0000000..fe6ab4a --- /dev/null +++ b/godot/test_track.tscn @@ -0,0 +1,166 @@ +[gd_scene load_steps=3 format=3 uid="uid://drkq82mmv8ofa"] + +[ext_resource type="Texture2D" uid="uid://boku1251l3foe" path="res://assets/textures/sidewalk.png" id="1_dd2be"] +[ext_resource type="Texture2D" uid="uid://dtfhvbp4vov3l" path="res://assets/textures/road_1.png" id="2_t3gmr"] + +[node name="Node2D" type="Node2D"] + +[node name="Sidewalk2" type="Sprite2D" parent="."] +position = Vector2(0, -512) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="."] +position = Vector2(0, 512) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="."] +position = Vector2(0, 1536) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="."] +position = Vector2(0, 2560) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") + +[node name="Sidewalk2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -3072) +texture = ExtResource("1_dd2be") + +[node name="Road1" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -2048) +texture = ExtResource("2_t3gmr") + +[node name="Road2" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, -1024) +texture = ExtResource("2_t3gmr") + +[node name="Sidewalk" type="Sprite2D" parent="Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk/Sidewalk"] +position = Vector2(1024, 0) +texture = ExtResource("1_dd2be") diff --git a/radio/Vinyl Voltage FM/Backin' Through the Town.mp3 b/radio/Vinyl Voltage FM/Backin' Through the Town.mp3 deleted file mode 100644 index ebad0ac..0000000 Binary files a/radio/Vinyl Voltage FM/Backin' Through the Town.mp3 and /dev/null differ diff --git a/radio/foo/Left on Red - variant.mp3 b/radio/foo/Left on Red - variant.mp3 deleted file mode 100644 index 25dc5a9..0000000 Binary files a/radio/foo/Left on Red - variant.mp3 and /dev/null differ diff --git a/radio/foo/Left on Red.mp3 b/radio/foo/Left on Red.mp3 deleted file mode 100644 index 857dc3a..0000000 Binary files a/radio/foo/Left on Red.mp3 and /dev/null differ diff --git a/rust/crates/grand-theft-arena/src/car.rs b/rust/crates/grand-theft-arena/src/car.rs index 6d568ed..4875efc 100644 --- a/rust/crates/grand-theft-arena/src/car.rs +++ b/rust/crates/grand-theft-arena/src/car.rs @@ -1,4 +1,6 @@ -//! Implements a vehicle model based on the bicycle model. +//! Implements a vehicle dynamics model +//! +//! Roughly based on the bicycle model. use godot::classes::IRigidBody2D; use godot::classes::RigidBody2D; @@ -6,6 +8,11 @@ use godot::global::deg_to_rad; use godot::prelude::*; use std::f32::consts::PI; +// Touch and feel value +const NEGLIGIBLE_SPEED_THRESHOLD: f32 = 10.0; +const NEGLIGIBLE_SPEED_THRESHOLD_SQUARED: f32 = NEGLIGIBLE_SPEED_THRESHOLD * NEGLIGIBLE_SPEED_THRESHOLD; +const CORNER_TAPER_SPEED_THRESHOLD: f32 = 800.0; + #[derive(GodotClass)] #[class(base=RigidBody2D)] struct Car { @@ -43,26 +50,26 @@ impl Wheel { #[godot_api] impl IRigidBody2D for Car { fn init(base: Base) -> Self { - let engine_force = 4000000.0; + let engine_force = 2_000_000.0; let brake_force = 2.5 * engine_force; - let max_cornering_force = 7000000.0; + let max_cornering_force = 3_000_000.0; Self { base, engine_force, brake_force, - air_drag_coefficient: 0.05, - mechanical_drag: 2000.0, + air_drag_coefficient: 0.02, + mechanical_drag: 15000.0, max_steering_angle: deg_to_rad(32.0) as f32, max_cornering_force, front_wheel: Wheel { - y_distance: 200.0, + y_distance: -200.0, has_grip: true, }, rear_wheel: Wheel { - y_distance: -200.0, + y_distance: 200.0, has_grip: true, }, } @@ -70,7 +77,18 @@ impl IRigidBody2D for Car { fn physics_process(&mut self, _delta: f64) { // self.base_mut().set_mass(1300.0); - // self.base_mut().set_inertia(260000000000.0); + // self.base_mut().set_inertia(260000000.0); + + let speed_is_negligible = self.speed_is_negligible(); + let angular_speed_is_negligible = self.angular_speed_is_negligible(); + + if speed_is_negligible { + self.base_mut().set_linear_velocity(Vector2::default()); + } + + if angular_speed_is_negligible { + self.base_mut().set_angular_velocity(0.0); + } let input = Input::singleton(); let move_forward = input.is_action_pressed("move_forward"); @@ -89,8 +107,8 @@ impl IRigidBody2D for Car { let rear_wheel_velocity = self.wheel_velocity(&self.rear_wheel); let front_wheel_angle = match (turn_left, turn_right) { - (true, false) => self.steering_angle(), - (false, true) => -self.steering_angle(), + (true, false) => -self.steering_angle(), + (false, true) => self.steering_angle(), (true, true) | (false, false) => 0.0, }; @@ -118,20 +136,36 @@ impl IRigidBody2D for Car { // front wheel drive self.base_mut().apply_force_ex(thrust_force).position(front_wheel_position).done(); - let front_slip_angle = front_wheels_direction.angle_to(front_wheel_velocity); - let rear_slip_angle = rear_wheels_direction.angle_to(rear_wheel_velocity); - - let speed_is_negligible = self.speed_is_negligible(); - - if speed_is_negligible { - self.base_mut().set_linear_velocity(Vector2::default()); - } + let speed = self.speed(); // Cornering forces - if !speed_is_negligible { - self.apply_cornering_force(front_wheel_position, front_slip_angle); - self.apply_cornering_force(rear_wheel_position, rear_slip_angle); - }; + if front_wheel_velocity.length_squared() > NEGLIGIBLE_SPEED_THRESHOLD_SQUARED { + let front_slip_angle = front_wheels_direction.angle_to(front_wheel_velocity); + let f_corner = self.cornering_force(front_slip_angle); + let f = if speed < CORNER_TAPER_SPEED_THRESHOLD { + // Taper at low speeds to prevent simulation instability + f_corner * speed / CORNER_TAPER_SPEED_THRESHOLD + } else { + f_corner + }; + self.base_mut().apply_force_ex(f).position(front_wheel_position).done(); + } + if rear_wheel_velocity.length_squared() > NEGLIGIBLE_SPEED_THRESHOLD_SQUARED { + let rear_slip_angle = rear_wheels_direction.angle_to(rear_wheel_velocity); + let f_corner = self.cornering_force(rear_slip_angle); + let f = if speed < CORNER_TAPER_SPEED_THRESHOLD { + // Taper at low speeds to prevent simulation instability + f_corner * speed / CORNER_TAPER_SPEED_THRESHOLD + } else { + f_corner + }; + + // Put more force on the rear wheel to correct heading. + // This is contrary to the physics though - on a normal car there is more grip on the front wheels afaict. + let f = f * 2.0; + + self.base_mut().apply_force_ex(f).position(rear_wheel_position).done(); + } // Air drag self.apply_air_drag(); @@ -142,15 +176,16 @@ impl IRigidBody2D for Car { } impl Car { - fn apply_cornering_force(&mut self, wheel_position: Vector2, slip_angle: f32) { + fn cornering_force(&mut self, slip_angle: f32) -> Vector2 { + debug_assert!(slip_angle.is_finite()); let right = self.right(); let a = slip_angle.abs(); let a = if a > PI / 2.0 { PI - a } else { a }; - let f = cornering_force(self.max_cornering_force, a); + let f = cornering_force_curve(self.max_cornering_force, a); + debug_assert!(f >= 0.0); - let force_vector = -slip_angle.sign() * right * f; - self.base_mut().apply_force_ex(force_vector).position(wheel_position).done(); + -slip_angle.sign() * right * f } /// Returns normal vector in the direction the car is facing. @@ -179,16 +214,29 @@ impl Car { self.base().get_linear_velocity().length() } - /// Returns true if current speed is low enough that lateral friction on wheels etc should not be calculated. - fn speed_is_negligible(&self) -> bool { - // Touch and feel value - const THRESHOLD: f32 = 10.0; + /// Returns current angular speed. + // TODO: in what unit? + fn angular_speed(&self) -> f32 { + self.base().get_angular_velocity() + } - self.speed() < THRESHOLD + /// Returns true if current speed is low enough that the car should just be stopped. + fn speed_is_negligible(&self) -> bool { + self.speed() < NEGLIGIBLE_SPEED_THRESHOLD + } + + /// Returns true if current angular speed is low enough that the car should just be stopped. + fn angular_speed_is_negligible(&self) -> bool { + // Touch and feel value + const THRESHOLD: f32 = 0.1; + + self.angular_speed().abs() < THRESHOLD } /// Returns steering angle, which is reduced from the max steering angle, the faster the car goes. fn steering_angle(&self) -> f32 { + return self.max_steering_angle; + // Steering angle as function of speed s. // f(0) = 1 // f(s) -> 0 as s -> infinity @@ -222,7 +270,7 @@ impl Car { let vel = self.base().get_linear_velocity(); if let Some(direction) = vel.try_normalized() { // TODO: Note in book that powf is non-deterministic. - let f = self.mechanical_drag * vel.length().powf(0.8); + let f = self.mechanical_drag * vel.length().powf(0.4); let f_vec = -direction * f; self.base_mut().apply_force(f_vec); } @@ -234,7 +282,8 @@ impl Car { /// Takes positive slip angle in [0; pi/2] /// /// Returns a non-negative value. -fn cornering_force(peak: f32, slip_angle: f32) -> f32 { +fn cornering_force_curve(peak: f32, slip_angle: f32) -> f32 { + debug_assert!(slip_angle.is_finite()); debug_assert!(slip_angle >= 0.0); debug_assert!(slip_angle <= PI / 2.0); @@ -258,7 +307,6 @@ fn cornering_force(peak: f32, slip_angle: f32) -> f32 { peak - c * peak * (slip_angle - peak_angle) / deg_to_rad(90.0) as f32 }; - // dbg!(rad_to_deg(slip_angle as f64), peak, magnitude); debug_assert!(magnitude >= 0.0); debug_assert!(peak >= magnitude);