diff --git a/assets/tyre_lateral.csv b/assets/tyre_lateral.csv new file mode 100644 index 0000000..db306b4 --- /dev/null +++ b/assets/tyre_lateral.csv @@ -0,0 +1,19 @@ +a +1.5 +0 +1100 +1100 +10 +0 +0 +-2 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/assets/tyre_longitudinal.csv b/assets/tyre_longitudinal.csv new file mode 100644 index 0000000..3f7a49c --- /dev/null +++ b/assets/tyre_longitudinal.csv @@ -0,0 +1,15 @@ +b +1.5 +-2.0 +1100 +0 +300 +0 +0 +0 +-2 +0 +0 +0 +0 +0 diff --git a/game/assets/assets.odin b/game/assets/assets.odin index 53140f5..1692525 100644 --- a/game/assets/assets.odin +++ b/game/assets/assets.odin @@ -16,16 +16,6 @@ import "vendor:raylib/rlgl" _ :: math -Loaded_Texture :: struct { - texture: rl.Texture2D, - modtime: c.long, -} - -Loaded_Model :: struct { - model: rl.Model, - modtime: c.long, -} - Loaded_BVH :: struct { // AABB of all bvhs aabb: bvh.AABB, @@ -53,39 +43,193 @@ destroy_loaded_bvh :: proc(loaded_bvh: Loaded_BVH) { delete(loaded_bvh.bvhs) } +Curve_2D :: [][2]f32 + Asset_Manager :: struct { - textures: map[cstring]Loaded_Texture, - models: map[cstring]Loaded_Model, - bvhs: map[cstring]Loaded_BVH, - curves: map[cstring]Loaded_Curve_2D, + textures: Asset_Cache(rl.Texture2D), + models: Asset_Cache(rl.Model), + curves_1d: Asset_Cache([]f32), + curves_2d: Asset_Cache(Curve_2D), + bvhs: map[cstring]Loaded_BVH, + 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 { tracy.Zone() - modtime := physfs.getLastModTime(path) + texture, _, _ := assetcache_fetch_or_load(&assetman.textures, path) - existing, ok := assetman.textures[path] - 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{} - } + return texture } get_model_ex :: proc( @@ -99,31 +243,11 @@ get_model_ex :: proc( ) { 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] - 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 - } + return } 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] } -// TODO: cache result +get_curve_1d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: []f32) { + curve, _, _ = assetcache_fetch_or_load(&assetman.curves_1d, path) + return +} + // Reads a two column comma separated csv file as a curve -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 - } - - 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) - } - +get_curve_2d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: Curve_2D) { + curve, _, _ = assetcache_fetch_or_load(&assetman.curves_2d, path) return } @@ -584,16 +700,13 @@ debug_draw_tetrahedron_wires :: proc(tri: [3]rl.Vector3, p: rl.Vector3, color: r shutdown :: proc(assetman: ^Asset_Manager) { tracy.Zone() - for _, texture in assetman.textures { - rl.UnloadTexture(texture.texture) - } - for _, model in assetman.models { - rl.UnloadModel(model.model) - } + assetcache_destroy(&assetman.textures) + assetcache_destroy(&assetman.models) + assetcache_destroy(&assetman.curves_1d) + assetcache_destroy(&assetman.curves_2d) + for _, loaded_bvh in assetman.bvhs { destroy_loaded_bvh(loaded_bvh) } - delete(assetman.textures) - delete(assetman.models) delete(assetman.bvhs) } diff --git a/game/assets/assets_test.odin b/game/assets/assets_test.odin index e84089c..21c5018 100644 --- a/game/assets/assets_test.odin +++ b/game/assets/assets_test.odin @@ -23,7 +23,7 @@ rpm,torque @(test) 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, len(curve.points), 3) @@ -34,9 +34,9 @@ test_curve_parsing :: proc(t: ^testing.T) { @(test) 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) - 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) } diff --git a/game/assets/parsers.odin b/game/assets/parsers.odin index a1246e8..a1fabad 100644 --- a/game/assets/parsers.odin +++ b/game/assets/parsers.odin @@ -6,18 +6,71 @@ import "core:io" import "core:log" import "core:strconv" -Curve_Parse_Error :: enum { +CSV_Parse_Error :: enum { Ok, TooManyColumns, 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, allocator := context.allocator, ) -> ( curve: Loaded_Curve_2D, - error: Curve_Parse_Error, + error: CSV_Parse_Error, ) { bytes_reader: bytes.Reader bytes.reader_init(&bytes_reader, data) diff --git a/game/game.odin b/game/game.odin index 80679b7..abd735f 100644 --- a/game/game.odin +++ b/game/game.odin @@ -345,6 +345,13 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { wheel_back_z := f32(-1.05) 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( &world.physics_scene, &runtime_world.solver_state, @@ -358,6 +365,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { damping = damping, body = runtime_world.car_handle, mass = wheel_mass, + pacejka_lat = pacejka_lat, + pacejka_long = pacejka_long, }, ) wheel_fr := physics.immediate_suspension_constraint( @@ -373,6 +382,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { damping = damping, body = runtime_world.car_handle, mass = wheel_mass, + pacejka_lat = pacejka_lat, + pacejka_long = pacejka_long, }, ) wheel_rl := physics.immediate_suspension_constraint( @@ -388,6 +399,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { damping = damping, body = runtime_world.car_handle, mass = wheel_mass, + pacejka_lat = pacejka_lat, + pacejka_long = pacejka_long, }, ) wheel_rr := physics.immediate_suspension_constraint( @@ -403,6 +416,8 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { damping = damping, body = runtime_world.car_handle, 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, #hash("engine", "fnv32a"), 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, 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}, axle = physics.Drive_Axle_Config { wheels = {wheel_rl, wheel_rr}, @@ -1080,6 +1098,7 @@ game_init :: proc() { g_mem^ = Game_Memory{} init_physifs_raylib_callbacks() + assets.assetman_init(&g_mem.assetman) physics.init_physics_scene(&g_mem.runtime_world.world.physics_scene, 100) diff --git a/game/physics/pacejka.odin b/game/physics/pacejka.odin index af72572..b7a695d 100644 --- a/game/physics/pacejka.odin +++ b/game/physics/pacejka.odin @@ -9,7 +9,7 @@ Pacejka94_Lateral_Params :: [18]f32 PACEJKA94_LONGITUDINAL_PEAK_X :: 6.24 PACEJKA94_LONGITUDINAL_PARAMS :: Pacejka94_Longitudinal_Params { 1.5, - 0, + -2.0, 1100, 0, 300, @@ -91,3 +91,17 @@ pacejka_94_lateral :: proc( F := D * math.sin(C * math.atan(Bx1 - E * (Bx1 - math.atan(Bx1)))) + V 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 +} diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 14f8cb6..fe3392f 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -146,6 +146,8 @@ Suspension_Constraint :: struct { natural_frequency: f32, // How much to damp velocity of the spring damping: f32, + pacejka_long: Pacejka94_Longitudinal_Params, + pacejka_lat: Pacejka94_Lateral_Params, // Runtime state @@ -358,6 +360,8 @@ Suspension_Constraint_Config :: struct { damping: f32, radius: f32, mass: f32, + pacejka_long: Pacejka94_Longitudinal_Params, + pacejka_lat: Pacejka94_Lateral_Params, } Drive_Axle_Config :: struct { @@ -448,6 +452,8 @@ update_suspension_constraint_from_config :: proc( constraint.damping = config.damping constraint.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 { diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 3f403c6..966ddca 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -716,7 +716,7 @@ pgs_solve_suspension :: proc( dir, 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 forward := wheel_get_forward_vec(body, v) @@ -791,9 +791,9 @@ pgs_solve_suspension :: proc( slip_ratio := ground_vel == 0 ? 0 : clamp(wheel_spin_vel / ground_vel - 1, -1, 1) * 100.0 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_angle / PACEJKA94_LATERAL_PEAK_X / MAX_SLIP_LEN, @@ -809,7 +809,7 @@ pgs_solve_suspension :: proc( long_friction := abs( pacejka_94_longitudinal( - PACEJKA94_LONGITUDINAL_PARAMS, + v.pacejka_long, slip_ratio, max(abs(v.spring_impulse), 0.001) * inv_dt * 0.001, ), @@ -818,7 +818,7 @@ pgs_solve_suspension :: proc( lat_friction := abs( pacejka_94_lateral( - PACEJKA94_LATERAL_PARAMS, + v.pacejka_lat, slip_angle, max(abs(v.spring_impulse), 0.001) * inv_dt * 0.001, 0.0, diff --git a/main_release/main_release.odin b/main_release/main_release.odin index 6dfff00..955ef8a 100644 --- a/main_release/main_release.odin +++ b/main_release/main_release.odin @@ -29,10 +29,11 @@ main :: proc() { 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 - game.game_init_window() + game.game_init_window(os.args) game.game_init() window_open := true @@ -73,4 +74,5 @@ main :: proc() { NvOptimusEnablement: u32 = 1 @(export) -AmdPowerXpressRequestHighPerformance: i32 = 1 \ No newline at end of file +AmdPowerXpressRequestHighPerformance: i32 = 1 + diff --git a/research/pacejka96.ipynb b/research/pacejka96.ipynb index 2d6e443..6bea2af 100644 --- a/research/pacejka96.ipynb +++ b/research/pacejka96.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 15, + "execution_count": 59, "metadata": {}, "outputs": [], "source": [ @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 60, "metadata": {}, "outputs": [], "source": [ @@ -26,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 61, "metadata": {}, "outputs": [ { @@ -35,7 +35,7 @@ "text": [ "<>: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" ] } @@ -81,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 62, "metadata": {}, "outputs": [], "source": [ @@ -106,12 +106,13 @@ " b11 = 0.0,\n", " b12 = 0.0,\n", " b13 = 0.0,\n", + " _Fz=float(1.0)\n", " ):\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 = result.subs({Fz: 1})\n", + " result = result.subs({Fz: _Fz})\n", "\n", - " return plot(result, (x, 0, 100))\n", + " return plot(result, (x, -100, 100))\n", "\n", "def pacejka_lateral_interactive(\n", " a0 = float(0.0),\n", @@ -143,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 63, "metadata": {}, "outputs": [], "source": [ @@ -155,13 +156,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ad99275593cc4cc0a0d9bbd2418231cf", + "model_id": "dc81af6a67754c5d8127cc89eb70368b", "version_major": 2, "version_minor": 0 }, @@ -175,7 +176,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "57e3277511e346aabc5d38e222bc8eee", + "model_id": "e09f497a55324a8ba3e35f5624741bce", "version_major": 2, "version_minor": 0 }, @@ -190,7 +191,7 @@ "source": [ "long_params = [\n", " 1.5,\n", - " 0.0,\n", + " -1.0,\n", " 1100.0,\n", " 0.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", "\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", "display(w)\n", "display(copy_long_btn)" @@ -218,16 +219,23 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ad99275593cc4cc0a0d9bbd2418231cf", + "model_id": "60b3a140f56547828b45f3038c9c039c", "version_major": 2, "version_minor": 0 }, "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": {}, @@ -236,7 +244,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3c5b82c12f154727a32aed50b1f8fc59", + "model_id": "01b9a4caf90c43caadf998126c9f5c51", "version_major": 2, "version_minor": 0 }, @@ -272,11 +280,11 @@ "\n", "widget_lat = interactive(pacejka_lateral_interactive, kwargs={f'a{i}': lat_params[i] for i in range(len(lat_params))})\n", "\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[:-2])))\n", + "copy_lat_btn = widgets.Button(description=\"Copy Params\")\n", + "copy_lat_btn.on_click(lambda _: copy_to_clipboard(',\\n'.join(str(p.value) for p in widget_lat.children[:-2])))\n", "\n", - "display(w)\n", - "display(copy_long_btn)" + "display(widget_lat)\n", + "display(copy_lat_btn)" ] } ],