Start implementing engine simulation
This commit is contained in:
parent
a9f007a90e
commit
78ab4eeae9
63
assets/ae86_rpm_torque.csv
Normal file
63
assets/ae86_rpm_torque.csv
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
rpm,torque
|
||||||
|
1000,63.6
|
||||||
|
1100,72.3
|
||||||
|
1200,79.5
|
||||||
|
1300,85.7
|
||||||
|
1400,90.9
|
||||||
|
1500,95.5
|
||||||
|
1600,99.4
|
||||||
|
1700,102.9
|
||||||
|
1800,106.1
|
||||||
|
1900,108.9
|
||||||
|
2000,111.4
|
||||||
|
2100,113.6
|
||||||
|
2200,115.7
|
||||||
|
2300,117.6
|
||||||
|
2400,119.3
|
||||||
|
2500,120.9
|
||||||
|
2600,122.4
|
||||||
|
2700,123.7
|
||||||
|
2800,125
|
||||||
|
2900,126.2
|
||||||
|
3000,127.3
|
||||||
|
3100,128.3
|
||||||
|
3200,129.3
|
||||||
|
3300,130.2
|
||||||
|
3400,131
|
||||||
|
3500,131.8
|
||||||
|
3600,132.6
|
||||||
|
3700,133.3
|
||||||
|
3800,134
|
||||||
|
3900,134.6
|
||||||
|
4000,135.2
|
||||||
|
4100,135.8
|
||||||
|
4200,136.4
|
||||||
|
4300,136.9
|
||||||
|
4400,137.4
|
||||||
|
4500,137.9
|
||||||
|
4600,138.3
|
||||||
|
4700,138.8
|
||||||
|
4800,139.2
|
||||||
|
4900,139.6
|
||||||
|
5000,140
|
||||||
|
5100,140
|
||||||
|
5200,139.8
|
||||||
|
5300,139.6
|
||||||
|
5400,139.3
|
||||||
|
5500,138.9
|
||||||
|
5600,138.4
|
||||||
|
5700,137.8
|
||||||
|
5800,137.2
|
||||||
|
5900,136.4
|
||||||
|
6000,135.6
|
||||||
|
6100,134.7
|
||||||
|
6200,133.7
|
||||||
|
6300,132.6
|
||||||
|
6400,131.4
|
||||||
|
6500,130.1
|
||||||
|
6600,128.8
|
||||||
|
6700,126.3
|
||||||
|
6800,123
|
||||||
|
6900,118.7
|
||||||
|
7000,113.6
|
||||||
|
7100,107.7
|
|
@ -1,9 +1,6 @@
|
|||||||
package assets
|
package assets
|
||||||
|
|
||||||
import "core:bytes"
|
|
||||||
import "core:c"
|
import "core:c"
|
||||||
import "core:encoding/csv"
|
|
||||||
import "core:io"
|
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
import lg "core:math/linalg"
|
import lg "core:math/linalg"
|
||||||
@ -194,57 +191,12 @@ get_curve_2d :: proc(assetman: ^Asset_Manager, path: string) -> (curve: Loaded_C
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes_reader: bytes.Reader
|
err2: Curve_Parse_Error
|
||||||
bytes.reader_init(&bytes_reader, data)
|
curve, err2 = parse_curve_2d(data, context.temp_allocator)
|
||||||
bytes_stream := bytes.reader_to_stream(&bytes_reader)
|
|
||||||
|
|
||||||
csv_reader: csv.Reader
|
if err2 != nil {
|
||||||
csv.reader_init(&csv_reader, bytes_stream, context.temp_allocator)
|
log.errorf("Failed to parse curve: %s, %v", path, err2)
|
||||||
|
|
||||||
tmp_result := make([dynamic][2]f32, context.temp_allocator)
|
|
||||||
|
|
||||||
skipped_header := false
|
|
||||||
for {
|
|
||||||
row, err := csv.read(&csv_reader, context.temp_allocator)
|
|
||||||
if err != nil {
|
|
||||||
if err != io.Error.EOF {
|
|
||||||
log.errorf("Failed to read curve %v", err)
|
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(row) != 2 {
|
|
||||||
log.errorf("Curve expected 2 columns, got %v", len(row))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ok: bool
|
|
||||||
key: f64
|
|
||||||
val: f64
|
|
||||||
key, ok = strconv.parse_f64(row[0])
|
|
||||||
if !ok {
|
|
||||||
if skipped_header {
|
|
||||||
log.errorf("Curve expected numbers, got %s", row[0])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
skipped_header = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val, ok = strconv.parse_f64(row[1])
|
|
||||||
if !ok {
|
|
||||||
if skipped_header {
|
|
||||||
log.errorf("Curve expected numbers, got %s", row[1])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
skipped_header = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
append(&tmp_result, [2]f32{f32(key), f32(val)})
|
|
||||||
}
|
|
||||||
|
|
||||||
curve.points = make([][2]f32, len(tmp_result), context.temp_allocator)
|
|
||||||
copy(curve.points, tmp_result[:])
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
42
game/assets/assets_test.odin
Normal file
42
game/assets/assets_test.odin
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import "core:testing"
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
test_csv := `
|
||||||
|
rpm,torque
|
||||||
|
1000,100
|
||||||
|
2000,110.5
|
||||||
|
5000,354.123
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
test_invalid_csv := `
|
||||||
|
rpm,torque
|
||||||
|
rpm,torque
|
||||||
|
1000,100
|
||||||
|
2000,110.5
|
||||||
|
5000,354.123
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_curve_parsing :: proc(t: ^testing.T) {
|
||||||
|
curve, err := parse_curve_2d(transmute([]u8)test_csv, context.temp_allocator)
|
||||||
|
|
||||||
|
testing.expect_value(t, err, nil)
|
||||||
|
testing.expect_value(t, len(curve.points), 3)
|
||||||
|
testing.expect_value(t, curve.points[0], [2]f32{1000, 100})
|
||||||
|
testing.expect_value(t, curve.points[1], [2]f32{2000, 110.5})
|
||||||
|
testing.expect_value(t, curve.points[2], [2]f32{5000, 354.123})
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_curve_parsing_error :: proc(t: ^testing.T) {
|
||||||
|
curve, err := parse_curve_2d(transmute([]u8)test_invalid_csv, context.temp_allocator)
|
||||||
|
defer free_all(context.temp_allocator)
|
||||||
|
|
||||||
|
testing.expect_value(t, err, Curve_Parse_Error.ExpectedNumber)
|
||||||
|
testing.expect_value(t, len(curve.points), 0)
|
||||||
|
}
|
79
game/assets/parsers.odin
Normal file
79
game/assets/parsers.odin
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import "core:bytes"
|
||||||
|
import "core:encoding/csv"
|
||||||
|
import "core:io"
|
||||||
|
import "core:log"
|
||||||
|
import "core:strconv"
|
||||||
|
|
||||||
|
Curve_Parse_Error :: enum {
|
||||||
|
Ok,
|
||||||
|
TooManyColumns,
|
||||||
|
ExpectedNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_curve_2d :: proc(
|
||||||
|
data: []byte,
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> (
|
||||||
|
curve: Loaded_Curve_2D,
|
||||||
|
error: Curve_Parse_Error,
|
||||||
|
) {
|
||||||
|
bytes_reader: bytes.Reader
|
||||||
|
bytes.reader_init(&bytes_reader, data)
|
||||||
|
bytes_stream := bytes.reader_to_stream(&bytes_reader)
|
||||||
|
|
||||||
|
csv_reader: csv.Reader
|
||||||
|
csv.reader_init(&csv_reader, bytes_stream, context.temp_allocator)
|
||||||
|
defer csv.reader_destroy(&csv_reader)
|
||||||
|
|
||||||
|
tmp_result := make([dynamic][2]f32, context.temp_allocator)
|
||||||
|
|
||||||
|
skipped_header := false
|
||||||
|
for {
|
||||||
|
row, err := csv.read(&csv_reader, context.temp_allocator)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.Error.EOF {
|
||||||
|
log.warnf("Failed to read curve %v", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(row) != 2 {
|
||||||
|
log.warnf("Curve expected 2 columns, got %v", len(row))
|
||||||
|
error = .TooManyColumns
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ok: bool
|
||||||
|
key: f64
|
||||||
|
val: f64
|
||||||
|
key, ok = strconv.parse_f64(row[0])
|
||||||
|
if !ok {
|
||||||
|
if skipped_header {
|
||||||
|
log.warnf("Curve expected numbers, got %s", row[0])
|
||||||
|
error = .ExpectedNumber
|
||||||
|
break
|
||||||
|
}
|
||||||
|
skipped_header = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, ok = strconv.parse_f64(row[1])
|
||||||
|
if !ok {
|
||||||
|
if skipped_header {
|
||||||
|
log.warnf("Curve expected numbers, got %s", row[1])
|
||||||
|
error = .ExpectedNumber
|
||||||
|
break
|
||||||
|
}
|
||||||
|
skipped_header = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&tmp_result, [2]f32{f32(key), f32(val)})
|
||||||
|
}
|
||||||
|
|
||||||
|
curve.points = make([][2]f32, len(tmp_result), allocator)
|
||||||
|
copy(curve.points, tmp_result[:])
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -15,7 +15,9 @@ init_physfs :: proc(args: []string) {
|
|||||||
|
|
||||||
physfs.init(strings.clone_to_cstring(args[0], context.temp_allocator))
|
physfs.init(strings.clone_to_cstring(args[0], context.temp_allocator))
|
||||||
physfs.setSaneConfig("serega", "gutter-runner", "zip", 0, 1)
|
physfs.setSaneConfig("serega", "gutter-runner", "zip", 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
init_physifs_raylib_callbacks :: proc() {
|
||||||
rl.SetLoadFileDataCallback(raylib_load_file_data_physfs)
|
rl.SetLoadFileDataCallback(raylib_load_file_data_physfs)
|
||||||
rl.SetSaveFileDataCallback(raylib_save_file_data_physfs)
|
rl.SetSaveFileDataCallback(raylib_save_file_data_physfs)
|
||||||
rl.SetLoadFileTextCallback(raylib_load_file_text_physfs)
|
rl.SetLoadFileTextCallback(raylib_load_file_text_physfs)
|
||||||
|
@ -394,6 +394,25 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
engine := physics.immediate_engine(
|
||||||
|
&world.physics_scene,
|
||||||
|
&runtime_world.solver_state,
|
||||||
|
#hash("engine", "fnv32a"),
|
||||||
|
physics.Engine_Config {
|
||||||
|
rpm_torque_curve = assets.get_curve_2d(&g_mem.assetman, "assets/ae86_rpm_torque.csv").points,
|
||||||
|
lowest_rpm = 1000,
|
||||||
|
inertia = 10,
|
||||||
|
internal_friction = 8,
|
||||||
|
gear_ratios = []f32{3.48, 3.587, 2.022, 1.384, 1, 0.861},
|
||||||
|
axle = physics.Drive_Axle_Config {
|
||||||
|
wheels = {wheel_rl, wheel_rr},
|
||||||
|
wheel_count = 2,
|
||||||
|
diff_type = .Fixed,
|
||||||
|
final_drive_ratio = 4.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
drive_wheels := []physics.Suspension_Constraint_Handle{wheel_rl, wheel_rr}
|
drive_wheels := []physics.Suspension_Constraint_Handle{wheel_rl, wheel_rr}
|
||||||
turn_wheels := []physics.Suspension_Constraint_Handle{wheel_fl, wheel_fr}
|
turn_wheels := []physics.Suspension_Constraint_Handle{wheel_fl, wheel_fr}
|
||||||
front_wheels := turn_wheels
|
front_wheels := turn_wheels
|
||||||
@ -426,11 +445,7 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
|
|||||||
wheel.brake_impulse = brake_input * (1.0 - BRAKE_BIAS) * BRAKE_IMPULSE
|
wheel.brake_impulse = brake_input * (1.0 - BRAKE_BIAS) * BRAKE_IMPULSE
|
||||||
}
|
}
|
||||||
|
|
||||||
for wheel_handle in drive_wheels {
|
physics.get_engine(sim_state, engine).throttle = gas_input
|
||||||
wheel := physics.get_suspension_constraint(sim_state, wheel_handle)
|
|
||||||
|
|
||||||
wheel.drive_impulse = gas_input * DRIVE_IMPULSE
|
|
||||||
}
|
|
||||||
|
|
||||||
car_body := physics.get_body(sim_state, runtime_world.car_handle)
|
car_body := physics.get_body(sim_state, runtime_world.car_handle)
|
||||||
turn_vel_correction := clamp(30.0 / linalg.length(car_body.v), 0, 1)
|
turn_vel_correction := clamp(30.0 / linalg.length(car_body.v), 0, 1)
|
||||||
@ -1040,6 +1055,8 @@ game_init :: proc() {
|
|||||||
|
|
||||||
g_mem^ = Game_Memory{}
|
g_mem^ = Game_Memory{}
|
||||||
|
|
||||||
|
init_physifs_raylib_callbacks()
|
||||||
|
|
||||||
physics.init_physics_scene(&g_mem.runtime_world.world.physics_scene, 100)
|
physics.init_physics_scene(&g_mem.runtime_world.world.physics_scene, 100)
|
||||||
|
|
||||||
game_hot_reloaded(g_mem)
|
game_hot_reloaded(g_mem)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package physics
|
package physics
|
||||||
|
|
||||||
import "collision"
|
import "collision"
|
||||||
|
import "core:math"
|
||||||
import lg "core:math/linalg"
|
import lg "core:math/linalg"
|
||||||
import "game:halfedge"
|
import "game:halfedge"
|
||||||
|
|
||||||
@ -150,3 +151,11 @@ body_get_aabb :: proc(body: Body_Ptr) -> (aabb: AABB) {
|
|||||||
|
|
||||||
return aabb
|
return aabb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpm_to_angular_velocity :: proc(rpm: f32) -> f32 {
|
||||||
|
return 2.0 * math.PI * (rpm / 60.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
angular_velocity_to_rpm :: proc(w: f32) -> f32 {
|
||||||
|
return (w / (2.0 * math.PI)) * 60.0
|
||||||
|
}
|
||||||
|
@ -71,10 +71,40 @@ immediate_suspension_constraint :: proc(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
immediate_engine :: proc(
|
||||||
|
scene: ^Scene,
|
||||||
|
state: ^Solver_State,
|
||||||
|
id: u32,
|
||||||
|
config: Engine_Config,
|
||||||
|
) -> (
|
||||||
|
handle: Engine_Handle,
|
||||||
|
) {
|
||||||
|
sim_state := get_sim_state(scene)
|
||||||
|
if id in state.immediate_engines {
|
||||||
|
engine := &state.immediate_engines[id]
|
||||||
|
if engine.last_ref != state.simulation_frame {
|
||||||
|
engine.last_ref = state.simulation_frame
|
||||||
|
state.num_referenced_engines += 1
|
||||||
|
}
|
||||||
|
handle = engine.handle
|
||||||
|
update_engine_from_config(sim_state, get_engine(sim_state, handle), config)
|
||||||
|
} else {
|
||||||
|
state.num_referenced_engines += 1
|
||||||
|
handle = add_engine(sim_state, config)
|
||||||
|
state.immediate_engines[id] = {
|
||||||
|
handle = handle,
|
||||||
|
last_ref = state.simulation_frame,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
prune_immediate :: proc(scene: ^Scene, state: ^Solver_State) {
|
prune_immediate :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||||
tracy.Zone()
|
tracy.Zone()
|
||||||
prune_immediate_bodies(scene, state)
|
prune_immediate_bodies(scene, state)
|
||||||
prune_immediate_suspension_constraints(scene, state)
|
prune_immediate_suspension_constraints(scene, state)
|
||||||
|
prune_immediate_engines(scene, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Generic version
|
// TODO: Generic version
|
||||||
@ -134,3 +164,30 @@ prune_immediate_suspension_constraints :: proc(scene: ^Scene, state: ^Solver_Sta
|
|||||||
remove_suspension_constraint(get_sim_state(scene), handle)
|
remove_suspension_constraint(get_sim_state(scene), handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prune_immediate_engines :: proc(scene: ^Scene, state: ^Solver_State) {
|
||||||
|
if int(state.num_referenced_engines) == len(state.immediate_engines) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
num_unreferenced_engines := len(state.immediate_engines) - int(state.num_referenced_engines)
|
||||||
|
assert(num_unreferenced_engines >= 0)
|
||||||
|
|
||||||
|
engines_to_remove := make([]u32, num_unreferenced_engines, context.temp_allocator)
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for k, &v in state.immediate_engines {
|
||||||
|
if v.last_ref != state.simulation_frame {
|
||||||
|
engines_to_remove[i] = k
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(i == len(engines_to_remove))
|
||||||
|
|
||||||
|
for k in engines_to_remove {
|
||||||
|
handle := state.immediate_engines[k].handle
|
||||||
|
delete_key(&state.immediate_engines, k)
|
||||||
|
remove_engine(get_sim_state(scene), handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package physics
|
|||||||
|
|
||||||
import "collision"
|
import "collision"
|
||||||
import lg "core:math/linalg"
|
import lg "core:math/linalg"
|
||||||
|
import "game:container/spanpool"
|
||||||
|
|
||||||
MAX_CONTACTS :: 1024 * 16
|
MAX_CONTACTS :: 1024 * 16
|
||||||
|
|
||||||
@ -43,18 +44,26 @@ contact_container_copy :: proc(dst: ^Contact_Container, src: Contact_Container)
|
|||||||
Sim_State :: struct {
|
Sim_State :: struct {
|
||||||
bodies: #soa[dynamic]Body,
|
bodies: #soa[dynamic]Body,
|
||||||
suspension_constraints: #soa[dynamic]Suspension_Constraint,
|
suspension_constraints: #soa[dynamic]Suspension_Constraint,
|
||||||
|
engines: [dynamic]Engine,
|
||||||
// Number of alive bodies
|
// Number of alive bodies
|
||||||
num_bodies: i32,
|
num_bodies: i32,
|
||||||
|
num_engines: i32,
|
||||||
|
|
||||||
// Slices. When you call get_body or get_suspension_constraint you will get a pointer to an element in this slice
|
// Slices. When you call get_body or get_suspension_constraint you will get a pointer to an element in this slice
|
||||||
bodies_slice: #soa[]Body,
|
bodies_slice: #soa[]Body,
|
||||||
suspension_constraints_slice: #soa[]Suspension_Constraint,
|
suspension_constraints_slice: #soa[]Suspension_Constraint,
|
||||||
first_free_body_plus_one: i32,
|
first_free_body_plus_one: i32,
|
||||||
first_free_suspension_constraint_plus_one: i32,
|
first_free_suspension_constraint_plus_one: i32,
|
||||||
|
first_free_engine_plus_one: i32,
|
||||||
|
|
||||||
// Persistent stuff for simulation
|
// Persistent stuff for simulation
|
||||||
contact_container: Contact_Container,
|
contact_container: Contact_Container,
|
||||||
convex_container: Convex_Container,
|
convex_container: Convex_Container,
|
||||||
|
|
||||||
|
// NOTE: kinda overkill, but it simplifies copying sim states around a lot
|
||||||
|
// Engine array data
|
||||||
|
rpm_torque_curves_pool: spanpool.Span_Pool([2]f32),
|
||||||
|
gear_ratios_pool: spanpool.Span_Pool(f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
Scene :: struct {
|
Scene :: struct {
|
||||||
@ -167,12 +176,80 @@ Suspension_Constraint :: struct {
|
|||||||
next_plus_one: i32,
|
next_plus_one: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Diff_Type :: enum {
|
||||||
|
Fixed,
|
||||||
|
// TODO: LSD
|
||||||
|
}
|
||||||
|
|
||||||
|
Drive_Wheel :: struct {
|
||||||
|
wheel: Suspension_Constraint_Handle,
|
||||||
|
impulse: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Drive_Axle :: struct {
|
||||||
|
// Params
|
||||||
|
wheels: [2]Drive_Wheel,
|
||||||
|
wheel_count: i32,
|
||||||
|
diff_type: Diff_Type,
|
||||||
|
final_drive_ratio: f32,
|
||||||
|
|
||||||
|
// State
|
||||||
|
// Diff angular vel
|
||||||
|
w: f32,
|
||||||
|
// Impulse that constrains diff and engine motion
|
||||||
|
engine_impulse: f32,
|
||||||
|
// Impulse that constrains wheels motion relative to each other
|
||||||
|
diff_impulse: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine_Curve_Handle :: distinct spanpool.Handle
|
||||||
|
Gear_Ratios_Handle :: distinct spanpool.Handle
|
||||||
|
|
||||||
|
// This actually handles everything, engine, transmission, differential, etc. It's easier to keep it in one place
|
||||||
|
Engine :: struct {
|
||||||
|
alive: bool,
|
||||||
|
// Engine Params
|
||||||
|
rpm_torque_curve: Engine_Curve_Handle,
|
||||||
|
lowest_rpm: f32,
|
||||||
|
inertia: f32,
|
||||||
|
internal_friction: f32,
|
||||||
|
|
||||||
|
// Transmission Params
|
||||||
|
// 0 - reverse, 1 - first, etc.
|
||||||
|
gear_ratios: Gear_Ratios_Handle,
|
||||||
|
axle: Drive_Axle,
|
||||||
|
|
||||||
|
// Engine State
|
||||||
|
// Angular velocity, omega
|
||||||
|
q: f32,
|
||||||
|
w: f32,
|
||||||
|
|
||||||
|
// Impulse applied when engine is stalling (rpm < lowest_rpm)
|
||||||
|
unstall_impulse: f32,
|
||||||
|
// Friction that makes rpm go down when you're not accelerating
|
||||||
|
friction_impulse: f32,
|
||||||
|
// Impulse applied from releasing throttle
|
||||||
|
throttle_impulse: f32,
|
||||||
|
|
||||||
|
// Transmission State
|
||||||
|
// -1 - reeverse, 0 - neutral, 1 - first, etc.
|
||||||
|
gear: i32,
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
throttle: f32,
|
||||||
|
|
||||||
|
// Free list
|
||||||
|
next_plus_one: i32,
|
||||||
|
}
|
||||||
|
|
||||||
// Index plus one, so handle 0 maps to invalid body
|
// Index plus one, so handle 0 maps to invalid body
|
||||||
Body_Handle :: distinct i32
|
Body_Handle :: distinct i32
|
||||||
Suspension_Constraint_Handle :: distinct i32
|
Suspension_Constraint_Handle :: distinct i32
|
||||||
|
Engine_Handle :: distinct i32
|
||||||
|
|
||||||
INVALID_BODY :: Body_Handle(0)
|
INVALID_BODY :: Body_Handle(0)
|
||||||
INVALID_SUSPENSION_CONSTRAINT :: Suspension_Constraint_Handle(0)
|
INVALID_SUSPENSION_CONSTRAINT :: Suspension_Constraint_Handle(0)
|
||||||
|
INVALID_ENGINE :: Engine_Handle(0)
|
||||||
|
|
||||||
is_body_handle_valid :: proc(handle: Body_Handle) -> bool {
|
is_body_handle_valid :: proc(handle: Body_Handle) -> bool {
|
||||||
return i32(handle) > 0
|
return i32(handle) > 0
|
||||||
@ -180,13 +257,18 @@ is_body_handle_valid :: proc(handle: Body_Handle) -> bool {
|
|||||||
is_suspension_constraint_handle_valid :: proc(handle: Suspension_Constraint_Handle) -> bool {
|
is_suspension_constraint_handle_valid :: proc(handle: Suspension_Constraint_Handle) -> bool {
|
||||||
return i32(handle) > 0
|
return i32(handle) > 0
|
||||||
}
|
}
|
||||||
|
is_engine_handle_valid :: proc(handle: Engine_Handle) -> bool {
|
||||||
|
return i32(handle) > 0
|
||||||
|
}
|
||||||
is_handle_valid :: proc {
|
is_handle_valid :: proc {
|
||||||
is_body_handle_valid,
|
is_body_handle_valid,
|
||||||
is_suspension_constraint_handle_valid,
|
is_suspension_constraint_handle_valid,
|
||||||
|
is_engine_handle_valid,
|
||||||
}
|
}
|
||||||
|
|
||||||
Body_Ptr :: #soa^#soa[]Body
|
Body_Ptr :: #soa^#soa[]Body
|
||||||
Suspension_Constraint_Ptr :: #soa^#soa[]Suspension_Constraint
|
Suspension_Constraint_Ptr :: #soa^#soa[]Suspension_Constraint
|
||||||
|
Engine_Ptr :: ^Engine
|
||||||
|
|
||||||
_invalid_body: #soa[1]Body
|
_invalid_body: #soa[1]Body
|
||||||
_invalid_body_slice := _invalid_body[:]
|
_invalid_body_slice := _invalid_body[:]
|
||||||
@ -264,6 +346,25 @@ Suspension_Constraint_Config :: struct {
|
|||||||
mass: f32,
|
mass: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Drive_Axle_Config :: struct {
|
||||||
|
wheels: [2]Suspension_Constraint_Handle,
|
||||||
|
wheel_count: i32,
|
||||||
|
diff_type: Diff_Type,
|
||||||
|
final_drive_ratio: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine_Config :: struct {
|
||||||
|
rpm_torque_curve: [][2]f32,
|
||||||
|
lowest_rpm: f32,
|
||||||
|
inertia: f32,
|
||||||
|
internal_friction: f32,
|
||||||
|
|
||||||
|
// Transmission Params
|
||||||
|
// 0 - reverse, 1 - first, etc.
|
||||||
|
gear_ratios: []f32,
|
||||||
|
axle: Drive_Axle_Config,
|
||||||
|
}
|
||||||
|
|
||||||
calculate_body_params_from_config :: proc(
|
calculate_body_params_from_config :: proc(
|
||||||
config: Body_Config,
|
config: Body_Config,
|
||||||
) -> (
|
) -> (
|
||||||
@ -335,6 +436,53 @@ update_suspension_constraint_from_config :: proc(
|
|||||||
constraint.inv_inertia = 1.0 / (0.5 * config.mass * config.radius * config.radius)
|
constraint.inv_inertia = 1.0 / (0.5 * config.mass * config.radius * config.radius)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_engine_curve :: proc(sim_state: ^Sim_State, curve: [][2]f32) -> Engine_Curve_Handle {
|
||||||
|
handle := spanpool.allocate_elems(&sim_state.rpm_torque_curves_pool, ..curve)
|
||||||
|
return Engine_Curve_Handle(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_engine_curve :: proc(sim_state: ^Sim_State, handle: Engine_Curve_Handle) {
|
||||||
|
spanpool.free(&sim_state.rpm_torque_curves_pool, spanpool.Handle(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
get_engine_curve :: proc(sim_state: ^Sim_State, handle: Engine_Curve_Handle) -> [][2]f32 {
|
||||||
|
return spanpool.resolve_slice(&sim_state.rpm_torque_curves_pool, spanpool.Handle(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
add_gear_ratios :: proc(sim_state: ^Sim_State, gear_ratios: []f32) -> Gear_Ratios_Handle {
|
||||||
|
handle := spanpool.allocate_elems(&sim_state.gear_ratios_pool, ..gear_ratios)
|
||||||
|
return Gear_Ratios_Handle(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_gear_ratios :: proc(sim_state: ^Sim_State, handle: Gear_Ratios_Handle) {
|
||||||
|
spanpool.free(&sim_state.gear_ratios_pool, spanpool.Handle(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
get_gear_ratios :: proc(sim_state: ^Sim_State, handle: Gear_Ratios_Handle) -> []f32 {
|
||||||
|
return spanpool.resolve_slice(&sim_state.gear_ratios_pool, spanpool.Handle(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
update_engine_from_config :: proc(
|
||||||
|
sim_state: ^Sim_State,
|
||||||
|
engine: Engine_Ptr,
|
||||||
|
config: Engine_Config,
|
||||||
|
) {
|
||||||
|
remove_engine_curve(sim_state, engine.rpm_torque_curve)
|
||||||
|
engine.rpm_torque_curve = add_engine_curve(sim_state, config.rpm_torque_curve)
|
||||||
|
|
||||||
|
remove_gear_ratios(sim_state, engine.gear_ratios)
|
||||||
|
engine.gear_ratios = add_gear_ratios(sim_state, config.gear_ratios)
|
||||||
|
|
||||||
|
engine.lowest_rpm = config.lowest_rpm
|
||||||
|
engine.inertia = config.inertia
|
||||||
|
engine.internal_friction = config.internal_friction
|
||||||
|
|
||||||
|
engine.axle.final_drive_ratio = config.axle.final_drive_ratio
|
||||||
|
engine.axle.wheels[0].wheel = config.axle.wheels[0]
|
||||||
|
engine.axle.wheels[1].wheel = config.axle.wheels[1]
|
||||||
|
engine.axle.wheel_count = config.axle.wheel_count
|
||||||
|
engine.axle.diff_type = config.axle.diff_type
|
||||||
|
}
|
||||||
|
|
||||||
add_body :: proc(sim_state: ^Sim_State, config: Body_Config) -> Body_Handle {
|
add_body :: proc(sim_state: ^Sim_State, config: Body_Config) -> Body_Handle {
|
||||||
body: Body
|
body: Body
|
||||||
@ -425,6 +573,54 @@ remove_suspension_constraint :: proc(sim_state: ^Sim_State, handle: Suspension_C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalid_engine: Engine
|
||||||
|
|
||||||
|
get_engine :: proc(sim_state: ^Sim_State, handle: Engine_Handle) -> Engine_Ptr {
|
||||||
|
index := int(handle) - 1
|
||||||
|
if index < 0 || index >= len(sim_state.bodies_slice) {
|
||||||
|
return &invalid_engine
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sim_state.engines[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
add_engine :: proc(sim_state: ^Sim_State, config: Engine_Config) -> Engine_Handle {
|
||||||
|
sim_state.num_engines += 1
|
||||||
|
|
||||||
|
engine: Engine
|
||||||
|
update_engine_from_config(sim_state, &engine, config)
|
||||||
|
|
||||||
|
engine.alive = true
|
||||||
|
|
||||||
|
if sim_state.first_free_engine_plus_one > 0 {
|
||||||
|
index := sim_state.first_free_engine_plus_one
|
||||||
|
new_engine := get_engine(sim_state, Engine_Handle(index))
|
||||||
|
next_plus_one := new_engine.next_plus_one
|
||||||
|
new_engine^ = engine
|
||||||
|
sim_state.first_free_engine_plus_one = next_plus_one
|
||||||
|
return Engine_Handle(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&sim_state.engines, engine)
|
||||||
|
index := len(sim_state.engines)
|
||||||
|
return Engine_Handle(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_engine :: proc(sim_state: ^Sim_State, handle: Engine_Handle) {
|
||||||
|
if is_handle_valid(handle) {
|
||||||
|
engine := get_engine(sim_state, handle)
|
||||||
|
|
||||||
|
remove_engine_curve(sim_state, engine.rpm_torque_curve)
|
||||||
|
remove_gear_ratios(sim_state, engine.gear_ratios)
|
||||||
|
|
||||||
|
engine.alive = false
|
||||||
|
engine.next_plus_one = sim_state.first_free_engine_plus_one
|
||||||
|
sim_state.first_free_engine_plus_one = i32(handle)
|
||||||
|
sim_state.num_engines -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 {
|
_get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 {
|
||||||
return sim_state.first_free_body_plus_one - 1
|
return sim_state.first_free_body_plus_one - 1
|
||||||
}
|
}
|
||||||
@ -434,7 +630,10 @@ destry_sim_state :: proc(sim_state: ^Sim_State) {
|
|||||||
delete_soa(sim_state.suspension_constraints)
|
delete_soa(sim_state.suspension_constraints)
|
||||||
delete_soa(sim_state.contact_container.contacts)
|
delete_soa(sim_state.contact_container.contacts)
|
||||||
delete_map(sim_state.contact_container.lookup)
|
delete_map(sim_state.contact_container.lookup)
|
||||||
|
delete(sim_state.engines)
|
||||||
convex_container_destroy(&sim_state.convex_container)
|
convex_container_destroy(&sim_state.convex_container)
|
||||||
|
spanpool.destroy_spanpool(&sim_state.rpm_torque_curves_pool)
|
||||||
|
spanpool.destroy_spanpool(&sim_state.gear_ratios_pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy_physics_scene :: proc(scene: ^Scene) {
|
destroy_physics_scene :: proc(scene: ^Scene) {
|
||||||
|
@ -9,6 +9,7 @@ import "core:math"
|
|||||||
import lg "core:math/linalg"
|
import lg "core:math/linalg"
|
||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
|
import "game:container/spanpool"
|
||||||
import "libs:tracy"
|
import "libs:tracy"
|
||||||
|
|
||||||
_ :: rand
|
_ :: rand
|
||||||
@ -31,13 +32,16 @@ Solver_State :: struct {
|
|||||||
// Number of immediate bodies referenced this frame
|
// Number of immediate bodies referenced this frame
|
||||||
num_referenced_bodies: i32,
|
num_referenced_bodies: i32,
|
||||||
num_referenced_suspension_constraints: i32,
|
num_referenced_suspension_constraints: i32,
|
||||||
|
num_referenced_engines: i32,
|
||||||
immedate_bodies: map[u32]Immedate_State(Body_Handle),
|
immedate_bodies: map[u32]Immedate_State(Body_Handle),
|
||||||
immediate_suspension_constraints: map[u32]Immedate_State(Suspension_Constraint_Handle),
|
immediate_suspension_constraints: map[u32]Immedate_State(Suspension_Constraint_Handle),
|
||||||
|
immediate_engines: map[u32]Immedate_State(Engine_Handle),
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy_solver_state :: proc(state: ^Solver_State) {
|
destroy_solver_state :: proc(state: ^Solver_State) {
|
||||||
delete(state.immedate_bodies)
|
delete(state.immedate_bodies)
|
||||||
delete(state.immediate_suspension_constraints)
|
delete(state.immediate_suspension_constraints)
|
||||||
|
delete(state.immediate_engines)
|
||||||
}
|
}
|
||||||
|
|
||||||
Immedate_State :: struct($T: typeid) {
|
Immedate_State :: struct($T: typeid) {
|
||||||
@ -57,9 +61,11 @@ sim_state_copy :: proc(dst: ^Sim_State, src: ^Sim_State) {
|
|||||||
dst.num_bodies = src.num_bodies
|
dst.num_bodies = src.num_bodies
|
||||||
dst.first_free_body_plus_one = src.first_free_body_plus_one
|
dst.first_free_body_plus_one = src.first_free_body_plus_one
|
||||||
dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one
|
dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one
|
||||||
|
dst.first_free_engine_plus_one = src.first_free_engine_plus_one
|
||||||
|
|
||||||
resize(&dst.bodies, len(src.bodies))
|
resize(&dst.bodies, len(src.bodies))
|
||||||
resize(&dst.suspension_constraints, len(src.suspension_constraints))
|
resize(&dst.suspension_constraints, len(src.suspension_constraints))
|
||||||
|
resize(&dst.engines, len(src.engines))
|
||||||
|
|
||||||
dst.bodies_slice = dst.bodies[:]
|
dst.bodies_slice = dst.bodies[:]
|
||||||
dst.suspension_constraints_slice = dst.suspension_constraints[:]
|
dst.suspension_constraints_slice = dst.suspension_constraints[:]
|
||||||
@ -70,9 +76,12 @@ sim_state_copy :: proc(dst: ^Sim_State, src: ^Sim_State) {
|
|||||||
for i in 0 ..< len(dst.suspension_constraints) {
|
for i in 0 ..< len(dst.suspension_constraints) {
|
||||||
dst.suspension_constraints[i] = src.suspension_constraints[i]
|
dst.suspension_constraints[i] = src.suspension_constraints[i]
|
||||||
}
|
}
|
||||||
|
copy(dst.engines[:], src.engines[:])
|
||||||
|
|
||||||
contact_container_copy(&dst.contact_container, src.contact_container)
|
contact_container_copy(&dst.contact_container, src.contact_container)
|
||||||
convex_container_copy(&dst.convex_container, src.convex_container)
|
convex_container_copy(&dst.convex_container, src.convex_container)
|
||||||
|
spanpool.copy(&dst.rpm_torque_curves_pool, src.rpm_torque_curves_pool)
|
||||||
|
spanpool.copy(&dst.gear_ratios_pool, src.gear_ratios_pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
Step_Mode :: enum {
|
Step_Mode :: enum {
|
||||||
@ -204,6 +213,7 @@ simulate :: proc(
|
|||||||
state.simulation_frame += 1
|
state.simulation_frame += 1
|
||||||
state.num_referenced_bodies = 0
|
state.num_referenced_bodies = 0
|
||||||
state.num_referenced_suspension_constraints = 0
|
state.num_referenced_suspension_constraints = 0
|
||||||
|
state.num_referenced_engines = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
GLOBAL_PLANE :: collision.Plane {
|
GLOBAL_PLANE :: collision.Plane {
|
||||||
@ -474,6 +484,119 @@ pgs_solve_contacts :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pgs_solve_engines :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_dt: f32) {
|
||||||
|
for &engine in sim_state.engines {
|
||||||
|
if engine.alive {
|
||||||
|
rpm_torque_curve := get_engine_curve(sim_state, engine.rpm_torque_curve)
|
||||||
|
gear_ratios := get_gear_ratios(sim_state, engine.gear_ratios)
|
||||||
|
|
||||||
|
// Unstall impulse
|
||||||
|
{
|
||||||
|
engine_lowest_velocity := rpm_to_angular_velocity(engine.lowest_rpm)
|
||||||
|
|
||||||
|
delta_omega := engine_lowest_velocity - engine.w
|
||||||
|
|
||||||
|
inv_w := engine.inertia
|
||||||
|
|
||||||
|
incremental_impulse := inv_w * delta_omega
|
||||||
|
new_total_impulse := max(engine.unstall_impulse + incremental_impulse, 0)
|
||||||
|
applied_impulse := new_total_impulse - engine.unstall_impulse
|
||||||
|
engine.unstall_impulse = new_total_impulse
|
||||||
|
|
||||||
|
engine.w += applied_impulse / engine.inertia
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal Friction
|
||||||
|
{
|
||||||
|
delta_omega := -engine.w
|
||||||
|
|
||||||
|
inv_w := engine.inertia
|
||||||
|
|
||||||
|
friction :=
|
||||||
|
math.pow(
|
||||||
|
max(engine.w - rpm_to_angular_velocity(engine.lowest_rpm), 0) * 0.001,
|
||||||
|
4,
|
||||||
|
) *
|
||||||
|
engine.internal_friction +
|
||||||
|
engine.internal_friction
|
||||||
|
|
||||||
|
incremental_impulse := inv_w * delta_omega
|
||||||
|
new_total_impulse := math.clamp(
|
||||||
|
engine.friction_impulse + incremental_impulse,
|
||||||
|
-friction,
|
||||||
|
friction,
|
||||||
|
)
|
||||||
|
applied_impulse := new_total_impulse - engine.friction_impulse
|
||||||
|
engine.friction_impulse = new_total_impulse
|
||||||
|
|
||||||
|
engine.w += applied_impulse / engine.inertia
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throttle
|
||||||
|
{
|
||||||
|
rpm := angular_velocity_to_rpm(engine.w)
|
||||||
|
|
||||||
|
torque: f32
|
||||||
|
|
||||||
|
idx, _ := slice.binary_search_by(
|
||||||
|
rpm_torque_curve,
|
||||||
|
rpm,
|
||||||
|
proc(a: [2]f32, k: f32) -> slice.Ordering {
|
||||||
|
return slice.cmp(a[0], k)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if idx > 0 && idx < len(rpm_torque_curve) - 1 {
|
||||||
|
cur_point := rpm_torque_curve[idx]
|
||||||
|
next_point := rpm_torque_curve[idx + 1]
|
||||||
|
rpm_diff := next_point[0] - cur_point[0]
|
||||||
|
alpha := (rpm - cur_point[0]) / rpm_diff
|
||||||
|
|
||||||
|
torque = math.lerp(cur_point[1], next_point[1], alpha)
|
||||||
|
} else {
|
||||||
|
torque = rpm_torque_curve[math.clamp(idx, 0, len(rpm_torque_curve) - 1)][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
torque *= engine.throttle
|
||||||
|
|
||||||
|
engine.w += torque / engine.inertia
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmission
|
||||||
|
{
|
||||||
|
// TODO: update from game
|
||||||
|
engine.gear = 1
|
||||||
|
|
||||||
|
power_split := 1.0 / f32(engine.axle.wheel_count)
|
||||||
|
for i in 0 ..< engine.axle.wheel_count {
|
||||||
|
drive_wheel := &engine.axle.wheels[i]
|
||||||
|
wheel := get_suspension_constraint(sim_state, drive_wheel.wheel)
|
||||||
|
|
||||||
|
ratio := gear_ratios[1] * engine.axle.final_drive_ratio
|
||||||
|
inv_ratio := f32(1.0 / ratio)
|
||||||
|
|
||||||
|
w1 := wheel.inv_inertia
|
||||||
|
w2 := f32(1.0 / (engine.inertia * ratio))
|
||||||
|
|
||||||
|
w := w1 + w2
|
||||||
|
inv_w := f32(1.0 / w)
|
||||||
|
|
||||||
|
delta_omega := -engine.w - wheel.w * ratio
|
||||||
|
|
||||||
|
incremental_impulse := -inv_w * delta_omega * power_split
|
||||||
|
drive_wheel.impulse += incremental_impulse
|
||||||
|
|
||||||
|
wheel.w += -incremental_impulse * wheel.inv_inertia * inv_ratio
|
||||||
|
engine.w += -(incremental_impulse / engine.inertia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracy.Plot("rpm", f64(angular_velocity_to_rpm(engine.w)))
|
||||||
|
log.debugf("rpm: %v", angular_velocity_to_rpm(engine.w))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pgs_solve_suspension :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_dt: f32) {
|
pgs_solve_suspension :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_dt: f32) {
|
||||||
// Solve suspension velocity
|
// Solve suspension velocity
|
||||||
for _, i in sim_state.suspension_constraints {
|
for _, i in sim_state.suspension_constraints {
|
||||||
@ -604,7 +727,7 @@ pgs_solve_suspension :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f
|
|||||||
slip_len = slip_len == 0 ? 0 : min(slip_len, 1) / slip_len
|
slip_len = slip_len == 0 ? 0 : min(slip_len, 1) / slip_len
|
||||||
slip_vec *= slip_len
|
slip_vec *= slip_len
|
||||||
|
|
||||||
log.debugf("slip_vec: %v", slip_vec)
|
// log.debugf("slip_vec: %v", slip_vec)
|
||||||
|
|
||||||
long_friction :=
|
long_friction :=
|
||||||
abs(
|
abs(
|
||||||
@ -712,6 +835,28 @@ pgs_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i in 0 ..< len(sim_state.engines) {
|
||||||
|
e := &sim_state.engines[i]
|
||||||
|
|
||||||
|
if e.alive {
|
||||||
|
gear_ratios := get_gear_ratios(sim_state, e.gear_ratios)
|
||||||
|
e.w += e.unstall_impulse / e.inertia
|
||||||
|
|
||||||
|
e.w += e.friction_impulse / e.inertia
|
||||||
|
|
||||||
|
for i in 0 ..< e.axle.wheel_count {
|
||||||
|
drive_wheel := &e.axle.wheels[i]
|
||||||
|
wheel := get_suspension_constraint(sim_state, drive_wheel.wheel)
|
||||||
|
|
||||||
|
ratio := gear_ratios[1] * e.axle.final_drive_ratio
|
||||||
|
inv_ratio := f32(1.0 / ratio)
|
||||||
|
|
||||||
|
wheel.w += -drive_wheel.impulse * wheel.inv_inertia * inv_ratio
|
||||||
|
e.w += -(drive_wheel.impulse / e.inertia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i in 0 ..< len(sim_state.suspension_constraints) {
|
for i in 0 ..< len(sim_state.suspension_constraints) {
|
||||||
s := &sim_state.suspension_constraints_slice[i]
|
s := &sim_state.suspension_constraints_slice[i]
|
||||||
|
|
||||||
@ -739,6 +884,7 @@ pgs_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_d
|
|||||||
|
|
||||||
apply_bias := true
|
apply_bias := true
|
||||||
pgs_solve_contacts(sim_state, config, dt, inv_dt, apply_bias)
|
pgs_solve_contacts(sim_state, config, dt, inv_dt, apply_bias)
|
||||||
|
pgs_solve_engines(sim_state, config, dt, inv_dt)
|
||||||
pgs_solve_suspension(sim_state, config, dt, inv_dt)
|
pgs_solve_suspension(sim_state, config, dt, inv_dt)
|
||||||
|
|
||||||
for i in 0 ..< len(sim_state.bodies_slice) {
|
for i in 0 ..< len(sim_state.bodies_slice) {
|
||||||
@ -763,6 +909,12 @@ pgs_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i in 0 ..< len(sim_state.engines) {
|
||||||
|
e := &sim_state.engines[i]
|
||||||
|
|
||||||
|
e.q = math.mod_f32(e.q + 0.5 * e.w * dt, math.PI * 2)
|
||||||
|
}
|
||||||
|
|
||||||
for i in 0 ..< len(sim_state.suspension_constraints_slice) {
|
for i in 0 ..< len(sim_state.suspension_constraints_slice) {
|
||||||
s := &sim_state.suspension_constraints_slice[i]
|
s := &sim_state.suspension_constraints_slice[i]
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
configure_cmake:
|
configure_cmake:
|
||||||
cmake -B build ./physfs
|
cmake -B build ./physfs
|
||||||
build_physfs: configure_cmake
|
build_physfs: configure_cmake
|
||||||
cmake --build build --config Release -j8
|
cmake --build build --config Debug -j8
|
||||||
libphysfs.a: build_physfs
|
libphysfs.a: build_physfs
|
||||||
cp ./build/libphysfs.a .
|
cp ./build/libphysfs.a .
|
||||||
libphysfs.so: build_physfs
|
libphysfs.so: build_physfs
|
||||||
|
98
research/engine_friction.ipynb
Normal file
98
research/engine_friction.ipynb
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
test.sh
2
test.sh
@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
odin test game -collection:common=./common -collection:game=./game -collection:libs=./libs -strict-style -vet
|
odin test game/assets -collection:common=./common -collection:game=./game -collection:libs=./libs -strict-style -vet -sanitize:memory
|
||||||
|
Loading…
x
Reference in New Issue
Block a user