Refactor asset loading to remove duplication, add loading of tyre params from csv

This commit is contained in:
sergeypdev 2025-04-27 22:53:07 +04:00
parent a27a5ccde6
commit bdd7bbb9b1
11 changed files with 369 additions and 120 deletions

19
assets/tyre_lateral.csv Normal file
View File

@ -0,0 +1,19 @@
a
1.5
0
1100
1100
10
0
0
-2
0
0
0
0
0
0
0
0
0
0
1 a
2 1.5
3 0
4 1100
5 1100
6 10
7 0
8 0
9 -2
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0

View File

@ -0,0 +1,15 @@
b
1.5
-2.0
1100
0
300
0
0
0
-2
0
0
0
0
0
1 b
2 1.5
3 -2.0
4 1100
5 0
6 300
7 0
8 0
9 0
10 -2
11 0
12 0
13 0
14 0
15 0

View File

@ -16,16 +16,6 @@ import "vendor:raylib/rlgl"
_ :: math _ :: math
Loaded_Texture :: struct {
texture: rl.Texture2D,
modtime: c.long,
}
Loaded_Model :: struct {
model: rl.Model,
modtime: c.long,
}
Loaded_BVH :: struct { Loaded_BVH :: struct {
// AABB of all bvhs // AABB of all bvhs
aabb: bvh.AABB, aabb: bvh.AABB,
@ -53,39 +43,193 @@ destroy_loaded_bvh :: proc(loaded_bvh: Loaded_BVH) {
delete(loaded_bvh.bvhs) delete(loaded_bvh.bvhs)
} }
Curve_2D :: [][2]f32
Asset_Manager :: struct { Asset_Manager :: struct {
textures: map[cstring]Loaded_Texture, textures: Asset_Cache(rl.Texture2D),
models: map[cstring]Loaded_Model, models: Asset_Cache(rl.Model),
curves_1d: Asset_Cache([]f32),
curves_2d: Asset_Cache(Curve_2D),
bvhs: map[cstring]Loaded_BVH, bvhs: map[cstring]Loaded_BVH,
curves: map[cstring]Loaded_Curve_2D, curves: map[cstring]Loaded_Curve_2D,
} }
Asset_Cache_Entry :: struct($E: typeid) {
value: E,
modtime: i64,
}
Asset_Cache_Loader :: struct($E: typeid) {
load: proc(path: cstring) -> (E, bool),
unload: proc(value: E),
}
Asset_Cache :: struct($E: typeid) {
cache: map[cstring]Asset_Cache_Entry(E),
loader: Asset_Cache_Loader(E),
}
MODEL_LOADER :: Asset_Cache_Loader(rl.Model) {
load = proc(path: cstring) -> (rl.Model, bool) {
model := rl.LoadModel(path)
return model, rl.IsModelValid(model)
},
unload = proc(model: rl.Model) {
rl.UnloadModel(model)
},
}
TEXTURE_LOADER :: Asset_Cache_Loader(rl.Texture2D) {
load = proc(path: cstring) -> (rl.Texture2D, bool) {
texture := rl.LoadTexture(path)
return texture, rl.IsTextureValid(texture)
},
unload = proc(texture: rl.Texture2D) {
rl.UnloadTexture(texture)
},
}
CURVE_1D_CSV_LOADER :: Asset_Cache_Loader([]f32) {
load = proc(path: cstring) -> ([]f32, bool) {
data, err := physfs.read_entire_file(string(path), context.temp_allocator)
if err != nil {
log.errorf("Failed to read curve: %s, %v", path, err)
return nil, false
}
values, err2 := parse_csv_1d(data)
if err2 != nil {
log.errorf("Failed to parse curve: %s, %v", path, err2)
}
return values, true
},
unload = proc(values: []f32) {
delete(values)
},
}
CURVE_2D_CSV_LOADER :: Asset_Cache_Loader(Curve_2D) {
load = proc(path: cstring) -> (Curve_2D, bool) {
data, err := physfs.read_entire_file(string(path), context.temp_allocator)
if err != nil {
log.errorf("Failed to read curve: %s, %v", path, err)
return nil, false
}
curve, err2 := parse_csv_2d(data)
if err2 != nil {
log.errorf("Failed to parse curve: %s, %v", path, err2)
}
return curve.points, true
},
unload = proc(curve: Curve_2D) {
delete(curve)
},
}
assetman_init :: proc(assetman: ^Asset_Manager) {
assetman.models = {
loader = MODEL_LOADER,
}
assetman.textures = {
loader = TEXTURE_LOADER,
}
assetman.curves_1d = {
loader = CURVE_1D_CSV_LOADER,
}
assetman.curves_2d = {
loader = CURVE_2D_CSV_LOADER,
}
}
Asset_Cache_Result :: enum {
Cached,
Loaded,
Reloaded,
Error,
}
assetcache_fetch_or_load :: proc(
ac: ^$T/Asset_Cache($E),
path: cstring,
) -> (
value: E,
modtime: i64,
result: Asset_Cache_Result,
) {
tracy.Zone()
existing, has_existing := ac.cache[path]
if has_existing {
new_modtime := physfs.getLastModTime(path)
if existing.modtime == new_modtime {
result = .Cached
return existing.value, new_modtime, result
} else {
// Try to load the new version
new_value, ok := ac.loader.load(path)
if ok {
result = .Reloaded
ac.loader.unload(existing.value)
ac.cache[path] = {
value = new_value,
modtime = new_modtime,
}
log.debugf("reloaded asset: %s", path)
return new_value, new_modtime, result
} else {
log.warnf("failed to reload asset after modification %s", path)
result = .Cached
return existing.value, existing.modtime, result
}
}
} else {
modtime = physfs.getLastModTime(path)
ok: bool
value, ok = ac.loader.load(path)
if ok {
ac.cache[path] = {
value = value,
modtime = modtime,
}
result = .Loaded
log.debugf("loaded asset: %s", path)
return value, modtime, result
} else {
log.errorf("failed to load asset %s", path)
result = .Error
return {}, 0, .Error
}
}
}
assetcache_destroy :: proc(ac: ^$T/Asset_Cache($E)) {
for _, v in ac.cache {
ac.loader.unload(v.value)
}
delete(ac.cache)
}
get_texture :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Texture2D { get_texture :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Texture2D {
tracy.Zone() tracy.Zone()
modtime := physfs.getLastModTime(path) texture, _, _ := assetcache_fetch_or_load(&assetman.textures, path)
existing, ok := assetman.textures[path] return texture
if ok && existing.modtime == modtime {
return existing.texture
}
if ok {
rl.UnloadTexture(existing.texture)
delete_key(&assetman.textures, path)
log.infof("deleted texture %s. New textures len: %d", path, len(assetman.textures))
}
loaded := rl.LoadTexture(path)
if rl.IsTextureValid(loaded) {
assetman.textures[path] = {
texture = loaded,
modtime = modtime,
}
return loaded
} else {
return rl.Texture2D{}
}
} }
get_model_ex :: proc( get_model_ex :: proc(
@ -99,31 +243,11 @@ get_model_ex :: proc(
) { ) {
tracy.Zone() tracy.Zone()
new_modtime := physfs.getLastModTime(path) result: Asset_Cache_Result
model, modtime, result = assetcache_fetch_or_load(&assetman.models, path)
reloaded = result == .Reloaded || ref_modtime != modtime
existing, ok := assetman.models[path] return
if ok && existing.modtime == new_modtime {
return existing.model,
existing.modtime,
ref_modtime == 0 ? false : existing.modtime != ref_modtime
}
if ok {
rl.UnloadModel(existing.model)
delete_key(&assetman.textures, path)
log.infof("deleted model %s. New models len: %d", path, len(assetman.textures))
}
loaded := rl.LoadModel(path)
if rl.IsModelValid(loaded) {
assetman.models[path] = {
model = loaded,
modtime = new_modtime,
}
return loaded, new_modtime, true
} else {
return rl.Model{}, 0, true
}
} }
get_model :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Model { get_model :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Model {
@ -182,22 +306,14 @@ get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> Loaded_BVH {
return assetman.bvhs[path] return assetman.bvhs[path]
} }
// TODO: cache result get_curve_1d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: []f32) {
// Reads a two column comma separated csv file as a curve curve, _, _ = assetcache_fetch_or_load(&assetman.curves_1d, path)
get_curve_2d :: proc(assetman: ^Asset_Manager, path: string) -> (curve: Loaded_Curve_2D) {
data, err := physfs.read_entire_file(path, context.temp_allocator)
if err != nil {
log.errorf("Failed to read curve: %s, %v", path, err)
return return
} }
err2: Curve_Parse_Error
curve, err2 = parse_curve_2d(data, context.temp_allocator)
if err2 != nil {
log.errorf("Failed to parse curve: %s, %v", path, err2)
}
// Reads a two column comma separated csv file as a curve
get_curve_2d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: Curve_2D) {
curve, _, _ = assetcache_fetch_or_load(&assetman.curves_2d, path)
return return
} }
@ -584,16 +700,13 @@ debug_draw_tetrahedron_wires :: proc(tri: [3]rl.Vector3, p: rl.Vector3, color: r
shutdown :: proc(assetman: ^Asset_Manager) { shutdown :: proc(assetman: ^Asset_Manager) {
tracy.Zone() tracy.Zone()
for _, texture in assetman.textures { assetcache_destroy(&assetman.textures)
rl.UnloadTexture(texture.texture) assetcache_destroy(&assetman.models)
} assetcache_destroy(&assetman.curves_1d)
for _, model in assetman.models { assetcache_destroy(&assetman.curves_2d)
rl.UnloadModel(model.model)
}
for _, loaded_bvh in assetman.bvhs { for _, loaded_bvh in assetman.bvhs {
destroy_loaded_bvh(loaded_bvh) destroy_loaded_bvh(loaded_bvh)
} }
delete(assetman.textures)
delete(assetman.models)
delete(assetman.bvhs) delete(assetman.bvhs)
} }

View File

@ -23,7 +23,7 @@ rpm,torque
@(test) @(test)
test_curve_parsing :: proc(t: ^testing.T) { test_curve_parsing :: proc(t: ^testing.T) {
curve, err := parse_curve_2d(transmute([]u8)test_csv, context.temp_allocator) curve, err := parse_csv_2d(transmute([]u8)test_csv, context.temp_allocator)
testing.expect_value(t, err, nil) testing.expect_value(t, err, nil)
testing.expect_value(t, len(curve.points), 3) testing.expect_value(t, len(curve.points), 3)
@ -34,9 +34,9 @@ test_curve_parsing :: proc(t: ^testing.T) {
@(test) @(test)
test_curve_parsing_error :: proc(t: ^testing.T) { test_curve_parsing_error :: proc(t: ^testing.T) {
curve, err := parse_curve_2d(transmute([]u8)test_invalid_csv, context.temp_allocator) curve, err := parse_csv_2d(transmute([]u8)test_invalid_csv, context.temp_allocator)
defer free_all(context.temp_allocator) defer free_all(context.temp_allocator)
testing.expect_value(t, err, Curve_Parse_Error.ExpectedNumber) testing.expect_value(t, err, CSV_Parse_Error.CSV_Parse_Error)
testing.expect_value(t, len(curve.points), 0) testing.expect_value(t, len(curve.points), 0)
} }

View File

@ -6,18 +6,71 @@ import "core:io"
import "core:log" import "core:log"
import "core:strconv" import "core:strconv"
Curve_Parse_Error :: enum { CSV_Parse_Error :: enum {
Ok, Ok,
TooManyColumns, TooManyColumns,
ExpectedNumber, ExpectedNumber,
} }
parse_curve_2d :: proc( parse_csv_1d :: proc(
data: []byte,
allocator := context.allocator,
) -> (
values: []f32,
error: CSV_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]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) != 1 {
log.warnf("expected 1 columns, got %v", len(row))
error = .TooManyColumns
break
}
val, ok := strconv.parse_f64(row[0])
if !ok {
if skipped_header {
log.warnf("Expected numbers, got %s", row[1])
error = .ExpectedNumber
break
}
skipped_header = true
continue
}
append(&tmp_result, f32(val))
}
values = make([]f32, len(tmp_result), allocator)
copy(values, tmp_result[:])
return
}
parse_csv_2d :: proc(
data: []byte, data: []byte,
allocator := context.allocator, allocator := context.allocator,
) -> ( ) -> (
curve: Loaded_Curve_2D, curve: Loaded_Curve_2D,
error: Curve_Parse_Error, error: CSV_Parse_Error,
) { ) {
bytes_reader: bytes.Reader bytes_reader: bytes.Reader
bytes.reader_init(&bytes_reader, data) bytes.reader_init(&bytes_reader, data)

View File

@ -345,6 +345,13 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
wheel_back_z := f32(-1.05) wheel_back_z := f32(-1.05)
wheel_mass := f32(14) wheel_mass := f32(14)
pacejka_long := physics.slice_to_pacejka94_long(
assets.get_curve_1d(&g_mem.assetman, "assets/tyre_longitudinal.csv"),
)
pacejka_lat := physics.slice_to_pacejka94_lat(
assets.get_curve_1d(&g_mem.assetman, "assets/tyre_lateral.csv"),
)
wheel_fl := physics.immediate_suspension_constraint( wheel_fl := physics.immediate_suspension_constraint(
&world.physics_scene, &world.physics_scene,
&runtime_world.solver_state, &runtime_world.solver_state,
@ -358,6 +365,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
damping = damping, damping = damping,
body = runtime_world.car_handle, body = runtime_world.car_handle,
mass = wheel_mass, mass = wheel_mass,
pacejka_lat = pacejka_lat,
pacejka_long = pacejka_long,
}, },
) )
wheel_fr := physics.immediate_suspension_constraint( wheel_fr := physics.immediate_suspension_constraint(
@ -373,6 +382,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
damping = damping, damping = damping,
body = runtime_world.car_handle, body = runtime_world.car_handle,
mass = wheel_mass, mass = wheel_mass,
pacejka_lat = pacejka_lat,
pacejka_long = pacejka_long,
}, },
) )
wheel_rl := physics.immediate_suspension_constraint( wheel_rl := physics.immediate_suspension_constraint(
@ -388,6 +399,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
damping = damping, damping = damping,
body = runtime_world.car_handle, body = runtime_world.car_handle,
mass = wheel_mass, mass = wheel_mass,
pacejka_lat = pacejka_lat,
pacejka_long = pacejka_long,
}, },
) )
wheel_rr := physics.immediate_suspension_constraint( wheel_rr := physics.immediate_suspension_constraint(
@ -403,6 +416,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
damping = damping, damping = damping,
body = runtime_world.car_handle, body = runtime_world.car_handle,
mass = wheel_mass, mass = wheel_mass,
pacejka_lat = pacejka_lat,
pacejka_long = pacejka_long,
}, },
) )
@ -411,10 +426,13 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
&runtime_world.solver_state, &runtime_world.solver_state,
#hash("engine", "fnv32a"), #hash("engine", "fnv32a"),
physics.Engine_Config { physics.Engine_Config {
rpm_torque_curve = assets.get_curve_2d(&g_mem.assetman, "assets/ae86_rpm_torque.csv").points, rpm_torque_curve = assets.get_curve_2d(
&g_mem.assetman,
"assets/ae86_rpm_torque.csv",
),
lowest_rpm = 1100, lowest_rpm = 1100,
inertia = 0.264, inertia = 0.264,
internal_friction = 0.01, internal_friction = 0.00,
gear_ratios = []f32{3.48, 3.587, 2.022, 1.384, 1, 0.861}, gear_ratios = []f32{3.48, 3.587, 2.022, 1.384, 1, 0.861},
axle = physics.Drive_Axle_Config { axle = physics.Drive_Axle_Config {
wheels = {wheel_rl, wheel_rr}, wheels = {wheel_rl, wheel_rr},
@ -1080,6 +1098,7 @@ game_init :: proc() {
g_mem^ = Game_Memory{} g_mem^ = Game_Memory{}
init_physifs_raylib_callbacks() init_physifs_raylib_callbacks()
assets.assetman_init(&g_mem.assetman)
physics.init_physics_scene(&g_mem.runtime_world.world.physics_scene, 100) physics.init_physics_scene(&g_mem.runtime_world.world.physics_scene, 100)

View File

@ -9,7 +9,7 @@ Pacejka94_Lateral_Params :: [18]f32
PACEJKA94_LONGITUDINAL_PEAK_X :: 6.24 PACEJKA94_LONGITUDINAL_PEAK_X :: 6.24
PACEJKA94_LONGITUDINAL_PARAMS :: Pacejka94_Longitudinal_Params { PACEJKA94_LONGITUDINAL_PARAMS :: Pacejka94_Longitudinal_Params {
1.5, 1.5,
0, -2.0,
1100, 1100,
0, 0,
300, 300,
@ -91,3 +91,17 @@ pacejka_94_lateral :: proc(
F := D * math.sin(C * math.atan(Bx1 - E * (Bx1 - math.atan(Bx1)))) + V F := D * math.sin(C * math.atan(Bx1 - E * (Bx1 - math.atan(Bx1)))) + V
return F / (f_z * 1000) return F / (f_z * 1000)
} }
slice_to_pacejka94_long :: proc(values: []f32) -> (result: Pacejka94_Longitudinal_Params) {
assert(len(values) == len(Pacejka94_Longitudinal_Params))
copy(result[:], values)
return result
}
slice_to_pacejka94_lat :: proc(values: []f32) -> (result: Pacejka94_Lateral_Params) {
assert(len(values) == len(Pacejka94_Lateral_Params))
copy(result[:], values)
return result
}

View File

@ -146,6 +146,8 @@ Suspension_Constraint :: struct {
natural_frequency: f32, natural_frequency: f32,
// How much to damp velocity of the spring // How much to damp velocity of the spring
damping: f32, damping: f32,
pacejka_long: Pacejka94_Longitudinal_Params,
pacejka_lat: Pacejka94_Lateral_Params,
// Runtime state // Runtime state
@ -358,6 +360,8 @@ Suspension_Constraint_Config :: struct {
damping: f32, damping: f32,
radius: f32, radius: f32,
mass: f32, mass: f32,
pacejka_long: Pacejka94_Longitudinal_Params,
pacejka_lat: Pacejka94_Lateral_Params,
} }
Drive_Axle_Config :: struct { Drive_Axle_Config :: struct {
@ -448,6 +452,8 @@ update_suspension_constraint_from_config :: proc(
constraint.damping = config.damping constraint.damping = config.damping
constraint.radius = config.radius constraint.radius = config.radius
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)
constraint.pacejka_long = config.pacejka_long
constraint.pacejka_lat = config.pacejka_lat
} }
add_engine_curve :: proc(sim_state: ^Sim_State, curve: [][2]f32) -> Engine_Curve_Handle { add_engine_curve :: proc(sim_state: ^Sim_State, curve: [][2]f32) -> Engine_Curve_Handle {

View File

@ -716,7 +716,7 @@ pgs_solve_suspension :: proc(
dir, dir,
v.rest, v.rest,
) )
log.debugf("hit_t: %v, hit: %v", v.hit_t, v.hit) // log.debugf("hit_t: %v, hit: %v", v.hit_t, v.hit)
v.hit_point = wheel_world_pos + dir * v.hit_t v.hit_point = wheel_world_pos + dir * v.hit_t
forward := wheel_get_forward_vec(body, v) forward := wheel_get_forward_vec(body, v)
@ -791,9 +791,9 @@ pgs_solve_suspension :: proc(
slip_ratio := slip_ratio :=
ground_vel == 0 ? 0 : clamp(wheel_spin_vel / ground_vel - 1, -1, 1) * 100.0 ground_vel == 0 ? 0 : clamp(wheel_spin_vel / ground_vel - 1, -1, 1) * 100.0
slip_angle := slip_angle :=
lg.angle_between(forward, body_vel_at_contact_patch) * math.DEG_PER_RAD -lg.angle_between(forward, body_vel_at_contact_patch) * math.DEG_PER_RAD
MAX_SLIP_LEN :: f32(2.0) MAX_SLIP_LEN :: f32(1.0)
slip_vec := Vec2 { slip_vec := Vec2 {
slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN, slip_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN,
@ -809,7 +809,7 @@ pgs_solve_suspension :: proc(
long_friction := long_friction :=
abs( abs(
pacejka_94_longitudinal( pacejka_94_longitudinal(
PACEJKA94_LONGITUDINAL_PARAMS, v.pacejka_long,
slip_ratio, slip_ratio,
max(abs(v.spring_impulse), 0.001) * inv_dt * 0.001, max(abs(v.spring_impulse), 0.001) * inv_dt * 0.001,
), ),
@ -818,7 +818,7 @@ pgs_solve_suspension :: proc(
lat_friction := lat_friction :=
abs( abs(
pacejka_94_lateral( pacejka_94_lateral(
PACEJKA94_LATERAL_PARAMS, v.pacejka_lat,
slip_angle, slip_angle,
max(abs(v.spring_impulse), 0.001) * inv_dt * 0.001, max(abs(v.spring_impulse), 0.001) * inv_dt * 0.001,
0.0, 0.0,

View File

@ -29,10 +29,11 @@ main :: proc() {
os.stderr = logh os.stderr = logh
} }
logger := logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger() logger :=
logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger()
context.logger = logger context.logger = logger
game.game_init_window() game.game_init_window(os.args)
game.game_init() game.game_init()
window_open := true window_open := true
@ -74,3 +75,4 @@ NvOptimusEnablement: u32 = 1
@(export) @(export)
AmdPowerXpressRequestHighPerformance: i32 = 1 AmdPowerXpressRequestHighPerformance: i32 = 1

View File

@ -2,7 +2,7 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 59,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -11,7 +11,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 60,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -26,7 +26,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 61,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@ -35,7 +35,7 @@
"text": [ "text": [
"<>:5: SyntaxWarning: invalid escape sequence '\\g'\n", "<>:5: SyntaxWarning: invalid escape sequence '\\g'\n",
"<>:5: SyntaxWarning: invalid escape sequence '\\g'\n", "<>:5: SyntaxWarning: invalid escape sequence '\\g'\n",
"/tmp/ipykernel_97985/2069913084.py:5: SyntaxWarning: invalid escape sequence '\\g'\n", "/tmp/ipykernel_100064/2069913084.py:5: SyntaxWarning: invalid escape sequence '\\g'\n",
" camber_angle = Symbol('\\gamma')\n" " camber_angle = Symbol('\\gamma')\n"
] ]
} }
@ -81,7 +81,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 18, "execution_count": 62,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -106,12 +106,13 @@
" b11 = 0.0,\n", " b11 = 0.0,\n",
" b12 = 0.0,\n", " b12 = 0.0,\n",
" b13 = 0.0,\n", " b13 = 0.0,\n",
" _Fz=float(1.0)\n",
" ):\n", " ):\n",
" params = [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13]\n", " params = [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13]\n",
" result = pacejka94_longitudinal().subs({sym: val for sym, val in zip(b, params)})\n", " result = pacejka94_longitudinal().subs({sym: val for sym, val in zip(b, params)})\n",
" result = result.subs({Fz: 1})\n", " result = result.subs({Fz: _Fz})\n",
"\n", "\n",
" return plot(result, (x, 0, 100))\n", " return plot(result, (x, -100, 100))\n",
"\n", "\n",
"def pacejka_lateral_interactive(\n", "def pacejka_lateral_interactive(\n",
" a0 = float(0.0),\n", " a0 = float(0.0),\n",
@ -143,7 +144,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 63,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -155,13 +156,13 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 64,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"application/vnd.jupyter.widget-view+json": { "application/vnd.jupyter.widget-view+json": {
"model_id": "ad99275593cc4cc0a0d9bbd2418231cf", "model_id": "dc81af6a67754c5d8127cc89eb70368b",
"version_major": 2, "version_major": 2,
"version_minor": 0 "version_minor": 0
}, },
@ -175,7 +176,7 @@
{ {
"data": { "data": {
"application/vnd.jupyter.widget-view+json": { "application/vnd.jupyter.widget-view+json": {
"model_id": "57e3277511e346aabc5d38e222bc8eee", "model_id": "e09f497a55324a8ba3e35f5624741bce",
"version_major": 2, "version_major": 2,
"version_minor": 0 "version_minor": 0
}, },
@ -190,7 +191,7 @@
"source": [ "source": [
"long_params = [\n", "long_params = [\n",
" 1.5,\n", " 1.5,\n",
" 0.0,\n", " -1.0,\n",
" 1100.0,\n", " 1100.0,\n",
" 0.0,\n", " 0.0,\n",
" 300.0,\n", " 300.0,\n",
@ -208,7 +209,7 @@
"w = interactive(pacejka_longitudinal_interactive, kwargs={f'b{i}': long_params[i] for i in range(len(long_params))})\n", "w = interactive(pacejka_longitudinal_interactive, kwargs={f'b{i}': long_params[i] for i in range(len(long_params))})\n",
"\n", "\n",
"copy_long_btn = widgets.Button(description=\"Copy Params\")\n", "copy_long_btn = widgets.Button(description=\"Copy Params\")\n",
"copy_long_btn.on_click(lambda _: copy_to_clipboard(',\\n'.join(str(p.value) for p in w.children[:-1])))\n", "copy_long_btn.on_click(lambda _: copy_to_clipboard(',\\n'.join(str(p.value) for p in w.children[:-2])))\n",
"\n", "\n",
"display(w)\n", "display(w)\n",
"display(copy_long_btn)" "display(copy_long_btn)"
@ -218,16 +219,23 @@
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {},
"outputs": [ "outputs": [
{ {
"data": { "data": {
"application/vnd.jupyter.widget-view+json": { "application/vnd.jupyter.widget-view+json": {
"model_id": "ad99275593cc4cc0a0d9bbd2418231cf", "model_id": "60b3a140f56547828b45f3038c9c039c",
"version_major": 2, "version_major": 2,
"version_minor": 0 "version_minor": 0
}, },
"text/plain": [ "text/plain": [
"interactive(children=(FloatSlider(value=1.5, description='b0', max=4.5, min=-1.5), FloatSlider(value=0.0, desc…" "interactive(children=(FloatSlider(value=0.0, description='a0', max=1.0), FloatSlider(value=0.0, description='a…"
] ]
}, },
"metadata": {}, "metadata": {},
@ -236,7 +244,7 @@
{ {
"data": { "data": {
"application/vnd.jupyter.widget-view+json": { "application/vnd.jupyter.widget-view+json": {
"model_id": "3c5b82c12f154727a32aed50b1f8fc59", "model_id": "01b9a4caf90c43caadf998126c9f5c51",
"version_major": 2, "version_major": 2,
"version_minor": 0 "version_minor": 0
}, },
@ -272,11 +280,11 @@
"\n", "\n",
"widget_lat = interactive(pacejka_lateral_interactive, kwargs={f'a{i}': lat_params[i] for i in range(len(lat_params))})\n", "widget_lat = interactive(pacejka_lateral_interactive, kwargs={f'a{i}': lat_params[i] for i in range(len(lat_params))})\n",
"\n", "\n",
"copy_long_btn = widgets.Button(description=\"Copy Params\")\n", "copy_lat_btn = widgets.Button(description=\"Copy Params\")\n",
"copy_long_btn.on_click(lambda _: copy_to_clipboard(',\\n'.join(str(p.value) for p in w.children[:-2])))\n", "copy_lat_btn.on_click(lambda _: copy_to_clipboard(',\\n'.join(str(p.value) for p in widget_lat.children[:-2])))\n",
"\n", "\n",
"display(w)\n", "display(widget_lat)\n",
"display(copy_long_btn)" "display(copy_lat_btn)"
] ]
} }
], ],