feat: car revamp and camera attachment
This commit is contained in:
parent
a39f898f8e
commit
3c13ef9226
@ -1,4 +1,5 @@
|
||||
# Summary
|
||||
|
||||
- [Brain Dump](./brain_dump.md)
|
||||
- [2D Vehicle Physics](./2d_vehicle_physics.md)
|
||||
- [Multiplayer](./multiplayer.md)
|
||||
|
6
docs/src/brain_dump.md
Normal file
6
docs/src/brain_dump.md
Normal file
@ -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.
|
BIN
godot/assets/police_car.png
Normal file
BIN
godot/assets/police_car.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
34
godot/assets/police_car.png.import
Normal file
34
godot/assets/police_car.png.import
Normal file
@ -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
|
0
godot/camera_2d.gd
Normal file
0
godot/camera_2d.gd
Normal file
1
godot/camera_2d.gd.uid
Normal file
1
godot/camera_2d.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://duwspluoy4xuu
|
13
godot/car.gd
Normal file
13
godot/car.gd
Normal file
@ -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)
|
1
godot/car.gd.uid
Normal file
1
godot/car.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bic64vi6lpelp
|
@ -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")
|
||||
|
@ -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)
|
||||
|
166
godot/test_track.tscn
Normal file
166
godot/test_track.tscn
Normal file
@ -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")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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<RigidBody2D>) -> 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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user