Load engines from TOML and improve scenario audio and carb tuning
Migrate 32 engine definitions into validated startup-loaded TOML files and separate configuration and snapshots from scenario assembly. Add camera-based stereo propagation from shared pipe geometry, preserve exhaust character, tune carburetor mixtures and idle, use ignition cuts for carb engines, and support returning to the scenario menu. Validation: full Windows build and all 14 CTest checks passed.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,3 +5,6 @@
|
|||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
/verification/bin/
|
/verification/bin/
|
||||||
/verification/obj/
|
/verification/obj/
|
||||||
|
|
||||||
|
# Generated compiler debug databases
|
||||||
|
*.pdb
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ option(FLUIDSIM_BUILD_DESKTOP "Build the SFML windowed simulator with keyboard c
|
|||||||
set(FLUIDSIM_FONT_FILE "" CACHE FILEPATH "Optional font file copied beside the desktop executable")
|
set(FLUIDSIM_FONT_FILE "" CACHE FILEPATH "Optional font file copied beside the desktop executable")
|
||||||
|
|
||||||
add_library(fluidsim_core
|
add_library(fluidsim_core
|
||||||
|
src/engine_config.cpp
|
||||||
src/carburetor.cpp
|
src/carburetor.cpp
|
||||||
src/ecu.cpp
|
src/ecu.cpp
|
||||||
src/engine.cpp
|
src/engine.cpp
|
||||||
@@ -17,11 +18,15 @@ add_library(fluidsim_core
|
|||||||
src/outdoor_audio.cpp
|
src/outdoor_audio.cpp
|
||||||
src/outdoor_exhaust_reverb.cpp
|
src/outdoor_exhaust_reverb.cpp
|
||||||
src/scenario.cpp
|
src/scenario.cpp
|
||||||
|
src/scenario_snapshot.cpp
|
||||||
|
src/scenario_geometry.cpp
|
||||||
src/solver.cpp
|
src/solver.cpp
|
||||||
src/vehicle.cpp
|
src/vehicle.cpp
|
||||||
src/simulation_ring_buffer.cpp
|
src/simulation_ring_buffer.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(fluidsim_core PUBLIC include)
|
target_include_directories(fluidsim_core PUBLIC include)
|
||||||
|
target_include_directories(fluidsim_core SYSTEM PRIVATE third_party/tomlplusplus)
|
||||||
|
target_compile_definitions(fluidsim_core PRIVATE FLUIDSIM_ENGINE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/engines")
|
||||||
target_compile_features(fluidsim_core PUBLIC cxx_std_20)
|
target_compile_features(fluidsim_core PUBLIC cxx_std_20)
|
||||||
option(FLUIDSIM_ENABLE_SIMD "Allow compiler SIMD in fluid kernels without fast-math" ON)
|
option(FLUIDSIM_ENABLE_SIMD "Allow compiler SIMD in fluid kernels without fast-math" ON)
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
@@ -109,6 +114,9 @@ endif()
|
|||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
if(BUILD_TESTING)
|
if(BUILD_TESTING)
|
||||||
|
add_executable(fluidsim_engine_config_tests tests/engine_config_tests.cpp)
|
||||||
|
target_link_libraries(fluidsim_engine_config_tests PRIVATE fluidsim_core)
|
||||||
|
add_test(NAME fluidsim_engine_config_tests COMMAND fluidsim_engine_config_tests)
|
||||||
add_executable(fluidsim_scenario_examples tests/scenario_examples.cpp)
|
add_executable(fluidsim_scenario_examples tests/scenario_examples.cpp)
|
||||||
target_link_libraries(fluidsim_scenario_examples PRIVATE fluidsim_core)
|
target_link_libraries(fluidsim_scenario_examples PRIVATE fluidsim_core)
|
||||||
add_test(NAME fluidsim_scenario_examples COMMAND fluidsim_scenario_examples)
|
add_test(NAME fluidsim_scenario_examples COMMAND fluidsim_scenario_examples)
|
||||||
@@ -167,4 +175,24 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME fluidsim_b205_regression COMMAND fluidsim_b205_regression)
|
add_test(NAME fluidsim_b205_regression COMMAND fluidsim_b205_regression)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_executable(fluidsim_carb_calibration tools/carb_calibration.cpp)
|
||||||
|
target_link_libraries(fluidsim_carb_calibration PRIVATE fluidsim_core)
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_test(NAME fluidsim_carb_calibration COMMAND fluidsim_carb_calibration --check)
|
||||||
|
endif()
|
||||||
|
|
||||||
include(cmake/MinGWRuntime.cmake)
|
include(cmake/MinGWRuntime.cmake)
|
||||||
|
|
||||||
|
# Keep definitions beside every built executable, including tests and CLI tools.
|
||||||
|
add_custom_target(fluidsim_engine_assets ALL
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DSOURCE=${CMAKE_CURRENT_SOURCE_DIR}/engines"
|
||||||
|
"-DDESTINATION=${CMAKE_CURRENT_BINARY_DIR}/engines"
|
||||||
|
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyEngineConfigs.cmake")
|
||||||
|
get_property(fluidsim_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
|
||||||
|
foreach(target IN LISTS fluidsim_targets)
|
||||||
|
get_target_property(target_type ${target} TYPE)
|
||||||
|
if(target_type STREQUAL "EXECUTABLE")
|
||||||
|
add_dependencies(${target} fluidsim_engine_assets)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||||
"CMAKE_C_COMPILER": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/gcc.exe",
|
"CMAKE_C_COMPILER": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/gcc.exe",
|
||||||
"CMAKE_CXX_COMPILER": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/g++.exe",
|
"CMAKE_CXX_COMPILER": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/g++.exe",
|
||||||
"CMAKE_MAKE_PROGRAM": "$penv{LOCALAPPDATA}/FluidSimTools/mingw64/bin/ninja.exe",
|
|
||||||
"FLUIDSIM_BUILD_DESKTOP": "ON",
|
"FLUIDSIM_BUILD_DESKTOP": "ON",
|
||||||
"BUILD_TESTING": "ON"
|
"BUILD_TESTING": "ON"
|
||||||
}
|
}
|
||||||
|
|||||||
107
README.md
107
README.md
@@ -123,18 +123,27 @@ The primary source is a linear compact-monopole approximation,
|
|||||||
`p(r,t) = rho_ambient * (d volume_flow/dt)/(4*pi*r)`. Mouth volume flow
|
`p(r,t) = rho_ambient * (d volume_flow/dt)/(4*pi*r)`. Mouth volume flow
|
||||||
comes from the boundary face velocity and area. Fluid conservation retains the
|
comes from the boundary face velocity and area. Fluid conservation retains the
|
||||||
actual mass flux; hot/cold density changes are excluded from the acoustic source.
|
actual mass flux; hot/cold density changes are excluded from the acoustic source.
|
||||||
Intake and exhaust have separate static
|
Intake and exhaust sources sit at the open ends of the same world routes used
|
||||||
positions, fractional-sample propagation delays, inverse-distance spreading,
|
to draw the pipes, including both outlets of dual exhausts. The desktop camera
|
||||||
and left/right ear paths. Playback calibration is 200 Pa per device full scale;
|
is the listener: orbit, pan and zoom change its position and ear orientation.
|
||||||
|
Left and right ears are 18 cm apart, with separate fractional-sample travel
|
||||||
|
delays at 343 m/s and inverse-distance spreading. Camera updates smooth over
|
||||||
|
20 ms while retaining propagation history and reflection filter state.
|
||||||
|
Playback calibration is 200 Pa per device full scale;
|
||||||
this is not calibrated speaker SPL. A second-order 6 kHz monitoring filter
|
this is not calibrated speaker SPL. A second-order 6 kHz monitoring filter
|
||||||
reduces sharp sample-to-sample transients without saturation or synthetic bangs.
|
reduces sharp sample-to-sample transients without saturation or synthetic bangs.
|
||||||
A roughly 3.5 Hz device DC blocker and final PCM clipping protection remain.
|
A roughly 3.5 Hz device DC blocker and final PCM clipping protection remain.
|
||||||
Use 1x playback for acoustic evaluation.
|
Use 1x playback for acoustic evaluation. Toggle **G** off to hear the full
|
||||||
|
distance-related loudness change; automatic gain otherwise compensates for it.
|
||||||
|
|
||||||
The default environment is a nearby wall. Press **R** to cycle dry, field,
|
The default environment is a nearby wall. Press **R** to cycle dry, field,
|
||||||
wall, woodland, and tunnel. Ground/wall image paths provide early reflections;
|
wall, woodland, and tunnel. Ground/wall image paths provide early reflections;
|
||||||
the live eight-line feedback-delay network supplies a scene-dependent late
|
the live eight-line feedback-delay network supplies a scene-dependent late
|
||||||
tail. Woodland scatterers and the hard-surface tunnel images are approximate.
|
tail. Early reflections follow the listener geometry, with plane reflections
|
||||||
|
disabled when the listener crosses to the other side. The diffuse tail receives
|
||||||
|
a delayed centre signal rather than summing the ears, avoiding ear-delay comb
|
||||||
|
filtering in the reverb input. Woodland scatterers and the hard-surface tunnel
|
||||||
|
images are approximate.
|
||||||
These are listening profiles, not validated acoustics. Mechanical sounds are
|
These are listening profiles, not validated acoustics. Mechanical sounds are
|
||||||
not implemented.
|
not implemented.
|
||||||
Open pipe mouths now use a causal unflanged radiation boundary with frequency-
|
Open pipe mouths now use a causal unflanged radiation boundary with frequency-
|
||||||
@@ -149,7 +158,9 @@ for capture commands, current renderer details and limitations.
|
|||||||
Launching `fluidsim_desktop` without `--scenario` opens a small scenario menu.
|
Launching `fluidsim_desktop` without `--scenario` opens a small scenario menu.
|
||||||
Choose the twin, YZ125, Saab B205, or American V8 card, or select with Up/Down
|
Choose the twin, YZ125, Saab B205, or American V8 card, or select with Up/Down
|
||||||
and press Enter.
|
and press Enter.
|
||||||
Escape closes the menu. Simulation and audio start after selection. Passing
|
Escape returns from a running scenario to the menu, stopping its simulation and
|
||||||
|
audio so another scenario can be selected. Escape closes the menu at the category
|
||||||
|
screen. Simulation and audio start after selection. Passing
|
||||||
`--scenario twin`, `--scenario two-stroke`, `--scenario b205`, or `--scenario v8`
|
`--scenario twin`, `--scenario two-stroke`, `--scenario b205`, or `--scenario v8`
|
||||||
bypasses the menu; `--scene` and `--exhaust` options also apply
|
bypasses the menu; `--scene` and `--exhaust` options also apply
|
||||||
when choosing through the menu.
|
when choosing through the menu.
|
||||||
@@ -353,11 +364,12 @@ catalyst, cold-start temperature model or injector electrical model. Idle
|
|||||||
control can still stall under excessive load and does not impose crankshaft
|
control can still stall under excessive load and does not impose crankshaft
|
||||||
speed or add artificial sustaining torque. Existing combustion/thermal models
|
speed or add artificial sustaining torque. Existing combustion/thermal models
|
||||||
remain approximations; ECU changes do not establish their physical accuracy.
|
remain approximations; ECU changes do not establish their physical accuracy.
|
||||||
The two-stroke uses a fixed mechanical carb slide stop instead of RPM-controlled
|
The two-stroke uses a fixed mechanical carb slide stop. With the pedal released,
|
||||||
idle throttle or idle ignition trim. Each carb scenario config sets
|
the ECU trims ignition timing around the configured idle RPM instead of moving
|
||||||
`TwoStrokeConfig::idle_slide_opening` (normalized 0..1); the default is 0.045,
|
the carb stop. Each carb scenario config sets
|
||||||
or 4.5% slide travel. Pedal demand above that stop opens the slide further.
|
`TwoStrokeConfig::idle_slide_opening` (normalized 0..1); the catalog 125 cc
|
||||||
The resulting idle RPM depends on the simulation and load. Its
|
scenarios now use a calibrated 3% stop. Pedal demand above that stop opens the
|
||||||
|
slide further. The resulting idle RPM depends on the simulation and load. Its
|
||||||
reed is a damped lumped cantilever model with finite opening time and seating
|
reed is a damped lumped cantilever model with finite opening time and seating
|
||||||
contacts. Cylinder and crankcase moving-volume work use adiabatic energy changes.
|
contacts. Cylinder and crankcase moving-volume work use adiabatic energy changes.
|
||||||
Jet dimensions are effective flow calibrations rather than measured Mikuni
|
Jet dimensions are effective flow calibrations rather than measured Mikuni
|
||||||
@@ -492,7 +504,8 @@ equal 400 mm x 45 mm secondaries, then a 50 mm exhaust. Pairings are 1+2 / 3+4
|
|||||||
and 5+7 / 6+8 with this simulation's bank numbering and firing order. Two T
|
and 5+7 / 6+8 with this simulation's bank numbering and firing order. Two T
|
||||||
junctions and a 780 mm x 38 mm tube form the H-pipe. Small configurable port-coupled junction volumes are used by the V8. The intake
|
junctions and a 780 mm x 38 mm tube form the H-pipe. Small configurable port-coupled junction volumes are used by the V8. The intake
|
||||||
plenum also remains a storage volume.
|
plenum also remains a storage volume.
|
||||||
`V8ExhaustConfig` configures primary length/diameter and enables the H crossover;
|
The TOML `primaries` pipe group configures primary length/diameter, while
|
||||||
|
`[junctions] h_pipe` enables the H crossover;
|
||||||
pass it as the fourth `FourStrokeScenario` constructor argument. The renderer
|
pass it as the fourth `FourStrokeScenario` constructor argument. The renderer
|
||||||
routes the primary pairs into Y merges, then the final Y collectors and H-pipe,
|
routes the primary pairs into Y merges, then the final Y collectors and H-pipe,
|
||||||
using local pipe diameter and pressure colors without collector boxes.
|
using local pipe diameter and pressure colors without collector boxes.
|
||||||
@@ -505,16 +518,25 @@ The previous header optimization measurements applied to the earlier finite-volu
|
|||||||
The startup browser now scrolls with the mouse wheel, Up/Down, Page Up/Down,
|
The startup browser now scrolls with the mouse wheel, Up/Down, Page Up/Down,
|
||||||
Home and End. It adapts to the window size and shows a scroll indicator. Click
|
Home and End. It adapts to the window size and shows a scroll indicator. Click
|
||||||
an example or press Enter to start it. The examples are defined in
|
an example or press Enter to start it. The examples are defined in
|
||||||
`ScenarioCatalog.hpp`, grouped into categories with selectable variants. These are configuration comparisons of
|
`engines/*.toml`, loaded at startup and grouped into categories with selectable variants. These are configuration comparisons of
|
||||||
the existing engine models, not measurements of additional production engines.
|
the existing engine models, not measurements of additional production engines.
|
||||||
|
|
||||||
The V8 currently uses port-coupled finite-volume fittings, configured by
|
The V8 currently uses port-coupled finite-volume fittings, configured by
|
||||||
`V8ExhaustConfig::junction_volume` (default 50 cc per fitting). They use the
|
`JunctionConfig::junction_volume` (default 50 cc per fitting). They use the
|
||||||
existing conservative component/port exchange and have no volume boxes in the
|
existing conservative component/port exchange and have no volume boxes in the
|
||||||
renderer. The earlier zero-storage Rusanov junction remains available for
|
renderer. The earlier zero-storage Rusanov junction remains available for
|
||||||
comparison through `BoundarySystem::add_junction`, but is not used by the V8.
|
comparison through `BoundarySystem::add_junction`, but is not used by the V8.
|
||||||
The 4-2-1 piping and the H crossover geometry remain the same.
|
The 4-2-1 piping and the H crossover geometry remain the same.
|
||||||
|
|
||||||
|
### TOML engine definitions
|
||||||
|
|
||||||
|
Every engine is defined in its own file under `engines/`. Copy an existing
|
||||||
|
`.toml` file, change its `id`, name and calibration, then restart to load it.
|
||||||
|
No rebuild is required when editing definitions in the active directory.
|
||||||
|
Unknown keys, invalid dimensions, malformed timing and duplicate IDs produce
|
||||||
|
a file-specific startup error. See [the engine configuration guide](docs/ENGINE_CONFIG.md)
|
||||||
|
for units, directory discovery, sections and pipe topology ordering.
|
||||||
|
|
||||||
### Engine families and inspired builds
|
### Engine families and inspired builds
|
||||||
|
|
||||||
The browser opens at engine categories; click a category to see variants, then
|
The browser opens at engine categories; click a category to see variants, then
|
||||||
@@ -531,7 +553,7 @@ estimated calibrations and simplified common-plenum/exhaust networks: they are
|
|||||||
not factory replicas, measured torque curves, or turbocharged configurations.
|
not factory replicas, measured torque curves, or turbocharged configurations.
|
||||||
|
|
||||||
The 8.0 L big-block variant transports carburetor fuel through its intake into
|
The 8.0 L big-block variant transports carburetor fuel through its intake into
|
||||||
the cylinders. Its fixed idle stop is configured with `EngineConfig::idle_opening`.
|
the cylinders. Its fixed idle stop is configured with `[ecu] idle_base_opening` in its TOML file.
|
||||||
It reuses the lumped carb model with scaled effective jets; multiple carb barrels,
|
It reuses the lumped carb model with scaled effective jets; multiple carb barrels,
|
||||||
floats and cylinder-specific fuel distribution are not resolved. Larger engine
|
floats and cylinder-specific fuel distribution are not resolved. Larger engine
|
||||||
geometry requires its own mixture and idle calibration.
|
geometry requires its own mixture and idle calibration.
|
||||||
@@ -544,5 +566,56 @@ chamber is a geometry experiment, not a guaranteed RPM or power improvement.
|
|||||||
|
|
||||||
Launch a specific variant directly with `--example big-carb`, `--example flat6`,
|
Launch a specific variant directly with `--example big-carb`, `--example flat6`,
|
||||||
`--example fifty` or another catalog ID. Carb jet scaling is configurable per
|
`--example fifty` or another catalog ID. Carb jet scaling is configurable per
|
||||||
engine (`EngineConfig::carb_jet_scale`, `carb_pilot_scale`, or
|
engine: four-strokes use effective jet diameters in `[fuel]`; two-strokes
|
||||||
`TwoStrokeConfig::jet_scale`).
|
use jet multipliers in `[two_stroke]`.
|
||||||
|
|
||||||
|
### Pulse-preserving custom exhausts
|
||||||
|
|
||||||
|
The custom inline three, F20 four, inline five and inline six examples use
|
||||||
|
unequal-length primaries, 50 cc junction collectors and secondary pipes rather
|
||||||
|
than a single large mixing volume. Their pipe damping matches the undamped Saab
|
||||||
|
path. Inline fours/fives/sixes use physical firing orders so collector grouping
|
||||||
|
preserves pulse timing; combustion remains even-fire.
|
||||||
|
|
||||||
|
The custom big-block V8 uses the American V8's cross-plane 1-5-4-2-6-3-7-8 firing
|
||||||
|
order and 4-2-1 headers with an H crossover, retaining its own displacement and
|
||||||
|
carburetor. V10/V12 examples retain separate banks through junction collectors,
|
||||||
|
secondary pipes and two radiating outlets, including both outlets in the spatial
|
||||||
|
audio renderer. The existing Saab, American V8, single and twin presets retain
|
||||||
|
their exhaust paths.
|
||||||
|
|
||||||
|
Capture any catalog entry at controlled RPM for comparison:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
./out/build/windows-gcc/fluidsim_audio_capture.exe --example v12 --rpm 3000 --warmup 0.6 --seconds 2 --throttle 0.55 --scene dry --out out/audio/v12
|
||||||
|
```
|
||||||
|
|
||||||
|
See [custom exhaust verification](verification/CUSTOM_EXHAUST_CHARACTER.md) for
|
||||||
|
fixed-RPM comparison results.
|
||||||
|
|
||||||
|
### Carburetor calibration
|
||||||
|
|
||||||
|
All seven two-stroke catalog variants and both carbureted four-strokes have
|
||||||
|
separate pilot, main-jet and needle-taper calibrations. Progressive pilot metering
|
||||||
|
fills the 5% throttle transition after lowering the 125 idle stops. The smaller four-stroke
|
||||||
|
single (`single-450`) now uses a 40 mm carburetor, transported fuel and milder
|
||||||
|
355/565-degree intake and 145/370-degree exhaust timing. The 650 cc single keeps
|
||||||
|
EFI. The big-block idle opening is 7%; the straight-pipe 125 uses 3%. The racing
|
||||||
|
125 and its chamber variants use the calibrated 3% mechanical slide stop.
|
||||||
|
|
||||||
|
Needle annulus area is interpolated continuously across slide positions; liquid
|
||||||
|
fuel still comes from the simulated jet pressure drop and float bowl. There is
|
||||||
|
no AFR feedback or cylinder-side fuel correction. The displayed carb AFR is the
|
||||||
|
uncapped ratio of filtered air flow to delivered fuel flow.
|
||||||
|
|
||||||
|
`fluidsim_carb_calibration --check` tests every carbureted catalog scenario at
|
||||||
|
idle, 5%, 10%, 15%, 25%, 50%, 75% and full pedal on an imposed-speed dyno, then
|
||||||
|
releases the dyno for an idle run. It fails on AFR outside the calibration
|
||||||
|
bounds, stalling, nonfinite RPM/AFR, pipe positivity repairs, a changed racing
|
||||||
|
125 stop, or loss of transported carb fuel in the 450 single. Run it through
|
||||||
|
`ctest --preset windows -R carb_calibration --output-on-failure`.
|
||||||
|
|
||||||
|
The tool also prints CSV diagnostics: run without arguments for the four main
|
||||||
|
operating points, or use `--sweep single-450` for a denser sweep of one scenario.
|
||||||
|
See [carb calibration results](verification/CARB_CALIBRATION.md) for operating
|
||||||
|
conditions, AFR values and the distinction between carb and recirculated charge.
|
||||||
|
|||||||
14
cmake/CopyEngineConfigs.cmake
Normal file
14
cmake/CopyEngineConfigs.cmake
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
file(MAKE_DIRECTORY "${DESTINATION}")
|
||||||
|
file(GLOB definitions "${SOURCE}/*.toml")
|
||||||
|
foreach(definition IN LISTS definitions)
|
||||||
|
get_filename_component(name "${definition}" NAME)
|
||||||
|
configure_file("${definition}" "${DESTINATION}/${name}" COPYONLY)
|
||||||
|
endforeach()
|
||||||
|
# Remove only stale definitions in the build's asset directory.
|
||||||
|
file(GLOB old_definitions "${DESTINATION}/*.toml")
|
||||||
|
foreach(definition IN LISTS old_definitions)
|
||||||
|
get_filename_component(name "${definition}" NAME)
|
||||||
|
if(NOT EXISTS "${SOURCE}/${name}")
|
||||||
|
file(REMOVE "${definition}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
126
docs/ENGINE_CONFIG.md
Normal file
126
docs/ENGINE_CONFIG.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definitions
|
||||||
|
|
||||||
|
Each `engines/*.toml` file defines one engine and its default vehicle/load
|
||||||
|
calibration. Files are loaded once when the program starts. Add a file, give it
|
||||||
|
a unique `id`, then restart to see it in the browser. Editing a definition does
|
||||||
|
not require recompiling the engine simulator.
|
||||||
|
|
||||||
|
Start by copying a similar engine, such as `single-450.toml`, `five.toml`,
|
||||||
|
`v8-h.toml` or `yz-racing.toml`. Existing definitions contain their effective
|
||||||
|
calibration values, including the previous carb and idle tunes.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
schema_version = 1
|
||||||
|
id = "my-single"
|
||||||
|
name = "My four-stroke single"
|
||||||
|
category = "Singles / four-stroke"
|
||||||
|
description = "My calibration"
|
||||||
|
order = 100
|
||||||
|
cycle = "four_stroke"
|
||||||
|
```
|
||||||
|
|
||||||
|
`id`, `name`, `category`, `cycle` and `schema_version` are required. IDs use
|
||||||
|
lowercase letters, digits, `-` and `_`. `order` controls menu order; ties sort by
|
||||||
|
ID. Duplicate IDs, unknown keys, unsupported versions, incorrect types and
|
||||||
|
invalid physical values stop loading with a diagnostic identifying the file
|
||||||
|
and field. TOML syntax errors also report their location.
|
||||||
|
|
||||||
|
## File discovery and distribution
|
||||||
|
|
||||||
|
The first available directory is used:
|
||||||
|
|
||||||
|
1. `FLUIDSIM_ENGINE_DIR`, when set. A missing override directory is an error.
|
||||||
|
2. `engines/` under the working directory.
|
||||||
|
3. `engines/` beside the executable on Windows and Linux.
|
||||||
|
4. The source checkout's `engines/` directory as a development fallback.
|
||||||
|
|
||||||
|
Definitions are cached for the process lifetime. The selected directory is
|
||||||
|
loaded as one catalog; it is not merged with the bundled files. In a repository
|
||||||
|
working directory, source definitions take priority over build copies.
|
||||||
|
|
||||||
|
CMake synchronizes the definitions into the build output when building an
|
||||||
|
executable. Distribute that `engines/` directory with the executable. The TOML
|
||||||
|
parser is vendored, so loading definitions needs no network connection.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:FLUIDSIM_ENGINE_DIR = "C:\MyEngines"
|
||||||
|
.\fluidsim_desktop.exe --example my-single
|
||||||
|
```
|
||||||
|
|
||||||
|
## Units and sections
|
||||||
|
|
||||||
|
File keys state dimensional units. `_mm` is millimetres, `_litres` is litres,
|
||||||
|
`_cc` is cubic centimetres, `_deg` is crank degrees, `_s` is seconds, `_nm` is
|
||||||
|
newton metres and `_kg_m2` is rotational inertia. The loader converts lengths
|
||||||
|
and volumes to SI units. Openings and mixture/taper multipliers are normalized
|
||||||
|
fractions, not percentages: `0.03` means 3%.
|
||||||
|
|
||||||
|
Four-stroke definitions use these sections:
|
||||||
|
|
||||||
|
| Section | Contents |
|
||||||
|
| --- | --- |
|
||||||
|
| `[engine]` | Cylinder count, layout, bank angle, phases, intake volumes, throttle, startup, load, and exhaust topology |
|
||||||
|
| `[cylinder]` | Bore/stroke/rod, compression, valve geometry and timing, combustion and heat transfer |
|
||||||
|
| `[crankshaft]` | Inertia and mechanical friction |
|
||||||
|
| `[ecu]` | Idle, actuator response, ignition curve, limiter and overrun thresholds |
|
||||||
|
| `[vehicle]` | Vehicle mass, gears, clutch, wheel geometry and road resistance |
|
||||||
|
| `[fuel]` | `system = "injection"` or `"carburetor"`, jet diameters and taper/film calibration |
|
||||||
|
| `[junctions]` | Fitting volume and H crossover selection |
|
||||||
|
| `[silencers]` | Connecting pipes, resonator and muffler profiles, when used |
|
||||||
|
| `[[pipes]]` | Ordered pipe groups for the selected exhaust topology |
|
||||||
|
|
||||||
|
`engine.phase_degrees` contains one phase per cylinder in cylinder index order,
|
||||||
|
within `[0,720)`. It records the actual crank offsets rather than a firing-order
|
||||||
|
label. `ecu.ignition_map` is an array of `[RPM, degrees BTDC]` pairs with strictly
|
||||||
|
increasing RPM. Negative advance means ignition after TDC.
|
||||||
|
|
||||||
|
For a carbureted four-stroke, use `ecu.idle_control = "fixed"` and set
|
||||||
|
`ecu.idle_base_opening`. Its limiter and overrun control cut ignition while the
|
||||||
|
carburetor continues supplying fuel. Jet diameters in `[fuel]` are effective
|
||||||
|
diameters after the previous displacement and jet multipliers have been applied.
|
||||||
|
|
||||||
|
Two-stroke definitions use `[two_stroke]`, `[ecu]` and `[vehicle]`.
|
||||||
|
`two_stroke.idle_slide_opening` sets the mechanical stop;
|
||||||
|
`idle_spark_gain` and `idle_minimum_advance_deg` set its idle spark trim.
|
||||||
|
Two-stroke jet multipliers retain their existing meaning.
|
||||||
|
`chamber_sections_mm` contains six `[length, inlet diameter, outlet diameter]`
|
||||||
|
sections: header, two diffusers, belly, baffle cone and stinger.
|
||||||
|
|
||||||
|
Required cylinder geometry must be explicit. Optional calibration fields use
|
||||||
|
the typed config defaults; a misspelled field is always an error. Existing files
|
||||||
|
include explicit values so their behavior does not depend on future defaults.
|
||||||
|
|
||||||
|
## Pipe groups and exhaust topologies
|
||||||
|
|
||||||
|
These definitions configure the existing topology builders. They do not yet
|
||||||
|
describe arbitrary graphs of cylinders and junctions. A group's `id` must be
|
||||||
|
unique; `count` repeats its dimensions across adjacent pipes.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 750.0
|
||||||
|
cells = 15
|
||||||
|
```
|
||||||
|
|
||||||
|
The order is significant because the topology builder connects the expanded
|
||||||
|
groups to fixed cylinder and junction ports:
|
||||||
|
|
||||||
|
| `engine.topology` | Pipe order after expansion |
|
||||||
|
| --- | --- |
|
||||||
|
| `collector` | Intake, N runners, N primaries, one tailpipe |
|
||||||
|
| `staged` | Intake, N runners, N primaries, outlet pipe(s), secondary pipe(s) |
|
||||||
|
| `american_v8` | Intake, 8 runners, 8 primaries, 4 secondaries, 2 bank collectors, 2 tailpipes, H crossover |
|
||||||
|
|
||||||
|
The staged inline triple has one secondary; other staged layouts have two.
|
||||||
|
Staged V engines have two independent outlets. The American topology requires
|
||||||
|
eight cylinders in a V layout; setting `junctions.h_pipe = false` closes both
|
||||||
|
ends of the unused crossover and leaves the banks independent.
|
||||||
|
|
||||||
|
A collector outlet can instead use `profile = "silenced"`. Its dimensions and
|
||||||
|
mesh then come from `[silencers]`, `[silencers.resonator]` and
|
||||||
|
`[silencers.muffler]`; omit the group's uniform pipe dimensions. The loader
|
||||||
|
checks expanded pipe counts and mesh limits before construction. Pipe positions
|
||||||
|
continue to use the shared scene routes for rendering and sound sources.
|
||||||
163
engines/big-carb.toml
Normal file
163
engines/big-carb.toml
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "big-carb"
|
||||||
|
name = "8.0 litre / carbureted big-block"
|
||||||
|
category = "V8 / big-block"
|
||||||
|
description = "Cross-plane firing / American 4-2-1 + H / carb fuel transport"
|
||||||
|
order = 29
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 90.0
|
||||||
|
collector_volume_litres = 2.0
|
||||||
|
cylinders = 8
|
||||||
|
initial_rpm = 1120.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 8.0
|
||||||
|
starter_torque_nm = 201.1122
|
||||||
|
throttle_diameter_mm = 85.0
|
||||||
|
topology = "american_v8"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 114.3
|
||||||
|
burn_duration_deg = 65.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 9.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 140.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 36.576
|
||||||
|
exhaust_valve_lift_mm = 10.287
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 570.0
|
||||||
|
intake_open_deg = 350.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 43.434
|
||||||
|
intake_valve_lift_mm = 11.43
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 176.4
|
||||||
|
stroke_mm = 98.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 32.17795
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.03217795
|
||||||
|
inertia_kg_m2 = 0.9653385
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.07
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 800.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
overrun_resume_rpm = 1100.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1769.787
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.4
|
||||||
|
final_drive_ratio = 3.623529
|
||||||
|
frontal_area = 2.0
|
||||||
|
gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "carburetor"
|
||||||
|
pilot_jet_diameter_mm = 0.2337673
|
||||||
|
main_jet_diameter_mm = 4.524528
|
||||||
|
needle_jet_diameter_mm = 4.656493
|
||||||
|
needle_low_scale = 0.6
|
||||||
|
needle_mid_scale = 0.65
|
||||||
|
needle_off_idle_scale = 0.964
|
||||||
|
needle_quarter_scale = 0.64
|
||||||
|
needle_tip_scale = 0.782
|
||||||
|
needle_transition_scale = 0.89
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
bowl_capacity_kg = 0.1
|
||||||
|
bowl_refill_kg_per_s = 0.1
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 3
|
||||||
|
diameter_mm = 70.0
|
||||||
|
length_mm = 180.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 8
|
||||||
|
cells = 2
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
cells = 15
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 750.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondaries"
|
||||||
|
count = 4
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 45.0
|
||||||
|
length_mm = 400.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "bank_collectors"
|
||||||
|
count = 2
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 550.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipes"
|
||||||
|
count = 2
|
||||||
|
cells = 25
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 1250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "crossover"
|
||||||
|
cells = 16
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 780.0
|
||||||
126
engines/busso.toml
Normal file
126
engines/busso.toml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "busso"
|
||||||
|
name = "Busso-inspired / 3.0 V6"
|
||||||
|
category = "V6"
|
||||||
|
description = "Estimated concept calibration / EFI"
|
||||||
|
order = 26
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 6
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 6.0
|
||||||
|
starter_torque_nm = 73.97478
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 93.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 29.76
|
||||||
|
exhaust_valve_lift_mm = 8.370001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 35.34
|
||||||
|
intake_valve_lift_mm = 9.3
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 130.68
|
||||||
|
stroke_mm = 72.6
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 11.83596
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.01183596
|
||||||
|
inertia_kg_m2 = 0.3550789
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 650.978
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 6
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 6
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
126
engines/ej20.toml
Normal file
126
engines/ej20.toml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "ej20"
|
||||||
|
name = "EJ20-inspired / 2.0 flat four NA"
|
||||||
|
category = "Boxer / flat four"
|
||||||
|
description = "Estimated concept calibration / EFI"
|
||||||
|
order = 27
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 180.0
|
||||||
|
collector_volume_litres = 1.0
|
||||||
|
cylinders = 4
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "boxer"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 540.0, 360.0, 180.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 49.85708
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 92.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 29.44
|
||||||
|
exhaust_valve_lift_mm = 8.280001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 34.96
|
||||||
|
intake_valve_lift_mm = 9.2
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 135.0
|
||||||
|
stroke_mm = 75.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 7.977133
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.007977134
|
||||||
|
inertia_kg_m2 = 0.239314
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 438.7423
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 4
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 4
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
159
engines/f20.toml
Normal file
159
engines/f20.toml
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "f20"
|
||||||
|
name = "F20C-inspired / 2.0 high-rev four"
|
||||||
|
category = "Inline four / specials"
|
||||||
|
description = "Unequal primaries / small junctions / staged collectors / EFI"
|
||||||
|
order = 22
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 1.0
|
||||||
|
cylinders = 4
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 180.0, 540.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 49.9353
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 87.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 27.84
|
||||||
|
exhaust_valve_lift_mm = 7.83
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 33.06
|
||||||
|
intake_valve_lift_mm = 8.7
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 151.2
|
||||||
|
stroke_mm = 84.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 7.989648
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.007989648
|
||||||
|
inertia_kg_m2 = 0.2396894
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 9000.0
|
||||||
|
rev_resume_rpm = 8750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 439.4306
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 4
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_4"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_9"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_10"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_11"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 540.0
|
||||||
68
engines/fifty.toml
Normal file
68
engines/fifty.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "fifty"
|
||||||
|
name = "50 cc / little screamer"
|
||||||
|
category = "Two-stroke / 50 cc"
|
||||||
|
description = "Concept single / transported carb fuel"
|
||||||
|
order = 15
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.004006997
|
||||||
|
bore_mm = 40.0
|
||||||
|
carburetor_diameter_mm = 19.0
|
||||||
|
crankcase_clearance_cc = 60.10495
|
||||||
|
exhaust = "racing"
|
||||||
|
exhaust_cell_length_mm = 15.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 26.54048
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.045
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 0.5
|
||||||
|
main_jet_scale = 1.6
|
||||||
|
minimum_burn_s = 0.0005897885
|
||||||
|
needle_jet_scale = 1.0
|
||||||
|
needle_low_scale = 2.72
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 12.0
|
||||||
|
needle_quarter_scale = 1.4
|
||||||
|
needle_tip_scale = 5.0
|
||||||
|
needle_transition_scale = 9.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.7125
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.0
|
||||||
|
rod_mm = 81.09593
|
||||||
|
stroke_mm = 39.8
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 36.86178
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[117.9577, 26.54048, 26.54048], [132.7024, 26.54048, 44.23414], [140.0748, 44.23414, 77.40974], [95.84063, 77.40974, 77.40974], [154.8195, 77.40974, 16.21918], [176.9366, 16.21918, 16.21918]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
165
engines/five.toml
Normal file
165
engines/five.toml
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "five"
|
||||||
|
name = "Audi-inspired / 2.2 inline five"
|
||||||
|
category = "Inline five"
|
||||||
|
description = "Unequal primaries / small junctions / staged collectors / EFI"
|
||||||
|
order = 23
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 1.25
|
||||||
|
cylinders = 5
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 576.0, 144.0, 432.0, 288.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 5.0
|
||||||
|
starter_torque_nm = 55.65237
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 81.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 25.92
|
||||||
|
exhaust_valve_lift_mm = 7.29
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 30.78
|
||||||
|
intake_valve_lift_mm = 8.1
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 155.52
|
||||||
|
stroke_mm = 86.4
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 8.90438
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.008904381
|
||||||
|
inertia_kg_m2 = 0.2671314
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 489.7409
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 5
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_4"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_5"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_11"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_12"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_13"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 540.0
|
||||||
68
engines/fivehundred.toml
Normal file
68
engines/fivehundred.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "fivehundred"
|
||||||
|
name = "500 cc / big single"
|
||||||
|
category = "Two-stroke / 500 cc"
|
||||||
|
description = "Concept single / transported carb fuel"
|
||||||
|
order = 16
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.04002316
|
||||||
|
bore_mm = 86.0
|
||||||
|
carburetor_diameter_mm = 41.0
|
||||||
|
crankcase_clearance_cc = 600.3475
|
||||||
|
exhaust = "racing"
|
||||||
|
exhaust_cell_length_mm = 15.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 57.15746
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.045
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 1.0
|
||||||
|
main_jet_scale = 1.7504
|
||||||
|
minimum_burn_s = 0.001270166
|
||||||
|
needle_jet_scale = 1.06
|
||||||
|
needle_low_scale = 3.04
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 12.0
|
||||||
|
needle_quarter_scale = 1.4
|
||||||
|
needle_tip_scale = 5.0
|
||||||
|
needle_transition_scale = 9.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.75
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.0
|
||||||
|
rod_mm = 174.6478
|
||||||
|
stroke_mm = 86.0
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 79.38537
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[254.0332, 57.15746, 57.15746], [285.7873, 57.15746, 95.26245], [301.6644, 95.26245, 166.7093], [206.402, 166.7093, 166.7093], [333.4185, 166.7093, 34.92956], [381.0498, 34.92956, 34.92956]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
126
engines/flat6.toml
Normal file
126
engines/flat6.toml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "flat6"
|
||||||
|
name = "911-inspired / 3.0 flat six"
|
||||||
|
category = "Boxer / flat six"
|
||||||
|
description = "Estimated concept calibration / EFI"
|
||||||
|
order = 28
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 180.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 6
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "boxer"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 6.0
|
||||||
|
starter_torque_nm = 74.85159
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 95.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 30.4
|
||||||
|
exhaust_valve_lift_mm = 8.55
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 36.1
|
||||||
|
intake_valve_lift_mm = 9.5
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 126.72
|
||||||
|
stroke_mm = 70.4
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 11.97625
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.01197626
|
||||||
|
inertia_kg_m2 = 0.3592876
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 658.694
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 6
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 6
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
126
engines/ltwin.toml
Normal file
126
engines/ltwin.toml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "ltwin"
|
||||||
|
name = "916-inspired / 90-degree V twin"
|
||||||
|
category = "Twins / V"
|
||||||
|
description = "Estimated concept calibration / EFI"
|
||||||
|
order = 20
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 90.0
|
||||||
|
collector_volume_litres = 0.5
|
||||||
|
cylinders = 2
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 2.0
|
||||||
|
starter_torque_nm = 22.90127
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 94.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 30.08
|
||||||
|
exhaust_valve_lift_mm = 8.46
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 35.72
|
||||||
|
intake_valve_lift_mm = 9.4
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 118.8
|
||||||
|
stroke_mm = 66.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 3.664203
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.003664203
|
||||||
|
inertia_kg_m2 = 0.1099261
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 201.5311
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 240.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 2
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 2
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
171
engines/rb26.toml
Normal file
171
engines/rb26.toml
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "rb26"
|
||||||
|
name = "RB26-inspired / 2.6 inline six NA"
|
||||||
|
category = "Inline six"
|
||||||
|
description = "Unequal primaries / small junctions / staged collectors / EFI"
|
||||||
|
order = 24
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 6
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 240.0, 480.0, 120.0, 600.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 6.0
|
||||||
|
starter_torque_nm = 64.21635
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 86.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 27.52
|
||||||
|
exhaust_valve_lift_mm = 7.740001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 32.68
|
||||||
|
intake_valve_lift_mm = 8.6
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 132.66
|
||||||
|
stroke_mm = 73.7
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 10.27462
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.01027462
|
||||||
|
inertia_kg_m2 = 0.3082384
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 565.1038
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 6
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_4"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_5"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_6"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_13"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_14"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_15"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 540.0
|
||||||
152
engines/saab-chambered.toml
Normal file
152
engines/saab-chambered.toml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "saab-chambered"
|
||||||
|
name = "Saab B205 / chambered"
|
||||||
|
category = "Inline four / Saab"
|
||||||
|
description = "Expansion resonator and two-chamber rear muffler"
|
||||||
|
order = 4
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 0.6
|
||||||
|
cylinders = 4
|
||||||
|
initial_rpm = 1200.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 200.0
|
||||||
|
minimum_substeps = 2
|
||||||
|
phase_degrees = [0.0, 180.0, 540.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 3.0
|
||||||
|
starter_torque_nm = 65.0
|
||||||
|
throttle_diameter_mm = 52.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 90.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 8.8
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 2
|
||||||
|
exhaust_valve_diameter_mm = 28.0
|
||||||
|
exhaust_valve_lift_mm = 8.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 2
|
||||||
|
intake_valve_diameter_mm = 32.0
|
||||||
|
intake_valve_lift_mm = 8.5
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 153.0
|
||||||
|
stroke_mm = 78.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 12.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.012
|
||||||
|
inertia_kg_m2 = 0.18
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_rpm = 850.0
|
||||||
|
idle_spark_gain = 0.025
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1300.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 6500.0
|
||||||
|
rev_resume_rpm = 6250.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 300.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.32
|
||||||
|
final_drive_ratio = 4.05
|
||||||
|
frontal_area = 2.2
|
||||||
|
gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1675.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 3.6
|
||||||
|
wheel_radius_mm = 317.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[silencers]
|
||||||
|
cell_length_mm = 35.0
|
||||||
|
connecting_length_mm = 800.0
|
||||||
|
diameter_mm = 60.0
|
||||||
|
enabled = true
|
||||||
|
inlet_length_mm = 1400.0
|
||||||
|
outlet_length_mm = 350.0
|
||||||
|
|
||||||
|
[silencers.resonator]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 400.0
|
||||||
|
loss_coefficient = 0.1
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 2.0
|
||||||
|
type = "expansion"
|
||||||
|
volume_litres = 6.0
|
||||||
|
|
||||||
|
[silencers.muffler]
|
||||||
|
chambers = 2
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 650.0
|
||||||
|
loss_coefficient = 0.35
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 4.0
|
||||||
|
type = "chambered"
|
||||||
|
volume_litres = 18.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 150.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 32.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 34.0
|
||||||
|
length_mm = 280.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
profile = "silenced"
|
||||||
152
engines/saab-expansion.toml
Normal file
152
engines/saab-expansion.toml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "saab-expansion"
|
||||||
|
name = "Saab B205 / expansion silencers"
|
||||||
|
category = "Inline four / Saab"
|
||||||
|
description = "Two single expansion chambers, same body volumes"
|
||||||
|
order = 5
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 0.6
|
||||||
|
cylinders = 4
|
||||||
|
initial_rpm = 1200.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 200.0
|
||||||
|
minimum_substeps = 2
|
||||||
|
phase_degrees = [0.0, 180.0, 540.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 3.0
|
||||||
|
starter_torque_nm = 65.0
|
||||||
|
throttle_diameter_mm = 52.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 90.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 8.8
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 2
|
||||||
|
exhaust_valve_diameter_mm = 28.0
|
||||||
|
exhaust_valve_lift_mm = 8.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 2
|
||||||
|
intake_valve_diameter_mm = 32.0
|
||||||
|
intake_valve_lift_mm = 8.5
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 153.0
|
||||||
|
stroke_mm = 78.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 12.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.012
|
||||||
|
inertia_kg_m2 = 0.18
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_rpm = 850.0
|
||||||
|
idle_spark_gain = 0.025
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1300.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 6500.0
|
||||||
|
rev_resume_rpm = 6250.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 300.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.32
|
||||||
|
final_drive_ratio = 4.05
|
||||||
|
frontal_area = 2.2
|
||||||
|
gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1675.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 3.6
|
||||||
|
wheel_radius_mm = 317.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[silencers]
|
||||||
|
cell_length_mm = 35.0
|
||||||
|
connecting_length_mm = 800.0
|
||||||
|
diameter_mm = 60.0
|
||||||
|
enabled = true
|
||||||
|
inlet_length_mm = 1400.0
|
||||||
|
outlet_length_mm = 350.0
|
||||||
|
|
||||||
|
[silencers.resonator]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 400.0
|
||||||
|
loss_coefficient = 0.1
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 2.0
|
||||||
|
type = "expansion"
|
||||||
|
volume_litres = 6.0
|
||||||
|
|
||||||
|
[silencers.muffler]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 650.0
|
||||||
|
loss_coefficient = 0.35
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 4.0
|
||||||
|
type = "expansion"
|
||||||
|
volume_litres = 18.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 150.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 32.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 34.0
|
||||||
|
length_mm = 280.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
profile = "silenced"
|
||||||
152
engines/saab-open.toml
Normal file
152
engines/saab-open.toml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "saab-open"
|
||||||
|
name = "Saab B205 / open exhaust"
|
||||||
|
category = "Inline four / Saab"
|
||||||
|
description = "Silencers bypassed; naturally aspirated baseline"
|
||||||
|
order = 6
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 0.6
|
||||||
|
cylinders = 4
|
||||||
|
initial_rpm = 1200.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 200.0
|
||||||
|
minimum_substeps = 2
|
||||||
|
phase_degrees = [0.0, 180.0, 540.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 3.0
|
||||||
|
starter_torque_nm = 65.0
|
||||||
|
throttle_diameter_mm = 52.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 90.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 8.8
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 2
|
||||||
|
exhaust_valve_diameter_mm = 28.0
|
||||||
|
exhaust_valve_lift_mm = 8.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 2
|
||||||
|
intake_valve_diameter_mm = 32.0
|
||||||
|
intake_valve_lift_mm = 8.5
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 153.0
|
||||||
|
stroke_mm = 78.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 12.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.012
|
||||||
|
inertia_kg_m2 = 0.18
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_rpm = 850.0
|
||||||
|
idle_spark_gain = 0.025
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1300.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 6500.0
|
||||||
|
rev_resume_rpm = 6250.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 300.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.32
|
||||||
|
final_drive_ratio = 4.05
|
||||||
|
frontal_area = 2.2
|
||||||
|
gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1675.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 3.6
|
||||||
|
wheel_radius_mm = 317.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[silencers]
|
||||||
|
cell_length_mm = 35.0
|
||||||
|
connecting_length_mm = 800.0
|
||||||
|
diameter_mm = 60.0
|
||||||
|
enabled = false
|
||||||
|
inlet_length_mm = 1400.0
|
||||||
|
outlet_length_mm = 350.0
|
||||||
|
|
||||||
|
[silencers.resonator]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 400.0
|
||||||
|
loss_coefficient = 0.1
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 2.0
|
||||||
|
type = "straight_through"
|
||||||
|
volume_litres = 6.0
|
||||||
|
|
||||||
|
[silencers.muffler]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 650.0
|
||||||
|
loss_coefficient = 0.35
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 4.0
|
||||||
|
type = "straight_through"
|
||||||
|
volume_litres = 18.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 150.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 32.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 34.0
|
||||||
|
length_mm = 280.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
profile = "silenced"
|
||||||
152
engines/saab-straight.toml
Normal file
152
engines/saab-straight.toml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "saab-straight"
|
||||||
|
name = "Saab B205 / straight-through"
|
||||||
|
category = "Inline four / Saab"
|
||||||
|
description = "6 L resonator and 18 L straight-through rear muffler"
|
||||||
|
order = 3
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 0.6
|
||||||
|
cylinders = 4
|
||||||
|
initial_rpm = 1200.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 200.0
|
||||||
|
minimum_substeps = 2
|
||||||
|
phase_degrees = [0.0, 180.0, 540.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 3.0
|
||||||
|
starter_torque_nm = 65.0
|
||||||
|
throttle_diameter_mm = 52.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 90.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 8.8
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 2
|
||||||
|
exhaust_valve_diameter_mm = 28.0
|
||||||
|
exhaust_valve_lift_mm = 8.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 2
|
||||||
|
intake_valve_diameter_mm = 32.0
|
||||||
|
intake_valve_lift_mm = 8.5
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 153.0
|
||||||
|
stroke_mm = 78.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 12.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.012
|
||||||
|
inertia_kg_m2 = 0.18
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_rpm = 850.0
|
||||||
|
idle_spark_gain = 0.025
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1300.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 6500.0
|
||||||
|
rev_resume_rpm = 6250.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 300.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.32
|
||||||
|
final_drive_ratio = 4.05
|
||||||
|
frontal_area = 2.2
|
||||||
|
gear_ratios = [3.382716, 1.758025, 1.177778, 0.8938271, 0.6592593]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1675.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 3.6
|
||||||
|
wheel_radius_mm = 317.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[silencers]
|
||||||
|
cell_length_mm = 35.0
|
||||||
|
connecting_length_mm = 800.0
|
||||||
|
diameter_mm = 60.0
|
||||||
|
enabled = true
|
||||||
|
inlet_length_mm = 1400.0
|
||||||
|
outlet_length_mm = 350.0
|
||||||
|
|
||||||
|
[silencers.resonator]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 400.0
|
||||||
|
loss_coefficient = 0.1
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 2.0
|
||||||
|
type = "straight_through"
|
||||||
|
volume_litres = 6.0
|
||||||
|
|
||||||
|
[silencers.muffler]
|
||||||
|
chambers = 1
|
||||||
|
core_diameter_mm = 60.0
|
||||||
|
length_mm = 650.0
|
||||||
|
loss_coefficient = 0.35
|
||||||
|
passage_diameter_mm = 60.0
|
||||||
|
resistance = 4.0
|
||||||
|
type = "straight_through"
|
||||||
|
volume_litres = 18.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 150.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 32.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 4
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 34.0
|
||||||
|
length_mm = 280.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
profile = "silenced"
|
||||||
136
engines/single-450.toml
Normal file
136
engines/single-450.toml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "single-450"
|
||||||
|
name = "450 cc / carbureted four-stroke thumper"
|
||||||
|
category = "Singles / four-stroke"
|
||||||
|
description = "40 mm carb / mild valve timing / fixed idle stop"
|
||||||
|
order = 17
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 0.25
|
||||||
|
cylinders = 1
|
||||||
|
initial_rpm = 1960.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 1.0
|
||||||
|
starter_torque_nm = 11.21926
|
||||||
|
throttle_diameter_mm = 40.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 96.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 145.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 30.72
|
||||||
|
exhaust_valve_lift_mm = 8.640001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 565.0
|
||||||
|
intake_open_deg = 355.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 36.48
|
||||||
|
intake_valve_lift_mm = 9.6
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 111.6
|
||||||
|
stroke_mm = 62.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 1.795081
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.001795081
|
||||||
|
inertia_kg_m2 = 0.05385243
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.045
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1400.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 98.72945
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 240.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "carburetor"
|
||||||
|
pilot_jet_diameter_mm = 0.09349564
|
||||||
|
main_jet_diameter_mm = 2.091829
|
||||||
|
needle_jet_diameter_mm = 2.15284
|
||||||
|
needle_low_scale = 0.944
|
||||||
|
needle_mid_scale = 0.564
|
||||||
|
needle_off_idle_scale = 0.995
|
||||||
|
needle_quarter_scale = 0.835
|
||||||
|
needle_tip_scale = 0.97
|
||||||
|
needle_transition_scale = 1.6
|
||||||
|
needle_upper_scale = 0.65
|
||||||
|
bowl_capacity_kg = 0.1
|
||||||
|
bowl_refill_kg_per_s = 0.1
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
124
engines/single-650.toml
Normal file
124
engines/single-650.toml
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "single-650"
|
||||||
|
name = "650 cc / big thumper"
|
||||||
|
category = "Singles / four-stroke"
|
||||||
|
description = "Estimated concept calibration / EFI"
|
||||||
|
order = 18
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 0.25
|
||||||
|
cylinders = 1
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 1.0
|
||||||
|
starter_torque_nm = 16.29701
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 100.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 32.0
|
||||||
|
exhaust_valve_lift_mm = 9.000001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 38.0
|
||||||
|
intake_valve_lift_mm = 10.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 149.4
|
||||||
|
stroke_mm = 83.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 2.607522
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.002607522
|
||||||
|
inertia_kg_m2 = 0.07822565
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 6500.0
|
||||||
|
rev_resume_rpm = 6250.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 143.4137
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 240.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
171
engines/six-long.toml
Normal file
171
engines/six-long.toml
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "six-long"
|
||||||
|
name = "3.0 litre / long-stroke inline six"
|
||||||
|
category = "Inline six"
|
||||||
|
description = "Unequal primaries / small junctions / staged collectors / EFI"
|
||||||
|
order = 25
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 6
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 240.0, 480.0, 120.0, 600.0, 360.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 6.0
|
||||||
|
starter_torque_nm = 74.93359
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 86.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 27.52
|
||||||
|
exhaust_valve_lift_mm = 7.740001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 32.68
|
||||||
|
intake_valve_lift_mm = 8.6
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 154.8
|
||||||
|
stroke_mm = 86.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 11.98938
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.01198938
|
||||||
|
inertia_kg_m2 = 0.3596812
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 659.4156
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 6
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_4"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_5"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_6"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_13"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_14"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_15"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 540.0
|
||||||
147
engines/triple.toml
Normal file
147
engines/triple.toml
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "triple"
|
||||||
|
name = "1.0 litre / inline triple"
|
||||||
|
category = "Inline three"
|
||||||
|
description = "Unequal primaries / small junctions / staged collectors / EFI"
|
||||||
|
order = 21
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 0.75
|
||||||
|
cylinders = 3
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 480.0, 240.0]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 3.0
|
||||||
|
starter_torque_nm = 24.99852
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 79.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 25.28
|
||||||
|
exhaust_valve_lift_mm = 7.110001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 30.02
|
||||||
|
intake_valve_lift_mm = 7.900001
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 122.4
|
||||||
|
stroke_mm = 68.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 3.999763
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.003999764
|
||||||
|
inertia_kg_m2 = 0.1199929
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 219.987
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 3
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_7"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_8"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
126
engines/twin-270.toml
Normal file
126
engines/twin-270.toml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "twin-270"
|
||||||
|
name = "700 cc / 270-degree parallel twin"
|
||||||
|
category = "Twins / parallel"
|
||||||
|
description = "Estimated concept calibration / EFI"
|
||||||
|
order = 19
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 0.5
|
||||||
|
cylinders = 2
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 2.0
|
||||||
|
starter_torque_nm = 17.09026
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 80.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 25.6
|
||||||
|
exhaust_valve_lift_mm = 7.2
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 30.4
|
||||||
|
intake_valve_lift_mm = 8.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 122.4
|
||||||
|
stroke_mm = 68.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 2.734442
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.002734442
|
||||||
|
inertia_kg_m2 = 0.08203326
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 150.3943
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 240.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 2
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 2
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
126
engines/twin.toml
Normal file
126
engines/twin.toml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "twin"
|
||||||
|
name = "600 cc parallel twin"
|
||||||
|
category = "Twins / parallel"
|
||||||
|
description = "EFI motorcycle baseline"
|
||||||
|
order = 0
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 0.15
|
||||||
|
cylinders = 2
|
||||||
|
initial_rpm = 2000.0
|
||||||
|
initialize_phased_charge = false
|
||||||
|
layout = "inline"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 180.0]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 1.0
|
||||||
|
starter_torque_nm = 18.0
|
||||||
|
throttle_diameter_mm = 26.0
|
||||||
|
topology = "collector"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 75.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 11.5
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 26.0
|
||||||
|
exhaust_valve_lift_mm = 7.5
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 30.0
|
||||||
|
intake_valve_lift_mm = 8.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 112.0
|
||||||
|
stroke_mm = 68.0
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 5.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.003
|
||||||
|
inertia_kg_m2 = 0.04
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1800.0
|
||||||
|
idle_spark_gain = 0.0
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 10000.0
|
||||||
|
rev_resume_rpm = 9750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 80.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 160.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 27.5
|
||||||
|
length_mm = 60.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 2
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 27.5
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 2
|
||||||
|
cells = 6
|
||||||
|
diameter_mm = 28.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe"
|
||||||
|
cells = 10
|
||||||
|
diameter_mm = 28.0
|
||||||
|
length_mm = 400.0
|
||||||
201
engines/v10.toml
Normal file
201
engines/v10.toml
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v10"
|
||||||
|
name = "5.0 litre / even-fire V10"
|
||||||
|
category = "V10"
|
||||||
|
description = "Separate five-cylinder banks / junction collectors / dual outlets"
|
||||||
|
order = 30
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 72.0
|
||||||
|
collector_volume_litres = 2.5
|
||||||
|
cylinders = 10
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 576.0, 432.0, 288.0, 144.0, 648.0001, 504.0, 360.0, 216.0, 72.00003]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 10.0
|
||||||
|
starter_torque_nm = 124.9751
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 92.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 29.44
|
||||||
|
exhaust_valve_lift_mm = 8.280001
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 34.96
|
||||||
|
intake_valve_lift_mm = 9.2
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 135.36
|
||||||
|
stroke_mm = 75.2
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 19.99601
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.01999602
|
||||||
|
inertia_kg_m2 = 0.5998804
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1099.781
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 10
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_4"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_5"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_6"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_7"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_8"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_9"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_10"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_21"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_22"
|
||||||
|
cells = 28
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1380.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_23"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_24"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 540.0
|
||||||
213
engines/v12.toml
Normal file
213
engines/v12.toml
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v12"
|
||||||
|
name = "250-inspired / 3.0 V12"
|
||||||
|
category = "V12"
|
||||||
|
description = "Separate six-cylinder banks / junction collectors / dual outlets"
|
||||||
|
order = 31
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 60.0
|
||||||
|
collector_volume_litres = 3.0
|
||||||
|
cylinders = 12
|
||||||
|
initial_rpm = 1400.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 30.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 600.0, 480.0, 360.0, 240.0, 120.0, 660.0, 540.0, 420.0, 300.0, 180.0, 59.99998]
|
||||||
|
pipe_damping = 0.0
|
||||||
|
plenum_volume_litres = 12.0
|
||||||
|
starter_torque_nm = 73.83022
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "staged"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 73.0
|
||||||
|
burn_duration_deg = 60.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 10.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 390.0
|
||||||
|
exhaust_open_deg = 130.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 23.36
|
||||||
|
exhaust_valve_lift_mm = 6.57
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 590.0
|
||||||
|
intake_open_deg = 340.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 27.74
|
||||||
|
intake_valve_lift_mm = 7.3
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 110.0
|
||||||
|
stroke_mm = 58.8
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 11.81283
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.01181284
|
||||||
|
inertia_kg_m2 = 0.354385
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.05
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 1000.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [15000.0, 40.0]]
|
||||||
|
overrun_cut_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 2400.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 649.7059
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.7
|
||||||
|
final_drive_ratio = 2.7
|
||||||
|
frontal_area = 0.65
|
||||||
|
gear_ratios = [4.1, 2.7, 2.0, 1.55, 1.25, 0.98]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 1400.0
|
||||||
|
primary_reduction = 1.8
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 4
|
||||||
|
diameter_mm = 75.0
|
||||||
|
length_mm = 200.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 12
|
||||||
|
cells = 5
|
||||||
|
diameter_mm = 35.0
|
||||||
|
length_mm = 250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_1"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_2"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_3"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_4"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_5"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_6"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_7"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_8"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_9"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_10"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 520.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_11"
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 585.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primary_12"
|
||||||
|
cells = 13
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 650.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_25"
|
||||||
|
cells = 30
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1500.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipe_26"
|
||||||
|
cells = 28
|
||||||
|
diameter_mm = 60.0
|
||||||
|
length_mm = 1380.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_27"
|
||||||
|
cells = 9
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 420.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondary_28"
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 48.0
|
||||||
|
length_mm = 540.0
|
||||||
151
engines/v8-dual.toml
Normal file
151
engines/v8-dual.toml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v8-dual"
|
||||||
|
name = "American 5.0 / independent banks"
|
||||||
|
category = "V8 / American 5.0"
|
||||||
|
description = "Same 4-2-1 headers with H crossover disconnected"
|
||||||
|
order = 8
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 8
|
||||||
|
initial_rpm = 1000.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 300.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 180.0
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "american_v8"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 101.6
|
||||||
|
burn_duration_deg = 65.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 7.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 140.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 37.0
|
||||||
|
exhaust_valve_lift_mm = 10.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 570.0
|
||||||
|
intake_open_deg = 350.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 45.0
|
||||||
|
intake_valve_lift_mm = 10.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 129.0
|
||||||
|
stroke_mm = 76.2
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 22.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.025
|
||||||
|
inertia_kg_m2 = 1.1
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 750.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1100.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1100.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.4
|
||||||
|
final_drive_ratio = 3.623529
|
||||||
|
frontal_area = 2.0
|
||||||
|
gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 3000.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = false
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 3
|
||||||
|
diameter_mm = 70.0
|
||||||
|
length_mm = 180.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 8
|
||||||
|
cells = 2
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
cells = 15
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 750.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondaries"
|
||||||
|
count = 4
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 45.0
|
||||||
|
length_mm = 400.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "bank_collectors"
|
||||||
|
count = 2
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 550.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipes"
|
||||||
|
count = 2
|
||||||
|
cells = 25
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 1250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "crossover"
|
||||||
|
cells = 16
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 780.0
|
||||||
151
engines/v8-h.toml
Normal file
151
engines/v8-h.toml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v8-h"
|
||||||
|
name = "American 5.0 / 4-2-1 + H"
|
||||||
|
category = "V8 / American 5.0"
|
||||||
|
description = "750 mm primaries, 50 cc junctions, H crossover"
|
||||||
|
order = 7
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 8
|
||||||
|
initial_rpm = 1000.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 300.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 180.0
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "american_v8"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 101.6
|
||||||
|
burn_duration_deg = 65.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 7.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 140.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 37.0
|
||||||
|
exhaust_valve_lift_mm = 10.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 570.0
|
||||||
|
intake_open_deg = 350.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 45.0
|
||||||
|
intake_valve_lift_mm = 10.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 129.0
|
||||||
|
stroke_mm = 76.2
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 22.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.025
|
||||||
|
inertia_kg_m2 = 1.1
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 750.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1100.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1100.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.4
|
||||||
|
final_drive_ratio = 3.623529
|
||||||
|
frontal_area = 2.0
|
||||||
|
gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 3000.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 3
|
||||||
|
diameter_mm = 70.0
|
||||||
|
length_mm = 180.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 8
|
||||||
|
cells = 2
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
cells = 15
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 750.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondaries"
|
||||||
|
count = 4
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 45.0
|
||||||
|
length_mm = 400.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "bank_collectors"
|
||||||
|
count = 2
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 550.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipes"
|
||||||
|
count = 2
|
||||||
|
cells = 25
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 1250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "crossover"
|
||||||
|
cells = 16
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 780.0
|
||||||
151
engines/v8-large.toml
Normal file
151
engines/v8-large.toml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v8-large"
|
||||||
|
name = "American 5.0 / larger primaries"
|
||||||
|
category = "V8 / American 5.0"
|
||||||
|
description = "42 mm x 750 mm primaries with H crossover"
|
||||||
|
order = 11
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 8
|
||||||
|
initial_rpm = 1000.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 300.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 180.0
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "american_v8"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 101.6
|
||||||
|
burn_duration_deg = 65.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 7.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 140.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 37.0
|
||||||
|
exhaust_valve_lift_mm = 10.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 570.0
|
||||||
|
intake_open_deg = 350.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 45.0
|
||||||
|
intake_valve_lift_mm = 10.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 129.0
|
||||||
|
stroke_mm = 76.2
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 22.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.025
|
||||||
|
inertia_kg_m2 = 1.1
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 750.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1100.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1100.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.4
|
||||||
|
final_drive_ratio = 3.623529
|
||||||
|
frontal_area = 2.0
|
||||||
|
gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 3000.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 3
|
||||||
|
diameter_mm = 70.0
|
||||||
|
length_mm = 180.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 8
|
||||||
|
cells = 2
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
cells = 15
|
||||||
|
diameter_mm = 42.0
|
||||||
|
length_mm = 750.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondaries"
|
||||||
|
count = 4
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 45.0
|
||||||
|
length_mm = 400.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "bank_collectors"
|
||||||
|
count = 2
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 550.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipes"
|
||||||
|
count = 2
|
||||||
|
cells = 25
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 1250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "crossover"
|
||||||
|
cells = 16
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 780.0
|
||||||
151
engines/v8-long.toml
Normal file
151
engines/v8-long.toml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v8-long"
|
||||||
|
name = "American 5.0 / long headers"
|
||||||
|
category = "V8 / American 5.0"
|
||||||
|
description = "900 mm primaries with H crossover"
|
||||||
|
order = 10
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 8
|
||||||
|
initial_rpm = 1000.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 300.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 180.0
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "american_v8"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 101.6
|
||||||
|
burn_duration_deg = 65.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 7.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 140.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 37.0
|
||||||
|
exhaust_valve_lift_mm = 10.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 570.0
|
||||||
|
intake_open_deg = 350.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 45.0
|
||||||
|
intake_valve_lift_mm = 10.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 129.0
|
||||||
|
stroke_mm = 76.2
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 22.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.025
|
||||||
|
inertia_kg_m2 = 1.1
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 750.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1100.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1100.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.4
|
||||||
|
final_drive_ratio = 3.623529
|
||||||
|
frontal_area = 2.0
|
||||||
|
gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 3000.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 3
|
||||||
|
diameter_mm = 70.0
|
||||||
|
length_mm = 180.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 8
|
||||||
|
cells = 2
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
cells = 18
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 900.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondaries"
|
||||||
|
count = 4
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 45.0
|
||||||
|
length_mm = 400.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "bank_collectors"
|
||||||
|
count = 2
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 550.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipes"
|
||||||
|
count = 2
|
||||||
|
cells = 25
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 1250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "crossover"
|
||||||
|
cells = 16
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 780.0
|
||||||
151
engines/v8-short.toml
Normal file
151
engines/v8-short.toml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "v8-short"
|
||||||
|
name = "American 5.0 / short headers"
|
||||||
|
category = "V8 / American 5.0"
|
||||||
|
description = "600 mm primaries with H crossover"
|
||||||
|
order = 9
|
||||||
|
cycle = "four_stroke"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
bank_angle_deg = 56.0
|
||||||
|
collector_volume_litres = 1.5
|
||||||
|
cylinders = 8
|
||||||
|
initial_rpm = 1000.0
|
||||||
|
initialize_phased_charge = true
|
||||||
|
layout = "v"
|
||||||
|
maximum_load_torque_nm = 300.0
|
||||||
|
minimum_substeps = 1
|
||||||
|
phase_degrees = [0.0, 450.0, 270.0, 540.0, 630.0, 360.0, 180.0, 89.99998]
|
||||||
|
pipe_damping = 1.0
|
||||||
|
plenum_volume_litres = 4.0
|
||||||
|
starter_torque_nm = 180.0
|
||||||
|
throttle_diameter_mm = 65.0
|
||||||
|
topology = "american_v8"
|
||||||
|
|
||||||
|
[cylinder]
|
||||||
|
afr = 14.7
|
||||||
|
ambient_temperature_k = 300.0
|
||||||
|
bore_mm = 101.6
|
||||||
|
burn_duration_deg = 65.0
|
||||||
|
burn_start_deg = 5.0
|
||||||
|
compression = 7.0
|
||||||
|
energy_variation = 0.05
|
||||||
|
exhaust_close_deg = 370.0
|
||||||
|
exhaust_open_deg = 140.0
|
||||||
|
exhaust_valve_count = 1
|
||||||
|
exhaust_valve_diameter_mm = 37.0
|
||||||
|
exhaust_valve_lift_mm = 10.0
|
||||||
|
fuel_lhv_j_per_kg = 4.4e+07
|
||||||
|
heat_transfer = 100.0
|
||||||
|
intake_close_deg = 570.0
|
||||||
|
intake_open_deg = 350.0
|
||||||
|
intake_valve_count = 1
|
||||||
|
intake_valve_diameter_mm = 45.0
|
||||||
|
intake_valve_lift_mm = 10.0
|
||||||
|
misfire_probability = 0.0
|
||||||
|
rod_mm = 129.0
|
||||||
|
stroke_mm = 76.2
|
||||||
|
wall_area = 0.02
|
||||||
|
wiebe_a = 5.0
|
||||||
|
wiebe_m = 2.0
|
||||||
|
|
||||||
|
[crankshaft]
|
||||||
|
friction_constant_nm = 22.0
|
||||||
|
friction_quadratic_nm_s2 = 0.0
|
||||||
|
friction_viscous_nm_s = 0.025
|
||||||
|
inertia_kg_m2 = 1.1
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_base_opening = 0.08
|
||||||
|
idle_control = "feedback"
|
||||||
|
idle_ki = 4e-05
|
||||||
|
idle_kp = 0.0001
|
||||||
|
idle_max_opening = 0.25
|
||||||
|
idle_minimum_advance_deg = -5.0
|
||||||
|
idle_rpm = 750.0
|
||||||
|
idle_spark_gain = 0.02
|
||||||
|
ignition_map = [[0.0, 10.0], [750.0, 12.0], [2500.0, 28.0], [4000.0, 32.0], [6000.0, 30.0]]
|
||||||
|
overrun_cut_rpm = 2000.0
|
||||||
|
overrun_fuel_cut_enabled = true
|
||||||
|
overrun_ignition_cut_enabled = false
|
||||||
|
overrun_resume_rpm = 1100.0
|
||||||
|
power_afr = 12.8
|
||||||
|
rev_limit_enabled = true
|
||||||
|
rev_limit_fuel_cut_enabled = true
|
||||||
|
rev_limit_rpm = 7000.0
|
||||||
|
rev_resume_rpm = 6750.0
|
||||||
|
stoichiometric_afr = 14.7
|
||||||
|
throttle_closing_rate = 0.0
|
||||||
|
throttle_closing_time_constant_s = 0.0
|
||||||
|
throttle_rate = 1000.0
|
||||||
|
throttle_time_constant_s = 0.001
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
air_density = 1.225
|
||||||
|
clutch_capacity = 1100.0
|
||||||
|
clutch_engagement_rate = 8.0
|
||||||
|
drag_coefficient = 0.4
|
||||||
|
final_drive_ratio = 3.623529
|
||||||
|
frontal_area = 2.0
|
||||||
|
gear_ratios = [3.35, 1.93, 1.29, 1.0, 0.68]
|
||||||
|
gravity = 9.80665
|
||||||
|
mass_kg = 3000.0
|
||||||
|
primary_reduction = 1.0
|
||||||
|
rolling_resistance = 0.015
|
||||||
|
rotational_inertia_kg_m2 = 1.2
|
||||||
|
wheel_radius_mm = 320.0
|
||||||
|
|
||||||
|
[fuel]
|
||||||
|
system = "injection"
|
||||||
|
|
||||||
|
[junctions]
|
||||||
|
h_pipe = true
|
||||||
|
junction_volume_cc = 50.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "intake"
|
||||||
|
cells = 3
|
||||||
|
diameter_mm = 70.0
|
||||||
|
length_mm = 180.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "runners"
|
||||||
|
count = 8
|
||||||
|
cells = 2
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 120.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "primaries"
|
||||||
|
count = 8
|
||||||
|
cells = 12
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 600.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "secondaries"
|
||||||
|
count = 4
|
||||||
|
cells = 8
|
||||||
|
diameter_mm = 45.0
|
||||||
|
length_mm = 400.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "bank_collectors"
|
||||||
|
count = 2
|
||||||
|
cells = 11
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 550.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "tailpipes"
|
||||||
|
count = 2
|
||||||
|
cells = 25
|
||||||
|
diameter_mm = 50.0
|
||||||
|
length_mm = 1250.0
|
||||||
|
|
||||||
|
[[pipes]]
|
||||||
|
id = "crossover"
|
||||||
|
cells = 16
|
||||||
|
diameter_mm = 38.0
|
||||||
|
length_mm = 780.0
|
||||||
68
engines/yz-fat.toml
Normal file
68
engines/yz-fat.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "yz-fat"
|
||||||
|
name = "125 / wide belly chamber"
|
||||||
|
category = "Two-stroke / 125 cc"
|
||||||
|
description = "Estimated chamber geometry / same 125 cc engine"
|
||||||
|
order = 14
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.01
|
||||||
|
bore_mm = 54.0
|
||||||
|
carburetor_diameter_mm = 38.1
|
||||||
|
crankcase_clearance_cc = 150.0
|
||||||
|
exhaust = "racing"
|
||||||
|
exhaust_cell_length_mm = 10.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 36.0
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.03
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 1.0
|
||||||
|
main_jet_scale = 1.6
|
||||||
|
minimum_burn_s = 0.0008
|
||||||
|
needle_jet_scale = 1.0
|
||||||
|
needle_low_scale = 2.9
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 24.0
|
||||||
|
needle_quarter_scale = 1.4
|
||||||
|
needle_tip_scale = 6.5
|
||||||
|
needle_transition_scale = 9.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.5475
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.55
|
||||||
|
rod_mm = 110.0
|
||||||
|
stroke_mm = 54.5
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 50.0
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[160.0, 42.48, 42.48], [180.0, 42.48, 70.8], [190.0, 70.8, 123.9], [130.0, 123.9, 123.9], [210.0, 123.9, 25.96], [240.0, 25.96, 25.96]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
68
engines/yz-racing.toml
Normal file
68
engines/yz-racing.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "yz-racing"
|
||||||
|
name = "YZ125 / expansion chamber"
|
||||||
|
category = "Two-stroke / 125 cc"
|
||||||
|
description = "Racing pipe / calibrated carb / 3% idle stop"
|
||||||
|
order = 1
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.01
|
||||||
|
bore_mm = 54.0
|
||||||
|
carburetor_diameter_mm = 38.1
|
||||||
|
crankcase_clearance_cc = 150.0
|
||||||
|
exhaust = "racing"
|
||||||
|
exhaust_cell_length_mm = 10.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 36.0
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.03
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 1.0
|
||||||
|
main_jet_scale = 1.6
|
||||||
|
minimum_burn_s = 0.0008
|
||||||
|
needle_jet_scale = 1.0
|
||||||
|
needle_low_scale = 2.9
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 24.0
|
||||||
|
needle_quarter_scale = 1.4
|
||||||
|
needle_tip_scale = 6.5
|
||||||
|
needle_transition_scale = 9.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.5475
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.55
|
||||||
|
rod_mm = 110.0
|
||||||
|
stroke_mm = 54.5
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 50.0
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[160.0, 36.0, 36.0], [180.0, 36.0, 60.0], [190.0, 60.0, 105.0], [130.0, 105.0, 105.0], [210.0, 105.0, 22.0], [240.0, 22.0, 22.0]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
68
engines/yz-short.toml
Normal file
68
engines/yz-short.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "yz-short"
|
||||||
|
name = "125 / short high-RPM chamber"
|
||||||
|
category = "Two-stroke / 125 cc"
|
||||||
|
description = "Estimated chamber geometry / same 125 cc engine"
|
||||||
|
order = 13
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.01
|
||||||
|
bore_mm = 54.0
|
||||||
|
carburetor_diameter_mm = 38.1
|
||||||
|
crankcase_clearance_cc = 150.0
|
||||||
|
exhaust = "racing"
|
||||||
|
exhaust_cell_length_mm = 10.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 36.0
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.03
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 1.0
|
||||||
|
main_jet_scale = 1.6
|
||||||
|
minimum_burn_s = 0.0008
|
||||||
|
needle_jet_scale = 1.0
|
||||||
|
needle_low_scale = 2.9
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 24.0
|
||||||
|
needle_quarter_scale = 1.4
|
||||||
|
needle_tip_scale = 6.5
|
||||||
|
needle_transition_scale = 9.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.5475
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.55
|
||||||
|
rod_mm = 110.0
|
||||||
|
stroke_mm = 54.5
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 50.0
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[131.2, 36.0, 36.0], [147.6, 36.0, 60.0], [155.8, 60.0, 105.0], [106.6, 105.0, 105.0], [172.2, 105.0, 22.0], [196.8, 22.0, 22.0]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
68
engines/yz-straight.toml
Normal file
68
engines/yz-straight.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "yz-straight"
|
||||||
|
name = "YZ125 / straight exhaust"
|
||||||
|
category = "Two-stroke / 125 cc"
|
||||||
|
description = "Straight exhaust / calibrated pilot and needle / 3% idle stop"
|
||||||
|
order = 2
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.01
|
||||||
|
bore_mm = 54.0
|
||||||
|
carburetor_diameter_mm = 38.1
|
||||||
|
crankcase_clearance_cc = 150.0
|
||||||
|
exhaust = "straight"
|
||||||
|
exhaust_cell_length_mm = 10.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 36.0
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.03
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 1.0
|
||||||
|
main_jet_scale = 1.6
|
||||||
|
minimum_burn_s = 0.0008
|
||||||
|
needle_jet_scale = 1.0
|
||||||
|
needle_low_scale = 2.9
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 50.0
|
||||||
|
needle_quarter_scale = 1.42
|
||||||
|
needle_tip_scale = 6.0
|
||||||
|
needle_transition_scale = 14.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.534
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.1
|
||||||
|
rod_mm = 110.0
|
||||||
|
stroke_mm = 54.5
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 50.0
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[160.0, 36.0, 36.0], [180.0, 36.0, 60.0], [190.0, 60.0, 105.0], [130.0, 105.0, 105.0], [210.0, 105.0, 22.0], [240.0, 22.0, 22.0]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
68
engines/yz-torque.toml
Normal file
68
engines/yz-torque.toml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Engine definition. Dimensional keys state their units.
|
||||||
|
schema_version = 1
|
||||||
|
id = "yz-torque"
|
||||||
|
name = "125 / long torque chamber"
|
||||||
|
category = "Two-stroke / 125 cc"
|
||||||
|
description = "Estimated chamber geometry / same 125 cc engine"
|
||||||
|
order = 12
|
||||||
|
cycle = "two_stroke"
|
||||||
|
|
||||||
|
[two_stroke]
|
||||||
|
initial_rpm = 3000.0
|
||||||
|
starter_torque_nm = 8.0
|
||||||
|
maximum_load_torque_nm = 10.0
|
||||||
|
crankshaft_inertia_kg_m2 = 0.01
|
||||||
|
bore_mm = 54.0
|
||||||
|
carburetor_diameter_mm = 38.1
|
||||||
|
crankcase_clearance_cc = 150.0
|
||||||
|
exhaust = "racing"
|
||||||
|
exhaust_cell_length_mm = 10.0
|
||||||
|
exhaust_duration_deg = 190.0
|
||||||
|
exhaust_width_mm = 36.0
|
||||||
|
friction_bar = 0.4
|
||||||
|
friction_bar_per_mps = 0.09
|
||||||
|
friction_bar_per_mps2 = 0.0009
|
||||||
|
idle_minimum_advance_deg = -10.0
|
||||||
|
idle_slide_opening = 0.03
|
||||||
|
idle_spark_gain = 0.05
|
||||||
|
jet_scale = 1.0
|
||||||
|
main_jet_scale = 1.6
|
||||||
|
minimum_burn_s = 0.0008
|
||||||
|
needle_jet_scale = 1.0
|
||||||
|
needle_low_scale = 2.9
|
||||||
|
needle_mid_scale = 0.8
|
||||||
|
needle_off_idle_scale = 24.0
|
||||||
|
needle_quarter_scale = 1.4
|
||||||
|
needle_tip_scale = 6.5
|
||||||
|
needle_transition_scale = 9.0
|
||||||
|
needle_upper_scale = 0.76
|
||||||
|
pilot_jet_scale = 0.5475
|
||||||
|
pilot_tip_scale = 1.0
|
||||||
|
pilot_transition_scale = 1.55
|
||||||
|
rod_mm = 110.0
|
||||||
|
stroke_mm = 54.5
|
||||||
|
transfer_duration_deg = 130.0
|
||||||
|
transfer_width_mm = 50.0
|
||||||
|
trapped_compression = 8.2
|
||||||
|
chamber_sections_mm = [[188.8, 36.0, 36.0], [212.4, 36.0, 60.0], [224.2, 60.0, 105.0], [153.4, 105.0, 105.0], [247.8, 105.0, 22.0], [283.2, 22.0, 22.0]]
|
||||||
|
|
||||||
|
[ecu]
|
||||||
|
idle_control = "fixed"
|
||||||
|
idle_rpm = 3000.0
|
||||||
|
idle_max_opening = 0.3
|
||||||
|
throttle_time_constant_s = 0.0002
|
||||||
|
throttle_rate = 5000.0
|
||||||
|
rev_limit_enabled = false
|
||||||
|
rev_limit_fuel_cut_enabled = false
|
||||||
|
overrun_cut_rpm = 5000.0
|
||||||
|
overrun_resume_rpm = 3500.0
|
||||||
|
overrun_fuel_cut_enabled = false
|
||||||
|
overrun_ignition_cut_enabled = true
|
||||||
|
ignition_map = [[0, 10], [3000, 18], [6000, 24], [9000, 18], [11000, 12], [13000, 6], [15000, 2], [18000, 0]]
|
||||||
|
|
||||||
|
[vehicle]
|
||||||
|
primary_reduction = 3.368421
|
||||||
|
final_drive_ratio = 3.769231
|
||||||
|
mass_kg = 170
|
||||||
|
wheel_radius_mm = 331.3
|
||||||
|
gear_ratios = [2.384615, 1.933333, 1.588235, 1.352941, 1.176471, 1.055556]
|
||||||
@@ -4,6 +4,7 @@ namespace fluidsim::port {
|
|||||||
|
|
||||||
struct AudioFrame { float left{}, right{}; };
|
struct AudioFrame { float left{}, right{}; };
|
||||||
struct Vec3 { float x{}, y{}, z{}; };
|
struct Vec3 { float x{}, y{}, z{}; };
|
||||||
|
struct AudioListener { Vec3 position{}, right{1,0,0}; };
|
||||||
|
|
||||||
// Simple flow-derivative source used by the Helmholtz and pulse scenarios.
|
// Simple flow-derivative source used by the Helmholtz and pulse scenarios.
|
||||||
class SoundProcessor {
|
class SoundProcessor {
|
||||||
|
|||||||
@@ -5,21 +5,30 @@
|
|||||||
|
|
||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
|
|
||||||
// Lumped slide-carburetor model for the crankcase-scavenged two-stroke. The
|
// Lumped carburetor shared by two-stroke and four-stroke scenarios. The
|
||||||
// jets use liquid-orifice flow from a float bowl into the measured venturi
|
// jets use liquid-orifice flow from a float bowl into the measured venturi
|
||||||
// depression. Delivered fuel is injected into the downstream fluid volume and
|
// depression. Delivered fuel is injected into the downstream fluid volume and
|
||||||
// is then transported as a conserved species; this class never doses a cylinder.
|
// is then transported as a conserved species; this class never doses a cylinder.
|
||||||
class Carburetor {
|
struct CarburetorConfig {
|
||||||
public:
|
|
||||||
float fuel_density{740.0f};
|
float fuel_density{740.0f};
|
||||||
float pilot_jet_diameter{0.00040f}, main_jet_diameter{0.00150f};
|
float pilot_jet_diameter{0.00040f}, main_jet_diameter{0.00150f};
|
||||||
float needle_jet_diameter{0.00190f};
|
float needle_jet_diameter{0.00190f};
|
||||||
|
// Needle annulus area corrections at configured slide positions; jets meter
|
||||||
|
// liquid fuel from measured pressure, without AFR feedback.
|
||||||
|
float needle_low_scale{1.f}, needle_mid_scale{1.f};
|
||||||
|
float needle_off_idle_scale{1.f}, needle_transition_scale{1.f}, needle_tip_scale{1.f};
|
||||||
|
float needle_quarter_scale{1.f}, needle_upper_scale{1.f};
|
||||||
|
float pilot_transition_scale{1.f}, pilot_tip_scale{1.f};
|
||||||
float pilot_discharge_coefficient{0.62f}, main_discharge_coefficient{0.72f};
|
float pilot_discharge_coefficient{0.62f}, main_discharge_coefficient{0.72f};
|
||||||
float bowl_head_pressure{};
|
float bowl_head_pressure{};
|
||||||
float film_fraction{0.28f}, film_time_constant{0.11f};
|
float film_fraction{0.28f}, film_time_constant{0.11f};
|
||||||
float bowl_capacity{0.008f}, bowl_refill_rate{0.0025f}; // kg, kg/s
|
float bowl_capacity{0.008f}, bowl_refill_rate{0.0025f}; // kg, kg/s
|
||||||
float overrun_rpm{4200.0f}, overrun_exit_rpm{3300.0f};
|
float overrun_rpm{4200.0f}, overrun_exit_rpm{3300.0f};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Carburetor : public CarburetorConfig {
|
||||||
|
public:
|
||||||
explicit Carburetor(float throat_diameter = 0.0381f);
|
explicit Carburetor(float throat_diameter = 0.0381f);
|
||||||
|
|
||||||
// air_mass_flow is positive from atmosphere toward the engine.
|
// air_mass_flow is positive from atmosphere toward the engine.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/ScenarioSnapshot.hpp"
|
||||||
#include "FluidSim/DynoCurve.hpp"
|
#include "FluidSim/DynoCurve.hpp"
|
||||||
#include "FluidSim/Desktop/PedalControl.hpp"
|
#include "FluidSim/Desktop/PedalControl.hpp"
|
||||||
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
|
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
|
||||||
@@ -22,6 +22,7 @@ public:
|
|||||||
void nudge_pedal(float amount) {pedal_.set(pedal_.value()+amount);}
|
void nudge_pedal(float amount) {pedal_.set(pedal_.value()+amount);}
|
||||||
void update_pedal(float dt,bool raise,bool lower) {if(!pedal_drag_)pedal_.advance(dt,raise,lower);}
|
void update_pedal(float dt,bool raise,bool lower) {if(!pedal_drag_)pedal_.advance(dt,raise,lower);}
|
||||||
bool handle_event(const sf::Event&,sf::Vector2u);
|
bool handle_event(const sf::Event&,sf::Vector2u);
|
||||||
|
AudioListener listener(const ScenarioSnapshot&,sf::Vector2u);
|
||||||
void draw(sf::RenderWindow&,const sf::Font*,const ScenarioSnapshot&,const PlaybackInfo&);
|
void draw(sf::RenderWindow&,const sf::Font*,const ScenarioSnapshot&,const PlaybackInfo&);
|
||||||
private:
|
private:
|
||||||
DynoCurve dyno_;
|
DynoCurve dyno_;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/ScenarioSnapshot.hpp"
|
||||||
#include "FluidSim/Desktop/OrbitCamera.hpp"
|
#include "FluidSim/Desktop/OrbitCamera.hpp"
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ namespace fluidsim::port::desktop {
|
|||||||
struct OrbitCamera {
|
struct OrbitCamera {
|
||||||
Vec3 target{.12f,.25f,0};
|
Vec3 target{.12f,.25f,0};
|
||||||
float yaw{-28},pitch{24},distance{2.2f};
|
float yaw{-28},pitch{24},distance{2.2f};
|
||||||
|
AudioListener listener(float distance_scale=1, float target_offset_x=0) const {
|
||||||
|
const float y=yaw*.017453293f,p=pitch*.017453293f,d=distance*distance_scale;
|
||||||
|
// Inverse of the renderer's pitch/yaw world-to-view rotation.
|
||||||
|
return {{target.x+target_offset_x-std::sin(y)*std::cos(p)*d,
|
||||||
|
target.y+std::sin(p)*d,target.z+std::cos(y)*std::cos(p)*d},
|
||||||
|
{std::cos(y),0,std::sin(y)}};
|
||||||
|
}
|
||||||
void reset(float aspect) {target={.12f,.25f,0};yaw=-28;pitch=24;distance=std::max(.65f,1.6f/aspect)/.414214f;}
|
void reset(float aspect) {target={.12f,.25f,0};yaw=-28;pitch=24;distance=std::max(.65f,1.6f/aspect)/.414214f;}
|
||||||
void orbit(sf::Vector2i delta) {yaw+=delta.x*.35f;pitch=std::clamp(pitch+delta.y*.3f,-75.f,80.f);}
|
void orbit(sf::Vector2i delta) {yaw+=delta.x*.35f;pitch=std::clamp(pitch+delta.y*.3f,-75.f,80.f);}
|
||||||
void pan(sf::Vector2i delta,float height) {
|
void pan(sf::Vector2i delta,float height) {
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
#include "FluidSim/TwoStrokeScenario.hpp"
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
// Shared desktop controls/snapshot contract. Two-stroke access is serialized;
|
// Shared desktop controls and snapshots. Each simulation owns its state lock.
|
||||||
// the twin already protects its own simulation state.
|
|
||||||
class DesktopScenario {
|
class DesktopScenario {
|
||||||
public:
|
public:
|
||||||
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, V8ExhaustConfig v8 = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)) {
|
explicit DesktopScenario(const ScenarioExample& e)
|
||||||
|
: DesktopScenario(e.kind,e.two,e.exhaust,e.junctions,e.engine,e.title) {}
|
||||||
|
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, JunctionConfig junctions = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)) {
|
||||||
if (kind == ScenarioKind::TwoStroke)
|
if (kind == ScenarioKind::TwoStroke)
|
||||||
two_ = std::make_unique<TwoStrokeScenario>(44100, config);
|
two_ = std::make_unique<TwoStrokeScenario>(44100, config);
|
||||||
else
|
else {
|
||||||
twin_ = std::make_unique<FourStrokeScenario>(44100, kind == ScenarioKind::SaabB205 ?
|
if(!engine.cylinders)engine=preset_engine_definition(kind==ScenarioKind::SaabB205?
|
||||||
FourStrokePreset::SaabB205 : kind == ScenarioKind::AmericanV8 ? FourStrokePreset::AmericanV8 : FourStrokePreset::Twin, exhaust, v8,engine);
|
FourStrokePreset::SaabB205:kind==ScenarioKind::AmericanV8?FourStrokePreset::AmericanV8:FourStrokePreset::Twin).engine;
|
||||||
|
four_ = std::make_unique<FourStrokeScenario>(44100,std::move(engine),exhaust,junctions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AudioFrame process() {
|
AudioFrame process() {
|
||||||
if (twin_)
|
if (four_)
|
||||||
return twin_->process();
|
return four_->process();
|
||||||
const std::lock_guard lock(mutex_);
|
const std::lock_guard lock(mutex_);
|
||||||
return two_->process();
|
return two_->process();
|
||||||
}
|
}
|
||||||
@@ -33,8 +37,11 @@ public:
|
|||||||
void set_audio_scene(OutdoorScene scene) {
|
void set_audio_scene(OutdoorScene scene) {
|
||||||
visit([&](auto& scenario) { scenario.set_audio_scene(scene); });
|
visit([&](auto& scenario) { scenario.set_audio_scene(scene); });
|
||||||
}
|
}
|
||||||
|
void set_listener(AudioListener listener) {
|
||||||
|
visit([&](auto& scenario) { scenario.set_listener(listener); });
|
||||||
|
}
|
||||||
ScenarioSnapshot snapshot() const {
|
ScenarioSnapshot snapshot() const {
|
||||||
if (twin_) { auto s=twin_->snapshot();s.example_name=name_;return s; }
|
if (four_) { auto s=four_->snapshot();s.example_name=name_;return s; }
|
||||||
const std::lock_guard lock(mutex_);
|
const std::lock_guard lock(mutex_);
|
||||||
const auto source = two_->snapshot();
|
const auto source = two_->snapshot();
|
||||||
ScenarioSnapshot s;
|
ScenarioSnapshot s;
|
||||||
@@ -65,6 +72,7 @@ public:
|
|||||||
s.valve1_exhaust_area = source.exhaust_area;
|
s.valve1_exhaust_area = source.exhaust_area;
|
||||||
s.reed_open = source.reed_open;
|
s.reed_open = source.reed_open;
|
||||||
s.fuel_cut = source.fuel_cut;
|
s.fuel_cut = source.fuel_cut;
|
||||||
|
s.ignition_cut = source.ignition_cut;
|
||||||
s.idle_control = two_->ecu.idle_active();
|
s.idle_control = two_->ecu.idle_active();
|
||||||
s.rev_limit1 = two_->ecu.rev_limited();
|
s.rev_limit1 = two_->ecu.rev_limited();
|
||||||
s.audio_scene = source.audio_scene;
|
s.audio_scene = source.audio_scene;
|
||||||
@@ -77,11 +85,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
template<class Function> void visit(Function&& function) {
|
template<class Function> void visit(Function&& function) {
|
||||||
const std::lock_guard lock(mutex_);
|
const std::lock_guard lock(mutex_);
|
||||||
if (twin_) function(*twin_);
|
if (four_) function(*four_);
|
||||||
else function(*two_);
|
else function(*two_);
|
||||||
}
|
}
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::unique_ptr<FourStrokeTwinScenario> twin_;
|
std::unique_ptr<FourStrokeScenario> four_;
|
||||||
std::unique_ptr<TwoStrokeScenario> two_;
|
std::unique_ptr<TwoStrokeScenario> two_;
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
|
|
||||||
// Ideal sensors and injectors; calibration parameters are not engine measurements.
|
// Ideal sensors and injectors; calibration parameters are not engine measurements.
|
||||||
class Ecu {
|
struct EcuConfig {
|
||||||
public:
|
|
||||||
enum class IdleControl { Feedback, FixedOpening };
|
enum class IdleControl { Feedback, FixedOpening };
|
||||||
IdleControl idle_control{IdleControl::Feedback};
|
IdleControl idle_control{IdleControl::Feedback};
|
||||||
struct IgnitionPoint { float rpm, degrees_btdc; };
|
struct IgnitionPoint { float rpm, degrees_btdc; };
|
||||||
@@ -18,18 +17,25 @@ public:
|
|||||||
float throttle_closing_time_constant{}, throttle_closing_rate{};
|
float throttle_closing_time_constant{}, throttle_closing_rate{};
|
||||||
float rev_limit_rpm{10000.0f}, rev_resume_rpm{9750.0f};
|
float rev_limit_rpm{10000.0f}, rev_resume_rpm{9750.0f};
|
||||||
bool rev_limit_enabled{true};
|
bool rev_limit_enabled{true};
|
||||||
|
bool rev_limit_fuel_cut_enabled{true};
|
||||||
float overrun_cut_rpm{3500.0f}, overrun_resume_rpm{2400.0f};
|
float overrun_cut_rpm{3500.0f}, overrun_resume_rpm{2400.0f};
|
||||||
bool overrun_fuel_cut_enabled{true};
|
bool overrun_fuel_cut_enabled{true};
|
||||||
|
bool overrun_ignition_cut_enabled{false};
|
||||||
float idle_spark_gain{0.0f}, idle_minimum_advance{-5.0f}; // degrees/RPM, degrees BTDC
|
float idle_spark_gain{0.0f}, idle_minimum_advance{-5.0f}; // degrees/RPM, degrees BTDC
|
||||||
float stoichiometric_afr{14.7f}, power_afr{12.8f};
|
float stoichiometric_afr{14.7f}, power_afr{12.8f};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Ecu : public EcuConfig {
|
||||||
|
public:
|
||||||
void step(float dt, float pedal, float rpm);
|
void step(float dt, float pedal, float rpm);
|
||||||
float fuel_for_air(float trapped_air_mass) const;
|
float fuel_for_air(float trapped_air_mass) const;
|
||||||
float throttle_opening() const { return opening_; }
|
float throttle_opening() const { return opening_; }
|
||||||
float throttle_target() const { return target_; }
|
float throttle_target() const { return target_; }
|
||||||
float target_afr() const { return afr_; }
|
float target_afr() const { return afr_; }
|
||||||
float spark_advance() const { return spark_; }
|
float spark_advance() const { return spark_; }
|
||||||
bool fuel_cut() const { return limiter_ || (overrun_ && overrun_fuel_cut_enabled) || fault_; }
|
bool fuel_cut() const { return (limiter_ && rev_limit_fuel_cut_enabled) || (overrun_ && overrun_fuel_cut_enabled) || fault_; }
|
||||||
|
bool ignition_cut() const { return limiter_ || (overrun_ && overrun_ignition_cut_enabled) || fault_; }
|
||||||
bool rev_limited() const { return limiter_; }
|
bool rev_limited() const { return limiter_; }
|
||||||
bool idle_active() const { return idle_; }
|
bool idle_active() const { return idle_; }
|
||||||
bool sensor_fault() const { return fault_; }
|
bool sensor_fault() const { return fault_; }
|
||||||
|
|||||||
@@ -10,12 +10,16 @@ namespace fluidsim::port {
|
|||||||
|
|
||||||
class Ecu;
|
class Ecu;
|
||||||
|
|
||||||
class Crankshaft {
|
struct CrankshaftConfig {
|
||||||
public:
|
float inertia{0.2f};
|
||||||
float omega{400.0f * 2.0f * pi / 60.0f};
|
|
||||||
float angle{}, previous_angle{}, inertia{0.2f};
|
|
||||||
float friction_constant{}, friction_viscous{};
|
float friction_constant{}, friction_viscous{};
|
||||||
float friction_quadratic{}; // Nm / (rad/s)^2, dissipative windage term
|
float friction_quadratic{}; // Nm / (rad/s)^2, dissipative windage term
|
||||||
|
};
|
||||||
|
|
||||||
|
class Crankshaft : public CrankshaftConfig {
|
||||||
|
public:
|
||||||
|
float omega{400.0f * 2.0f * pi / 60.0f};
|
||||||
|
float angle{}, previous_angle{};
|
||||||
float load_torque{}, drivetrain_torque{};
|
float load_torque{}, drivetrain_torque{};
|
||||||
float average_power{}, average_torque{};
|
float average_power{}, average_torque{};
|
||||||
|
|
||||||
@@ -28,18 +32,22 @@ private:
|
|||||||
float power_sum_{}, torque_sum_{};
|
float power_sum_{}, torque_sum_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class EngineCylinder : public Component {
|
struct CylinderConfig {
|
||||||
public:
|
float intake_open{340}, intake_close{590}, exhaust_open{130}, exhaust_close{390};
|
||||||
Ecu* ecu{}; // Optional ideal electronic fuel/ignition controller.
|
|
||||||
bool transported_fuel{}; // Upstream carb meters the fuel species.
|
|
||||||
float bore{0.075f}, stroke{0.068f}, rod{0.112f}, compression{11.5f};
|
float bore{0.075f}, stroke{0.068f}, rod{0.112f}, compression{11.5f};
|
||||||
float intake_valve_diameter{0.030f}, exhaust_valve_diameter{0.028f};
|
float intake_valve_diameter{0.030f}, exhaust_valve_diameter{0.028f};
|
||||||
int intake_valve_count{1}, exhaust_valve_count{1};
|
int intake_valve_count{1}, exhaust_valve_count{1};
|
||||||
float intake_valve_lift{0.005f}, exhaust_valve_lift{0.005f};
|
float intake_valve_lift{0.005f}, exhaust_valve_lift{0.005f};
|
||||||
float phase{};
|
|
||||||
float spark_advance{20.0f}, wiebe_a{5.0f}, wiebe_m{2.0f}, burn_duration{60.0f}, burn_start{5.0f};
|
float spark_advance{20.0f}, wiebe_a{5.0f}, wiebe_m{2.0f}, burn_duration{60.0f}, burn_start{5.0f};
|
||||||
float afr{14.7f}, fuel_lhv{44.0e6f}, energy_variation{0.05f}, misfire_probability{};
|
float afr{14.7f}, fuel_lhv{44.0e6f}, energy_variation{0.05f}, misfire_probability{};
|
||||||
float wall_area{0.02f}, heat_transfer{100.0f}, ambient_temperature{300.0f};
|
float wall_area{0.02f}, heat_transfer{100.0f}, ambient_temperature{300.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
class EngineCylinder : public Component, public CylinderConfig {
|
||||||
|
public:
|
||||||
|
Ecu* ecu{};
|
||||||
|
bool transported_fuel{};
|
||||||
|
float phase{};
|
||||||
bool rev_limit_enabled{};
|
bool rev_limit_enabled{};
|
||||||
float rev_limit_rpm{7500.0f}, rev_limit_cut_time{0.05f}, rev_limit_hysteresis{200.0f};
|
float rev_limit_rpm{7500.0f}, rev_limit_cut_time{0.05f}, rev_limit_hysteresis{200.0f};
|
||||||
float air_mass{}, exhaust_mass{}, energy{}, volume{}, pressure{101325.0f};
|
float air_mass{}, exhaust_mass{}, energy{}, volume{}, pressure{101325.0f};
|
||||||
@@ -78,7 +86,6 @@ protected:
|
|||||||
|
|
||||||
class FourStrokeCylinder final : public EngineCylinder {
|
class FourStrokeCylinder final : public EngineCylinder {
|
||||||
public:
|
public:
|
||||||
float intake_open{340.0f}, intake_close{590.0f}, exhaust_open{130.0f}, exhaust_close{390.0f};
|
|
||||||
float valve_area(float cycle_degrees, bool inlet) const override;
|
float valve_area(float cycle_degrees, bool inlet) const override;
|
||||||
void pre_step(Crankshaft& crank, float dt, float throttle) override;
|
void pre_step(Crankshaft& crank, float dt, float throttle) override;
|
||||||
};
|
};
|
||||||
|
|||||||
53
include/FluidSim/EngineConfig.hpp
Normal file
53
include/FluidSim/EngineConfig.hpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "FluidSim/Engine.hpp"
|
||||||
|
#include "FluidSim/Ecu.hpp"
|
||||||
|
#include "FluidSim/Vehicle.hpp"
|
||||||
|
#include "FluidSim/Carburetor.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fluidsim::port {
|
||||||
|
enum class ScenarioKind { Twin, TwoStroke, SaabB205, AmericanV8 };
|
||||||
|
enum class FourStrokePreset { Twin, SaabB205, AmericanV8 };
|
||||||
|
enum class EngineLayout { Inline, V, Boxer };
|
||||||
|
enum class ExhaustTopology { Collector, Staged, AmericanV8 };
|
||||||
|
|
||||||
|
struct PipeConfig {
|
||||||
|
std::string id;
|
||||||
|
float diameter{}, length{}; // metres
|
||||||
|
int cells{}, count{1};
|
||||||
|
bool silenced{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parsed calibration only. Runtime state belongs to the scenario components.
|
||||||
|
// Dimensional fields use SI units after loading.
|
||||||
|
struct EngineConfig {
|
||||||
|
int cylinders{};
|
||||||
|
EngineLayout layout{EngineLayout::Inline};
|
||||||
|
ExhaustTopology topology{ExhaustTopology::Collector};
|
||||||
|
float bank_angle{60};
|
||||||
|
CylinderConfig cylinder;
|
||||||
|
CrankshaftConfig crankshaft;
|
||||||
|
EcuConfig ecu;
|
||||||
|
VehicleConfig vehicle;
|
||||||
|
CarburetorConfig fuel;
|
||||||
|
bool carbureted{}, initialize_phased_charge{true};
|
||||||
|
float initial_rpm{}, starter_torque{}, maximum_load_torque{};
|
||||||
|
float plenum_volume{}, collector_volume{}, throttle_diameter{}, pipe_damping{};
|
||||||
|
int minimum_substeps{1};
|
||||||
|
std::vector<float> phase_degrees;
|
||||||
|
std::vector<PipeConfig> pipes;
|
||||||
|
|
||||||
|
bool has_silenced_exhaust() const { for(const auto& p:pipes)if(p.silenced)return true;return false; }
|
||||||
|
bool american_headers() const { return topology==ExhaustTopology::AmericanV8; }
|
||||||
|
bool staged_headers() const { return topology==ExhaustTopology::Staged; }
|
||||||
|
bool dual_outlets() const { return american_headers() || (staged_headers() && layout==EngineLayout::V); }
|
||||||
|
int header_groups() const { return cylinders==3?1:2; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JunctionConfig {
|
||||||
|
bool h_pipe{true};
|
||||||
|
float junction_volume{50e-6f};
|
||||||
|
};
|
||||||
|
void validate_engine_config(const EngineConfig& config,const std::string& context="engine config");
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ struct OutdoorAudioConfig {
|
|||||||
float monitor_cutoff_hz{6000.0f}; // Second-order acquisition/rendering filter.
|
float monitor_cutoff_hz{6000.0f}; // Second-order acquisition/rendering filter.
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static geometric direct/first-order reflection paths plus an explicitly
|
// Moving geometric direct/first-order reflection paths plus an explicitly
|
||||||
// approximate diffuse tail. No feedback into the fluid/combustion simulation.
|
// approximate diffuse tail. No feedback into the fluid/combustion simulation.
|
||||||
class OutdoorAudio {
|
class OutdoorAudio {
|
||||||
public:
|
public:
|
||||||
@@ -35,6 +35,9 @@ public:
|
|||||||
AudioFrame process(float intake_mass_flow, float exhaust_mass_flow, float second_exhaust_mass_flow = 0);
|
AudioFrame process(float intake_mass_flow, float exhaust_mass_flow, float second_exhaust_mass_flow = 0);
|
||||||
void set_scene(OutdoorScene scene) { requested_scene_.store(scene, std::memory_order_relaxed); }
|
void set_scene(OutdoorScene scene) { requested_scene_.store(scene, std::memory_order_relaxed); }
|
||||||
OutdoorScene scene() const { return requested_scene_.load(std::memory_order_relaxed); }
|
OutdoorScene scene() const { return requested_scene_.load(std::memory_order_relaxed); }
|
||||||
|
// Call on the simulation thread or under the scenario's simulation lock.
|
||||||
|
void set_listener(AudioListener listener);
|
||||||
|
void set_source_positions(const std::array<Vec3,3>& positions);
|
||||||
void set_monitor_filter(bool enabled) { monitor_filter_ = enabled; } // Set before playback/capture.
|
void set_monitor_filter(bool enabled) { monitor_filter_ = enabled; } // Set before playback/capture.
|
||||||
AudioFrame dry_frame() const { return last_dry_; }
|
AudioFrame dry_frame() const { return last_dry_; }
|
||||||
AudioFrame unfiltered_frame() const { return last_raw_; }
|
AudioFrame unfiltered_frame() const { return last_raw_; }
|
||||||
@@ -45,8 +48,8 @@ private:
|
|||||||
float process(float x);
|
float process(float x);
|
||||||
};
|
};
|
||||||
struct Path {
|
struct Path {
|
||||||
std::size_t delay_left{}, delay_right{};
|
float delay_left{}, delay_right{}, gain_left{}, gain_right{}, alpha{1};
|
||||||
float fraction_left{}, fraction_right{}, gain_left{}, gain_right{}, alpha{1};
|
float target_delay_left{}, target_delay_right{}, target_gain_left{}, target_gain_right{};
|
||||||
float state_left{}, state_right{};
|
float state_left{}, state_right{};
|
||||||
int group{}; // direct, ground, wall, trees, tunnel
|
int group{}; // direct, ground, wall, trees, tunnel
|
||||||
};
|
};
|
||||||
@@ -55,6 +58,7 @@ private:
|
|||||||
std::vector<Path> paths;
|
std::vector<Path> paths;
|
||||||
std::size_t position{};
|
std::size_t position{};
|
||||||
float previous_flow{};
|
float previous_flow{};
|
||||||
|
float center_delay{}, target_center_delay{}, center_gain{}, target_center_gain{};
|
||||||
LowPass filter;
|
LowPass filter;
|
||||||
};
|
};
|
||||||
int rate_{};
|
int rate_{};
|
||||||
@@ -66,8 +70,10 @@ private:
|
|||||||
OutdoorExhaustReverb late_reverb_;
|
OutdoorExhaustReverb late_reverb_;
|
||||||
std::array<float, 5> weights_{1, 0, 0, 0, 0};
|
std::array<float, 5> weights_{1, 0, 0, 0, 0};
|
||||||
float scene_alpha_{}, tail_mix_{};
|
float scene_alpha_{}, tail_mix_{};
|
||||||
|
float geometry_alpha_{}, reverb_input_{};
|
||||||
AudioFrame last_dry_{}, last_raw_{};
|
AudioFrame last_dry_{}, last_raw_{};
|
||||||
void configure_emitter(Emitter &emitter, Vec3 position);
|
void configure_emitter(Emitter &emitter, Vec3 position);
|
||||||
|
void update_geometry();
|
||||||
AudioFrame render(Emitter &emitter, float flow, AudioFrame &dry, AudioFrame &raw);
|
AudioFrame render(Emitter &emitter, float flow, AudioFrame &dry, AudioFrame &raw);
|
||||||
};
|
};
|
||||||
} // namespace fluidsim::port
|
} // namespace fluidsim::port
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FluidSim/Silencer.hpp"
|
#include "FluidSim/Silencer.hpp"
|
||||||
|
#include "FluidSim/EngineConfig.hpp"
|
||||||
|
#include "FluidSim/ScenarioSnapshot.hpp"
|
||||||
#include "FluidSim/Carburetor.hpp"
|
#include "FluidSim/Carburetor.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "FluidSim/Junction.hpp"
|
#include "FluidSim/Junction.hpp"
|
||||||
@@ -19,85 +21,24 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
|
struct ScenarioExample;
|
||||||
|
|
||||||
enum class ScenarioKind { Twin, TwoStroke, SaabB205, AmericanV8 };
|
// Calibrated four-stroke assembly with independent runners and shared plenums.
|
||||||
enum class FourStrokePreset { Twin, SaabB205, AmericanV8 };
|
|
||||||
|
|
||||||
enum class EngineLayout { Inline, V, Boxer };
|
|
||||||
struct EngineConfig {
|
|
||||||
int cylinders{}; // zero selects the existing preset
|
|
||||||
EngineLayout layout{EngineLayout::Inline};
|
|
||||||
float bank_angle{60}, bore{.086f}, stroke{.086f}, rod{.145f}, compression{10};
|
|
||||||
float idle_rpm{1000}, rev_limit{7000}, idle_opening{.045f};
|
|
||||||
bool carbureted{};
|
|
||||||
float carb_pilot_scale{1.f};
|
|
||||||
float carb_jet_scale{1.f}; // multiplier on displacement-scaled effective jet diameters
|
|
||||||
std::array<float,12> phase_degrees{}; // used when custom_phases is true
|
|
||||||
bool custom_phases{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct V8ExhaustConfig {
|
|
||||||
float primary_length{.75f}, primary_diameter{.038f};
|
|
||||||
bool h_pipe{true};
|
|
||||||
float junction_volume{50e-6f}; // m3, 50 cc per fitting
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ScenarioSnapshot {
|
|
||||||
std::string example_name;
|
|
||||||
EngineLayout layout{EngineLayout::Inline};
|
|
||||||
float bank_angle{};
|
|
||||||
bool v8_headers{};
|
|
||||||
PipeSolver pipe_solver{PipeSolver::Rusanov};
|
|
||||||
int cylinder_count{2};
|
|
||||||
float rev_limit_rpm{10000};
|
|
||||||
std::array<float, 12> cylinder_angles{};
|
|
||||||
std::array<float, 12> cylinder_pressures{}, piston_fractions{}, intake_areas{}, exhaust_areas{};
|
|
||||||
std::uint64_t solver_substeps{}, boundary_iterations{}, pipe_positivity_repairs{};
|
|
||||||
float rpm{}, speed_kmh{}, kinetic_energy{}, average_power{}, average_torque{};
|
|
||||||
float cylinder_pressure1{}, cylinder_pressure2{};
|
|
||||||
float cylinder_temperature1{}, cylinder_temperature2{};
|
|
||||||
float cylinder_piston_fraction1{}, cylinder_piston_fraction2{};
|
|
||||||
float valve1_intake_area{}, valve1_exhaust_area{}, valve2_intake_area{}, valve2_exhaust_area{};
|
|
||||||
float intake_pressure{}, exhaust_pressure{}, second_bank_pressure{};
|
|
||||||
std::array<float,2> crossover_pressures{};
|
|
||||||
float throttle{}, pedal{}, target_afr{}, clutch{}, intake_flow{}, exhaust_flow{};
|
|
||||||
bool idle_control{}, fuel_cut{}, two_stroke{}, reed_open{};
|
|
||||||
bool racing_exhaust{};
|
|
||||||
bool silencers{}, h_pipe{};
|
|
||||||
ExhaustConfig exhaust_config{};
|
|
||||||
float crankcase_pressure{};
|
|
||||||
OutdoorScene audio_scene{OutdoorScene::Wall};
|
|
||||||
int gear{};
|
|
||||||
bool rev_limit1{}, rev_limit2{};
|
|
||||||
std::array<std::vector<GasState>, 32> pipe_states;
|
|
||||||
std::array<std::vector<float>, 32> pipe_areas, pipe_lengths;
|
|
||||||
void capture_pipes(const std::vector<Pipe1D>& pipes) {
|
|
||||||
for (std::size_t k = 0; k < pipes.size(); ++k) {
|
|
||||||
pipe_positivity_repairs += pipes[k].positivity_repairs;
|
|
||||||
pipe_areas[k] = pipes[k].cell_areas;
|
|
||||||
pipe_lengths[k] = pipes[k].cell_lengths;
|
|
||||||
pipe_states[k].reserve(pipes[k].cells.size());
|
|
||||||
for (std::size_t i = 0; i < pipes[k].cells.size(); ++i)
|
|
||||||
pipe_states[k].push_back(pipes[k].state(static_cast<int>(i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shared four-stroke topology: independent runners/headers and shared plenums.
|
|
||||||
// Presets include a 600 cc twin, B205-sized inline-four and American 5.0 V8.
|
|
||||||
class FourStrokeScenario : public MotorcycleTest {
|
class FourStrokeScenario : public MotorcycleTest {
|
||||||
public:
|
public:
|
||||||
explicit FourStrokeScenario(int sample_rate = 44100, FourStrokePreset preset = FourStrokePreset::Twin, ExhaustConfig exhaust = {}, V8ExhaustConfig v8_exhaust = {}, EngineConfig engine = {});
|
explicit FourStrokeScenario(int sample_rate = 44100, FourStrokePreset preset = FourStrokePreset::Twin);
|
||||||
const FourStrokePreset preset;
|
FourStrokeScenario(int sample_rate, const ScenarioExample& definition);
|
||||||
|
FourStrokeScenario(int sample_rate, EngineConfig engine, ExhaustConfig exhaust = {}, JunctionConfig junctions = {});
|
||||||
const ExhaustConfig exhaust_config;
|
const ExhaustConfig exhaust_config;
|
||||||
const V8ExhaustConfig v8_exhaust_config;
|
const JunctionConfig junction_config;
|
||||||
const EngineConfig engine_config;
|
const EngineConfig engine_config;
|
||||||
int cylinder_count() const { return engine_config.cylinders ? engine_config.cylinders : preset == FourStrokePreset::AmericanV8 ? 8 : preset == FourStrokePreset::SaabB205 ? 4 : 2; }
|
int cylinder_count() const { return engine_config.cylinders; }
|
||||||
std::array<FourStrokeCylinder*, 12> cylinders() { return {&cylinder1, &cylinder2, &cylinder3, &cylinder4,
|
std::array<FourStrokeCylinder*, 12> cylinders() { return {&cylinder1, &cylinder2, &cylinder3, &cylinder4,
|
||||||
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; }
|
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12}; }
|
||||||
AudioFrame process();
|
AudioFrame process();
|
||||||
void set_controls(float throttle_value, float load_value, float clutch_value);
|
void set_controls(float throttle_value, float load_value, float clutch_value);
|
||||||
void set_audio_scene(OutdoorScene scene);
|
void set_audio_scene(OutdoorScene scene);
|
||||||
|
void set_listener(AudioListener listener);
|
||||||
void set_pipe_solver(PipeSolver solver);
|
void set_pipe_solver(PipeSolver solver);
|
||||||
void set_monitor_filter(bool enabled);
|
void set_monitor_filter(bool enabled);
|
||||||
void set_starter(bool engaged);
|
void set_starter(bool engaged);
|
||||||
@@ -115,8 +56,8 @@ public:
|
|||||||
FourStrokeCylinder cylinder1{}, cylinder2{}, cylinder3{}, cylinder4{};
|
FourStrokeCylinder cylinder1{}, cylinder2{}, cylinder3{}, cylinder4{};
|
||||||
FourStrokeCylinder cylinder5{}, cylinder6{}, cylinder7{}, cylinder8{};
|
FourStrokeCylinder cylinder5{}, cylinder6{}, cylinder7{}, cylinder8{};
|
||||||
FourStrokeCylinder cylinder9{}, cylinder10{}, cylinder11{}, cylinder12{};
|
FourStrokeCylinder cylinder9{}, cylinder10{}, cylinder11{}, cylinder12{};
|
||||||
Volume0D intake_plenum{1.0e-3f, 101325.0f, 300.0f};
|
Volume0D intake_plenum;
|
||||||
Junction exhaust_collector{0.15e-3f, 101325.0f, 700.0f};
|
Junction exhaust_collector;
|
||||||
std::vector<Pipe1D> pipes;
|
std::vector<Pipe1D> pipes;
|
||||||
float intake_flow{}, exhaust_flow{};
|
float intake_flow{}, exhaust_flow{};
|
||||||
std::uint64_t sample_count{};
|
std::uint64_t sample_count{};
|
||||||
@@ -134,7 +75,7 @@ private:
|
|||||||
Port* throttle_port_{};
|
Port* throttle_port_{};
|
||||||
|
|
||||||
static float area_for_diameter(float diameter);
|
static float area_for_diameter(float diameter);
|
||||||
static std::vector<Pipe1D> create_pipes(FourStrokePreset preset, const ExhaustConfig& exhaust, const V8ExhaustConfig& v8_exhaust, const EngineConfig& engine);
|
static std::vector<Pipe1D> create_pipes(const EngineConfig& engine, const ExhaustConfig& exhaust);
|
||||||
};
|
};
|
||||||
|
|
||||||
using FourStrokeTwinScenario = FourStrokeScenario;
|
using FourStrokeTwinScenario = FourStrokeScenario;
|
||||||
|
|||||||
@@ -1,83 +1,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/EngineConfig.hpp"
|
||||||
|
#include "FluidSim/Silencer.hpp"
|
||||||
#include "FluidSim/TwoStrokeConfig.hpp"
|
#include "FluidSim/TwoStrokeConfig.hpp"
|
||||||
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
struct ScenarioExample {
|
struct ScenarioExample {
|
||||||
std::string id,title,description;
|
std::string id,title,description;
|
||||||
ScenarioKind kind;
|
ScenarioKind kind{ScenarioKind::Twin};
|
||||||
TwoStrokeConfig two{};
|
TwoStrokeConfig two{};
|
||||||
ExhaustConfig exhaust{};
|
ExhaustConfig exhaust{};
|
||||||
V8ExhaustConfig v8{};
|
JunctionConfig junctions{};
|
||||||
EngineConfig engine{};
|
EngineConfig engine{};
|
||||||
std::string category;
|
std::string category;
|
||||||
|
int order{};
|
||||||
};
|
};
|
||||||
inline std::vector<ScenarioExample> scenario_examples() {
|
ScenarioExample load_engine_file(const std::filesystem::path& path);
|
||||||
std::vector<ScenarioExample> examples;
|
std::vector<ScenarioExample> load_engine_directory(const std::filesystem::path& directory);
|
||||||
auto add=[&](const char* id,const char* title,const char* description,ScenarioKind kind)->ScenarioExample& {
|
// FLUIDSIM_ENGINE_DIR overrides discovery. Otherwise search the working directory,
|
||||||
examples.push_back({id,title,description,kind,{},{},{},{},{}});return examples.back();
|
// beside the executable, then the source tree for developer tools.
|
||||||
};
|
std::filesystem::path engine_directory();
|
||||||
add("twin","600 cc parallel twin","EFI motorcycle baseline",ScenarioKind::Twin);
|
const std::vector<ScenarioExample>& scenario_examples();
|
||||||
add("yz-racing","YZ125 / expansion chamber","Racing pipe, fixed 4.5% carb idle",ScenarioKind::TwoStroke);
|
const ScenarioExample& scenario_example(const std::string& id);
|
||||||
add("yz-straight","YZ125 / straight exhaust","Same engine and carb, straight exhaust comparison",ScenarioKind::TwoStroke).two.exhaust=TwoStrokeExhaust::Straight;
|
const ScenarioExample& preset_engine_definition(FourStrokePreset preset);
|
||||||
add("saab-straight","Saab B205 / straight-through","6 L resonator and 18 L straight-through rear muffler",ScenarioKind::SaabB205);
|
|
||||||
auto& chambered=add("saab-chambered","Saab B205 / chambered","Expansion resonator and two-chamber rear muffler",ScenarioKind::SaabB205);
|
|
||||||
chambered.exhaust.resonator.type=SilencerType::ExpansionResonator;
|
|
||||||
chambered.exhaust.muffler.type=SilencerType::Chambered;chambered.exhaust.muffler.chambers=2;
|
|
||||||
auto& resonator=add("saab-expansion","Saab B205 / expansion silencers","Two single expansion chambers, same body volumes",ScenarioKind::SaabB205);
|
|
||||||
resonator.exhaust.resonator.type=SilencerType::ExpansionResonator;resonator.exhaust.muffler.type=SilencerType::ExpansionResonator;
|
|
||||||
add("saab-open","Saab B205 / open exhaust","Silencers bypassed; naturally aspirated baseline",ScenarioKind::SaabB205).exhaust.enabled=false;
|
|
||||||
add("v8-h","American 5.0 / 4-2-1 + H","750 mm primaries, 50 cc junctions, H crossover",ScenarioKind::AmericanV8);
|
|
||||||
add("v8-dual","American 5.0 / independent banks","Same 4-2-1 headers with H crossover disconnected",ScenarioKind::AmericanV8).v8.h_pipe=false;
|
|
||||||
add("v8-short","American 5.0 / short headers","600 mm primaries with H crossover",ScenarioKind::AmericanV8).v8.primary_length=.60f;
|
|
||||||
add("v8-long","American 5.0 / long headers","900 mm primaries with H crossover",ScenarioKind::AmericanV8).v8.primary_length=.90f;
|
|
||||||
add("v8-large","American 5.0 / larger primaries","42 mm x 750 mm primaries with H crossover",ScenarioKind::AmericanV8).v8.primary_diameter=.042f;
|
|
||||||
for(auto& e:examples) e.category=e.kind==ScenarioKind::TwoStroke?"Two-stroke / 125 cc":e.kind==ScenarioKind::SaabB205?"Inline four / Saab":e.kind==ScenarioKind::AmericanV8?"V8 / American 5.0":"Twins / parallel";
|
|
||||||
auto chamber=[&](const char* id,const char* title,float length,float width) {
|
|
||||||
auto& e=add(id,title,"Estimated chamber geometry / same 125 cc engine",ScenarioKind::TwoStroke);
|
|
||||||
e.category="Two-stroke / 125 cc";
|
|
||||||
for(auto& section:e.two.racing_pipe) {section.length*=length;section.inlet_diameter*=width;section.outlet_diameter*=width;}
|
|
||||||
};
|
|
||||||
chamber("yz-torque","125 / long torque chamber",1.18f,1.f);
|
|
||||||
chamber("yz-short","125 / short high-RPM chamber",.82f,1.f);
|
|
||||||
chamber("yz-fat","125 / wide belly chamber",1.f,1.18f);
|
|
||||||
auto two=[&](const char* id,const char* title,const char* category,float bore,float stroke,float carb) {
|
|
||||||
auto& e=add(id,title,"Concept single / transported carb fuel",ScenarioKind::TwoStroke);e.category=category;
|
|
||||||
const float scale=std::cbrt((bore*bore*stroke)/(.054f*.054f*.0545f));
|
|
||||||
e.two.jet_scale=bore<.05f?.5f:1.f;
|
|
||||||
e.two.bore=bore;e.two.stroke=stroke;e.two.rod*=scale;e.two.carburetor_diameter=carb;
|
|
||||||
e.two.transfer_width*=scale;e.two.exhaust_width*=scale;e.two.crankcase_clearance*=scale*scale*scale;
|
|
||||||
e.two.minimum_burn_seconds*=scale;
|
|
||||||
for(auto& section:e.two.racing_pipe) {section.length*=scale;section.inlet_diameter*=scale;section.outlet_diameter*=scale;}
|
|
||||||
e.two.exhaust_cell_length=.015f;
|
|
||||||
};
|
|
||||||
two("fifty","50 cc / little screamer","Two-stroke / 50 cc",.040f,.0398f,.019f);
|
|
||||||
two("fivehundred","500 cc / big single","Two-stroke / 500 cc",.086f,.086f,.041f);
|
|
||||||
auto custom=[&](const char* id,const char* title,const char* category,int count,EngineLayout layout,float bore,float stroke)->ScenarioExample& {
|
|
||||||
auto& e=add(id,title,"Estimated concept calibration / EFI",ScenarioKind::Twin);e.category=category;
|
|
||||||
e.engine.cylinders=count;e.engine.layout=layout;e.engine.bore=bore;e.engine.stroke=stroke;
|
|
||||||
e.engine.rod=std::max(.11f,stroke*1.8f);return e;
|
|
||||||
};
|
|
||||||
custom("single-450","450 cc / four-stroke thumper","Singles / four-stroke",1,EngineLayout::Inline,.096f,.062f);
|
|
||||||
custom("single-650","650 cc / big thumper","Singles / four-stroke",1,EngineLayout::Inline,.100f,.083f).engine.rev_limit=6500;
|
|
||||||
auto& twin270=custom("twin-270","700 cc / 270-degree parallel twin","Twins / parallel",2,EngineLayout::Inline,.080f,.068f);
|
|
||||||
twin270.engine.custom_phases=true;twin270.engine.phase_degrees[1]=450;
|
|
||||||
auto& ducati=custom("ltwin","916-inspired / 90-degree V twin","Twins / V",2,EngineLayout::V,.094f,.066f);
|
|
||||||
ducati.engine.bank_angle=90;ducati.engine.custom_phases=true;ducati.engine.phase_degrees[1]=450;
|
|
||||||
custom("triple","1.0 litre / inline triple","Inline three",3,EngineLayout::Inline,.079f,.068f);
|
|
||||||
auto& four=custom("f20","F20C-inspired / 2.0 high-rev four","Inline four / specials",4,EngineLayout::Inline,.087f,.084f);four.engine.rev_limit=9000;
|
|
||||||
custom("five","Audi-inspired / 2.2 inline five","Inline five",5,EngineLayout::Inline,.081f,.0864f);
|
|
||||||
custom("rb26","RB26-inspired / 2.6 inline six NA","Inline six",6,EngineLayout::Inline,.086f,.0737f);
|
|
||||||
custom("six-long","3.0 litre / long-stroke inline six","Inline six",6,EngineLayout::Inline,.086f,.086f);
|
|
||||||
custom("busso","Busso-inspired / 3.0 V6","V6",6,EngineLayout::V,.093f,.0726f);
|
|
||||||
auto& flat4=custom("ej20","EJ20-inspired / 2.0 flat four NA","Boxer / flat four",4,EngineLayout::Boxer,.092f,.075f);
|
|
||||||
flat4.engine.bank_angle=180;
|
|
||||||
custom("flat6","911-inspired / 3.0 flat six","Boxer / flat six",6,EngineLayout::Boxer,.095f,.0704f).engine.bank_angle=180;
|
|
||||||
auto& big=custom("big-carb","8.0 litre / carbureted big-block","V8 / big-block",8,EngineLayout::V,.1143f,.098f);
|
|
||||||
big.engine.carb_pilot_scale=.4f;big.engine.carb_jet_scale=.47f;big.engine.bank_angle=90;big.engine.compression=9;big.engine.carbureted=true;big.engine.idle_rpm=800;big.engine.idle_opening=.08f;
|
|
||||||
big.description="Carb fuel transport / fixed configurable idle stop";
|
|
||||||
custom("v10","5.0 litre / even-fire V10","V10",10,EngineLayout::V,.092f,.0752f).engine.bank_angle=72;
|
|
||||||
custom("v12","250-inspired / 3.0 V12","V12",12,EngineLayout::V,.073f,.0588f).engine.bank_angle=60;
|
|
||||||
return examples;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
include/FluidSim/ScenarioGeometry.hpp
Normal file
16
include/FluidSim/ScenarioGeometry.hpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "FluidSim/AudioProcessing.hpp"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace fluidsim::port {
|
||||||
|
struct ScenarioSnapshot;
|
||||||
|
// Fractions refer to simulated pipe length; positions are world metres.
|
||||||
|
// Both the pipe mesh and its acoustic mouths use these same routes.
|
||||||
|
struct PipeRoute {
|
||||||
|
std::array<Vec3, 5> points{};
|
||||||
|
std::array<float, 5> fractions{};
|
||||||
|
unsigned count{};
|
||||||
|
};
|
||||||
|
std::array<PipeRoute, 32> scenario_pipe_routes(const ScenarioSnapshot& state);
|
||||||
|
std::array<Vec3, 3> scenario_sound_sources(const ScenarioSnapshot& state);
|
||||||
|
}
|
||||||
54
include/FluidSim/ScenarioSnapshot.hpp
Normal file
54
include/FluidSim/ScenarioSnapshot.hpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "FluidSim/EngineConfig.hpp"
|
||||||
|
#include "FluidSim/Fluid.hpp"
|
||||||
|
#include "FluidSim/OutdoorAudio.hpp"
|
||||||
|
#include "FluidSim/Silencer.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fluidsim::port {
|
||||||
|
struct ScenarioSnapshot {
|
||||||
|
std::string example_name;
|
||||||
|
EngineLayout layout{EngineLayout::Inline};
|
||||||
|
float bank_angle{};
|
||||||
|
bool v8_headers{};
|
||||||
|
bool staged_headers{}, dual_exhaust{};
|
||||||
|
PipeSolver pipe_solver{PipeSolver::Rusanov};
|
||||||
|
int cylinder_count{2};
|
||||||
|
float rev_limit_rpm{10000};
|
||||||
|
std::array<float, 12> cylinder_angles{};
|
||||||
|
std::array<float, 12> cylinder_pressures{}, piston_fractions{}, intake_areas{}, exhaust_areas{};
|
||||||
|
std::uint64_t solver_substeps{}, boundary_iterations{}, pipe_positivity_repairs{};
|
||||||
|
float rpm{}, speed_kmh{}, kinetic_energy{}, average_power{}, average_torque{};
|
||||||
|
float cylinder_pressure1{}, cylinder_pressure2{};
|
||||||
|
float cylinder_temperature1{}, cylinder_temperature2{};
|
||||||
|
float cylinder_piston_fraction1{}, cylinder_piston_fraction2{};
|
||||||
|
float valve1_intake_area{}, valve1_exhaust_area{}, valve2_intake_area{}, valve2_exhaust_area{};
|
||||||
|
float intake_pressure{}, exhaust_pressure{}, second_bank_pressure{};
|
||||||
|
std::array<float,2> crossover_pressures{};
|
||||||
|
float throttle{}, pedal{}, target_afr{}, clutch{}, intake_flow{}, exhaust_flow{};
|
||||||
|
bool idle_control{}, fuel_cut{}, ignition_cut{}, two_stroke{}, reed_open{};
|
||||||
|
bool racing_exhaust{};
|
||||||
|
bool silencers{}, h_pipe{};
|
||||||
|
ExhaustConfig exhaust_config{};
|
||||||
|
float crankcase_pressure{};
|
||||||
|
OutdoorScene audio_scene{OutdoorScene::Wall};
|
||||||
|
int gear{};
|
||||||
|
bool rev_limit1{}, rev_limit2{};
|
||||||
|
std::array<std::vector<GasState>, 32> pipe_states;
|
||||||
|
std::array<std::vector<float>, 32> pipe_areas, pipe_lengths;
|
||||||
|
void capture_pipes(const std::vector<Pipe1D>& pipes) {
|
||||||
|
for (std::size_t k = 0; k < pipes.size(); ++k) {
|
||||||
|
pipe_positivity_repairs += pipes[k].positivity_repairs;
|
||||||
|
pipe_areas[k] = pipes[k].cell_areas;
|
||||||
|
pipe_lengths[k] = pipes[k].cell_lengths;
|
||||||
|
pipe_states[k].reserve(pipes[k].cells.size());
|
||||||
|
for (std::size_t i = 0; i < pipes[k].cells.size(); ++i)
|
||||||
|
pipe_states[k].push_back(pipes[k].state(static_cast<int>(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FluidSim/PipeProfile.hpp"
|
#include "FluidSim/PipeProfile.hpp"
|
||||||
|
#include "FluidSim/Ecu.hpp"
|
||||||
|
#include "FluidSim/Vehicle.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
@@ -8,14 +10,40 @@ enum class TwoStrokeExhaust { Racing, Straight };
|
|||||||
// YZ125-sized baseline. Port/chamber/combustion dimensions are explicit design
|
// YZ125-sized baseline. Port/chamber/combustion dimensions are explicit design
|
||||||
// estimates, not a measured Yamaha cylinder or factory exhaust/ignition map.
|
// estimates, not a measured Yamaha cylinder or factory exhaust/ignition map.
|
||||||
struct TwoStrokeConfig {
|
struct TwoStrokeConfig {
|
||||||
|
EcuConfig ecu=[] {
|
||||||
|
EcuConfig e;
|
||||||
|
e.idle_control=EcuConfig::IdleControl::FixedOpening;
|
||||||
|
e.throttle_time_constant=.0002f;e.throttle_rate=5000;
|
||||||
|
e.ignition_map={{0,10},{3000,18},{6000,24},{9000,18},{11000,12},{13000,6},{15000,2},{18000,0}};
|
||||||
|
e.idle_rpm=3000;e.idle_max_opening=.3f;e.rev_limit_enabled=false;
|
||||||
|
e.overrun_cut_rpm=5000;e.overrun_resume_rpm=3500;
|
||||||
|
e.rev_limit_fuel_cut_enabled=false;e.overrun_fuel_cut_enabled=false;e.overrun_ignition_cut_enabled=true;
|
||||||
|
return e;
|
||||||
|
}();
|
||||||
|
VehicleConfig vehicle=[] {
|
||||||
|
VehicleConfig v;
|
||||||
|
v.primary_reduction=64.f/19;v.final_drive_ratio=49.f/13;
|
||||||
|
v.gear_ratios={31.f/13,29.f/15,27.f/17,23.f/17,20.f/17,19.f/18};
|
||||||
|
v.mass=170;v.wheel_radius=.3313f;
|
||||||
|
return v;
|
||||||
|
}();
|
||||||
|
float initial_rpm{3000}, starter_torque{8}, maximum_load_torque{10};
|
||||||
|
float crankshaft_inertia{.01f};
|
||||||
TwoStrokeExhaust exhaust{TwoStrokeExhaust::Racing};
|
TwoStrokeExhaust exhaust{TwoStrokeExhaust::Racing};
|
||||||
float bore{.054f}, stroke{.0545f}, rod{.110f};
|
float bore{.054f}, stroke{.0545f}, rod{.110f};
|
||||||
float trapped_compression{8.2f}; // exhaust-closure volume / clearance volume
|
float trapped_compression{8.2f}; // exhaust-closure volume / clearance volume
|
||||||
float transfer_duration{130}, exhaust_duration{190};
|
float transfer_duration{130}, exhaust_duration{190};
|
||||||
float transfer_width{.050f}, exhaust_width{.036f}; // summed effective window widths
|
float transfer_width{.050f}, exhaust_width{.036f}; // summed effective window widths
|
||||||
|
float pilot_jet_scale{.75f}, main_jet_scale{1.6f}, needle_jet_scale{1.f};
|
||||||
|
float needle_low_scale{2.72f}, needle_mid_scale{.8f};
|
||||||
|
float needle_off_idle_scale{12.f}, needle_transition_scale{9.f}, needle_tip_scale{5.f};
|
||||||
|
float needle_quarter_scale{1.4f}, needle_upper_scale{.76f};
|
||||||
|
float pilot_transition_scale{1.f}, pilot_tip_scale{1.f};
|
||||||
float jet_scale{1.f}; // multiplier on effective jet diameters
|
float jet_scale{1.f}; // multiplier on effective jet diameters
|
||||||
float crankcase_clearance{150e-6f};
|
float crankcase_clearance{150e-6f};
|
||||||
float carburetor_diameter{.0381f};
|
float carburetor_diameter{.0381f};
|
||||||
|
float idle_spark_gain{.05f}; // spark degrees per RPM error at the fixed carb stop
|
||||||
|
float idle_minimum_advance{-10.f};
|
||||||
float idle_slide_opening{.045f}; // fixed mechanical stop, normalized 0..1 (4.5%)
|
float idle_slide_opening{.045f}; // fixed mechanical stop, normalized 0..1 (4.5%)
|
||||||
float minimum_burn_seconds{.0008f};
|
float minimum_burn_seconds{.0008f};
|
||||||
// Empirical mechanical FMEP = A + B*mean_piston_speed + C*speed^2.
|
// Empirical mechanical FMEP = A + B*mean_piston_speed + C*speed^2.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace fluidsim::port {
|
|||||||
struct TwoStrokeSnapshot {
|
struct TwoStrokeSnapshot {
|
||||||
float rpm{}, cylinder_pressure{}, crankcase_pressure{}, intake_pressure{};
|
float rpm{}, cylinder_pressure{}, crankcase_pressure{}, intake_pressure{};
|
||||||
float throttle{}, transfer_area{}, exhaust_area{};
|
float throttle{}, transfer_area{}, exhaust_area{};
|
||||||
bool reed_open{}, fuel_cut{};
|
bool reed_open{}, fuel_cut{}, ignition_cut{};
|
||||||
float pedal{};
|
float pedal{};
|
||||||
OutdoorScene audio_scene{OutdoorScene::Wall};
|
OutdoorScene audio_scene{OutdoorScene::Wall};
|
||||||
};
|
};
|
||||||
@@ -28,6 +28,7 @@ struct TwoStrokeSnapshot {
|
|||||||
// the plenum / reed / crankcase / transfer / cylinder / exhaust topology.
|
// the plenum / reed / crankcase / transfer / cylinder / exhaust topology.
|
||||||
class TwoStrokeScenario : public MotorcycleTest {
|
class TwoStrokeScenario : public MotorcycleTest {
|
||||||
public:
|
public:
|
||||||
|
void set_listener(AudioListener listener) { audio_.set_listener(listener); }
|
||||||
explicit TwoStrokeScenario(int sample_rate = 44100, TwoStrokeConfig config = {});
|
explicit TwoStrokeScenario(int sample_rate = 44100, TwoStrokeConfig config = {});
|
||||||
AudioFrame process();
|
AudioFrame process();
|
||||||
void set_throttle(float value);
|
void set_throttle(float value);
|
||||||
|
|||||||
@@ -5,16 +5,19 @@
|
|||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
|
|
||||||
// Longitudinal vehicle model and the friction clutch coupling it to the crank.
|
// Longitudinal vehicle model and the friction clutch coupling it to the crank.
|
||||||
class Vehicle {
|
struct VehicleConfig {
|
||||||
public:
|
|
||||||
int current_gear{};
|
|
||||||
std::vector<float> gear_ratios{4.1f, 2.7f, 2.0f, 1.55f, 1.25f, 0.98f};
|
std::vector<float> gear_ratios{4.1f, 2.7f, 2.0f, 1.55f, 1.25f, 0.98f};
|
||||||
float final_drive_ratio{2.7f}, primary_reduction{1.8f};
|
float final_drive_ratio{2.7f}, primary_reduction{1.8f};
|
||||||
float clutch_input{1.0f}, clutch_capacity{80.0f}, clutch_engagement_rate{8.0f};
|
float clutch_capacity{80.0f}, clutch_engagement_rate{8.0f};
|
||||||
float mass{160.0f}, wheel_radius{0.32f}, rotational_inertia{1.2f};
|
float mass{160.0f}, wheel_radius{0.32f}, rotational_inertia{1.2f};
|
||||||
float rolling_resistance{0.015f}, drag_coefficient{0.7f}, frontal_area{0.65f};
|
float rolling_resistance{0.015f}, drag_coefficient{0.7f}, frontal_area{0.65f};
|
||||||
float air_density{1.225f}, gravity{9.80665f};
|
float air_density{1.225f}, gravity{9.80665f};
|
||||||
float speed{}, engagement{};
|
};
|
||||||
|
|
||||||
|
class Vehicle : public VehicleConfig {
|
||||||
|
public:
|
||||||
|
int current_gear{};
|
||||||
|
float clutch_input{1.0f}, speed{}, engagement{};
|
||||||
|
|
||||||
float total_gear_ratio() const;
|
float total_gear_ratio() const;
|
||||||
float kinetic_energy() const;
|
float kinetic_energy() const;
|
||||||
|
|||||||
@@ -61,14 +61,31 @@ void Carburetor::step(float dt, float pedal, float slide, float rpm, float air_m
|
|||||||
const float pilot_area = circle_area(pilot_jet_diameter);
|
const float pilot_area = circle_area(pilot_jet_diameter);
|
||||||
const float main_area = circle_area(main_jet_diameter);
|
const float main_area = circle_area(main_jet_diameter);
|
||||||
const float needle_jet_area = circle_area(needle_jet_diameter);
|
const float needle_jet_area = circle_area(needle_jet_diameter);
|
||||||
const float needle_lift = smoothstep((slide - 0.04f) / 0.86f);
|
// Piecewise needle annulus area, interpolated continuously between configured
|
||||||
|
// slide positions. A monotone area profile avoids artificial lean/rich holes
|
||||||
|
// from interpolating a multiplier over a curved base taper.
|
||||||
|
const std::array<float,10> positions{.04f,.045f,.05f,.07f,.1f,.15f,.25f,.5f,.75f,1.f};
|
||||||
|
const std::array<float,10> scales{1.f,std::lerp(1.f,needle_low_scale,.005f/.11f),
|
||||||
|
needle_off_idle_scale,needle_transition_scale,needle_tip_scale,needle_low_scale,needle_quarter_scale,
|
||||||
|
needle_mid_scale,needle_upper_scale,1.f};
|
||||||
|
float needle_lift=0;
|
||||||
|
for(std::size_t i=1;i<positions.size();++i) {
|
||||||
|
const float a=smoothstep((positions[i-1]-.04f)/.86f)*scales[i-1];
|
||||||
|
const float b=smoothstep((positions[i]-.04f)/.86f)*scales[i];
|
||||||
|
needle_lift=std::lerp(a,b,std::clamp((slide-positions[i-1])/(positions[i]-positions[i-1]),0.f,1.f));
|
||||||
|
if(slide<=positions[i]) break;
|
||||||
|
}
|
||||||
const float needle_area = needle_jet_area * needle_lift;
|
const float needle_area = needle_jet_area * needle_lift;
|
||||||
const float main_path_area = needle_area > 1e-12f ?
|
const float main_path_area = needle_area > 1e-12f ?
|
||||||
1.0f / std::sqrt(1.0f / (main_area * main_area) + 1.0f / (needle_area * needle_area)) : 0.0f;
|
1.0f / std::sqrt(1.0f / (main_area * main_area) + 1.0f / (needle_area * needle_area)) : 0.0f;
|
||||||
const float pilot_depression = bowl_head_pressure + (0.62f * static_drop +
|
const float pilot_depression = bowl_head_pressure + (0.62f * static_drop +
|
||||||
0.25f * dynamic_pressure) * (1.0f + 0.25f * lift_enrichment_);
|
0.25f * dynamic_pressure) * (1.0f + 0.25f * lift_enrichment_);
|
||||||
const float main_depression = bowl_head_pressure + venturi_depression_;
|
const float main_depression = bowl_head_pressure + venturi_depression_;
|
||||||
const float pilot_flow = pilot_discharge_coefficient * pilot_area *
|
const float pilot_progression = slide<.05f ? std::lerp(1.f,pilot_transition_scale,
|
||||||
|
std::clamp((slide-.03f)/.02f,0.f,1.f)) : slide<.1f ?
|
||||||
|
std::lerp(pilot_transition_scale,pilot_tip_scale,(slide-.05f)/.05f) :
|
||||||
|
std::lerp(pilot_tip_scale,1.f,std::clamp((slide-.1f)/.1f,0.f,1.f));
|
||||||
|
const float pilot_flow = pilot_progression * pilot_discharge_coefficient * pilot_area *
|
||||||
std::sqrt(2.0f * fuel_density * std::max(pilot_depression, 0.0f));
|
std::sqrt(2.0f * fuel_density * std::max(pilot_depression, 0.0f));
|
||||||
const float main_flow = main_discharge_coefficient * main_path_area *
|
const float main_flow = main_discharge_coefficient * main_path_area *
|
||||||
std::sqrt(2.0f * fuel_density * std::max(main_depression, 0.0f));
|
std::sqrt(2.0f * fuel_density * std::max(main_depression, 0.0f));
|
||||||
@@ -96,7 +113,7 @@ void Carburetor::step(float dt, float pedal, float slide, float rpm, float air_m
|
|||||||
const float fuel_alpha = -std::expm1(-dt / 0.025f);
|
const float fuel_alpha = -std::expm1(-dt / 0.025f);
|
||||||
filtered_fuel_flow_ += (delivered_fuel_flow_ - filtered_fuel_flow_) * fuel_alpha;
|
filtered_fuel_flow_ += (delivered_fuel_flow_ - filtered_fuel_flow_) * fuel_alpha;
|
||||||
if (filtered_air_flow_ > 1e-7f && filtered_fuel_flow_ > 1e-9f)
|
if (filtered_air_flow_ > 1e-7f && filtered_fuel_flow_ > 1e-9f)
|
||||||
indicated_afr_ = std::clamp(filtered_air_flow_ / filtered_fuel_flow_, 4.0f, 40.0f);
|
indicated_afr_ = filtered_air_flow_ / filtered_fuel_flow_;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fluidsim::port
|
} // namespace fluidsim::port
|
||||||
|
|||||||
@@ -28,14 +28,11 @@ constexpr float throttle_lerp_rate = 10.0f;
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) try {
|
||||||
using namespace fluidsim::port;
|
using namespace fluidsim::port;
|
||||||
ScenarioKind selected_kind=ScenarioKind::Twin;
|
|
||||||
bool scenario_from_args=false;
|
bool scenario_from_args=false;
|
||||||
TwoStrokeConfig two_stroke_config;
|
const auto& examples=scenario_examples();
|
||||||
ExhaustConfig exhaust_config; V8ExhaustConfig v8_config; EngineConfig engine_config;
|
auto selected=examples.front();
|
||||||
const auto examples=scenario_examples();
|
|
||||||
std::string example_title;
|
|
||||||
PipeSolver selected_solver=PipeSolver::Rusanov;
|
PipeSolver selected_solver=PipeSolver::Rusanov;
|
||||||
OutdoorScene selected_scene=OutdoorScene::Wall;
|
OutdoorScene selected_scene=OutdoorScene::Wall;
|
||||||
for(int i=1;i<argc;++i){
|
for(int i=1;i<argc;++i){
|
||||||
@@ -45,18 +42,17 @@ int main(int argc, char** argv) {
|
|||||||
if(option=="--example") {
|
if(option=="--example") {
|
||||||
const auto found=std::find_if(examples.begin(),examples.end(),[&](const auto& e){return e.id==value;});
|
const auto found=std::find_if(examples.begin(),examples.end(),[&](const auto& e){return e.id==value;});
|
||||||
if(found==examples.end()){std::cerr<<"Unknown example: "<<value<<"\n";return 2;}
|
if(found==examples.end()){std::cerr<<"Unknown example: "<<value<<"\n";return 2;}
|
||||||
selected_kind=found->kind;two_stroke_config=found->two;exhaust_config=found->exhaust;v8_config=found->v8;engine_config=found->engine;example_title=found->title;scenario_from_args=true;
|
selected=*found;scenario_from_args=true;
|
||||||
} else if(option=="--scenario"){
|
} else if(option=="--scenario"){
|
||||||
if(value!="twin"&&value!="two-stroke"&&value!="b205"&&value!="v8"){std::cerr<<"Choose twin, two-stroke, b205 or v8\n";return 2;}
|
if(value!="twin"&&value!="two-stroke"&&value!="b205"&&value!="v8"){std::cerr<<"Choose twin, two-stroke, b205 or v8\n";return 2;}
|
||||||
selected_kind=value=="v8" ? ScenarioKind::AmericanV8 : value=="b205" ? ScenarioKind::SaabB205 :
|
selected=scenario_example(value=="v8"?"v8-h":value=="b205"?"saab-straight":value=="two-stroke"?"yz-racing":"twin");
|
||||||
(value=="two-stroke" ? ScenarioKind::TwoStroke : ScenarioKind::Twin);
|
|
||||||
scenario_from_args=true;
|
scenario_from_args=true;
|
||||||
}else if(option=="--solver"){
|
}else if(option=="--solver"){
|
||||||
try { selected_solver=parse_pipe_solver(value); }
|
try { selected_solver=parse_pipe_solver(value); }
|
||||||
catch(const std::exception& e) { std::cerr<<e.what()<<"\n"; return 2; }
|
catch(const std::exception& e) { std::cerr<<e.what()<<"\n"; return 2; }
|
||||||
}else if(option=="--exhaust"){
|
}else if(option=="--exhaust"){
|
||||||
if(value=="racing") two_stroke_config.exhaust=TwoStrokeExhaust::Racing;
|
if(value=="racing") selected.two.exhaust=TwoStrokeExhaust::Racing;
|
||||||
else if(value=="straight") two_stroke_config.exhaust=TwoStrokeExhaust::Straight;
|
else if(value=="straight") selected.two.exhaust=TwoStrokeExhaust::Straight;
|
||||||
else { std::cerr<<"Choose racing or straight exhaust\n";return 2; }
|
else { std::cerr<<"Choose racing or straight exhaust\n";return 2; }
|
||||||
}else{
|
}else{
|
||||||
if(value=="dry")selected_scene=OutdoorScene::Dry;
|
if(value=="dry")selected_scene=OutdoorScene::Dry;
|
||||||
@@ -89,22 +85,26 @@ int main(int argc, char** argv) {
|
|||||||
if(!has_font)std::cerr<<"No UI font available; configure FLUIDSIM_FONT_FILE.\n";
|
if(!has_font)std::cerr<<"No UI font available; configure FLUIDSIM_FONT_FILE.\n";
|
||||||
const sf::Font* font_ptr=has_font?&font:nullptr;
|
const sf::Font* font_ptr=has_font?&font:nullptr;
|
||||||
|
|
||||||
|
while (window.isOpen()) {
|
||||||
if (!scenario_from_args) {
|
if (!scenario_from_args) {
|
||||||
|
window.setTitle("FluidSim - Examples");
|
||||||
|
window.setMinimumSize(sf::Vector2u{720,606});
|
||||||
|
window.setSize({720,606});
|
||||||
const auto choice = desktop::choose_scenario(window, font_ptr,examples);
|
const auto choice = desktop::choose_scenario(window, font_ptr,examples);
|
||||||
if (!choice) return 0;
|
if (!choice) return 0;
|
||||||
const auto& example=examples[*choice];
|
selected=examples[*choice];
|
||||||
selected_kind=example.kind;two_stroke_config=example.two;exhaust_config=example.exhaust;v8_config=example.v8;engine_config=example.engine;example_title=example.title;
|
|
||||||
window.setSize({window_width, window_height});
|
window.setSize({window_width, window_height});
|
||||||
window.setView(sf::View(sf::FloatRect({0, 0}, {float(window_width), float(window_height)})));
|
window.setView(sf::View(sf::FloatRect({0, 0}, {float(window_width), float(window_height)})));
|
||||||
}
|
}
|
||||||
window.setTitle(selected_kind == ScenarioKind::AmericanV8 ? "FluidSim - 1980s American 5.0 V8" : selected_kind == ScenarioKind::SaabB205 ? "FluidSim - Saab B205 / 9-5 wagon" :
|
scenario_from_args=false;
|
||||||
(selected_kind == ScenarioKind::TwoStroke ? "FluidSim - YZ125 two-stroke" : "FluidSim - parallel twin"));
|
window.setTitle("FluidSim - "+selected.title);
|
||||||
window.setMinimumSize(sf::Vector2u{1040,800});
|
window.setMinimumSize(sf::Vector2u{1040,800});
|
||||||
if(!example_title.empty())window.setTitle("FluidSim - "+example_title);
|
DesktopScenario scenario(selected);
|
||||||
DesktopScenario scenario(selected_kind, two_stroke_config,exhaust_config,v8_config,engine_config,example_title);
|
|
||||||
scenario.set_pipe_solver(selected_solver);
|
scenario.set_pipe_solver(selected_solver);
|
||||||
scenario.set_audio_scene(selected_scene);
|
scenario.set_audio_scene(selected_scene);
|
||||||
std::cout << "Desktop started: scenario=" << static_cast<int>(selected_kind)
|
desktop::DesktopUi ui;
|
||||||
|
scenario.set_listener(ui.listener(scenario.snapshot(),window.getSize()));
|
||||||
|
std::cout << "Desktop started: scenario=" << selected.id
|
||||||
<< " solver=" << pipe_solver_name(selected_solver) << " scene=" << scene_name(selected_scene) << std::endl;
|
<< " solver=" << pipe_solver_name(selected_solver) << " scene=" << scene_name(selected_scene) << std::endl;
|
||||||
SimulationRingBuffer ring(ring_capacity);
|
SimulationRingBuffer ring(ring_capacity);
|
||||||
for (std::size_t i = 0; i < preroll_samples; ++i) {
|
for (std::size_t i = 0; i < preroll_samples; ++i) {
|
||||||
@@ -134,11 +134,16 @@ int main(int argc, char** argv) {
|
|||||||
float brake_target = 0.0f, brake_current = 0.0f;
|
float brake_target = 0.0f, brake_current = 0.0f;
|
||||||
float clutch_current = 0.0f;
|
float clutch_current = 0.0f;
|
||||||
bool throttle_held = false, throttle_down = false, clutch_held = false;
|
bool throttle_held = false, throttle_down = false, clutch_held = false;
|
||||||
desktop::DesktopUi ui;
|
|
||||||
auto previous_frame = std::chrono::steady_clock::now();
|
auto previous_frame = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
while (window.isOpen()) {
|
bool return_to_menu=false;
|
||||||
|
while (window.isOpen() && !return_to_menu) {
|
||||||
while (const std::optional event = window.pollEvent()) {
|
while (const std::optional event = window.pollEvent()) {
|
||||||
|
if (const auto* key = event->getIf<sf::Event::KeyPressed>();
|
||||||
|
key && key->code == sf::Keyboard::Key::Escape) {
|
||||||
|
return_to_menu=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if(ui.handle_event(*event,window.getSize())) continue;
|
if(ui.handle_event(*event,window.getSize())) continue;
|
||||||
if(event->is<sf::Event::FocusLost>()) { throttle_held=false; throttle_down=false; clutch_held=false; scenario.set_starter(false); }
|
if(event->is<sf::Event::FocusLost>()) { throttle_held=false; throttle_down=false; clutch_held=false; scenario.set_starter(false); }
|
||||||
if (event->is<sf::Event::Closed>()) window.close();
|
if (event->is<sf::Event::Closed>()) window.close();
|
||||||
@@ -185,6 +190,8 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.isOpen() || return_to_menu) break;
|
||||||
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
const float frame_dt = std::chrono::duration<float>(now - previous_frame).count();
|
const float frame_dt = std::chrono::duration<float>(now - previous_frame).count();
|
||||||
previous_frame = now;
|
previous_frame = now;
|
||||||
@@ -202,11 +209,18 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
const desktop::PlaybackInfo playback{throttle_current,brake_current,current_speed,
|
const desktop::PlaybackInfo playback{throttle_current,brake_current,current_speed,
|
||||||
ring.available_samples(),ring_capacity,audio.underrun_blocks(),audio.auto_gain(),time_warp};
|
ring.available_samples(),ring_capacity,audio.underrun_blocks(),audio.auto_gain(),time_warp};
|
||||||
ui.draw(window,font_ptr,scenario.snapshot(),playback);
|
const auto state=scenario.snapshot();
|
||||||
|
scenario.set_listener(ui.listener(state,window.getSize()));
|
||||||
|
ui.draw(window,font_ptr,state,playback);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulation.request_stop();
|
simulation.request_stop();
|
||||||
audio.stop();
|
audio.stop();
|
||||||
|
simulation.join();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
} catch(const std::exception& error) {
|
||||||
|
std::cerr<<"Startup failed: "<<error.what()<<'\n';
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ bool DesktopUi::handle_event(const sf::Event& event,sf::Vector2u size) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
AudioListener DesktopUi::listener(const ScenarioSnapshot& s,sf::Vector2u size) {
|
||||||
|
const auto viewport=WorkspaceLayout::calculate(size).viewport;
|
||||||
|
if(!camera_fitted_) {
|
||||||
|
camera_.reset(viewport.size.x/(viewport.size.y-56));
|
||||||
|
camera_fitted_=true;
|
||||||
|
}
|
||||||
|
return camera_.listener(s.silencers?1.8f:s.cylinder_count==8?1.4f:1.f,
|
||||||
|
s.silencers?1.1f:s.cylinder_count==8?.7f:0.f);
|
||||||
|
}
|
||||||
void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnapshot& s,const PlaybackInfo& p) {
|
void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnapshot& s,const PlaybackInfo& p) {
|
||||||
const auto size=w.getSize();const float width=float(size.x),height=float(size.y);
|
const auto size=w.getSize();const float width=float(size.x),height=float(size.y);
|
||||||
w.setView(sf::View(sf::FloatRect({0,0},{width,height})));w.clear(background);
|
w.setView(sf::View(sf::FloatRect({0,0},{width,height})));w.clear(background);
|
||||||
@@ -85,7 +94,7 @@ void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnap
|
|||||||
y+=12;label(w,font,"AUDIO & SIMULATION",x,y,11,accent);y+=28;
|
y+=12;label(w,font,"AUDIO & SIMULATION",x,y,11,accent);y+=28;
|
||||||
row("Solver [F3]",pipe_solver_name(s.pipe_solver));row("Scene [R]",scene_name(s.audio_scene));row("Auto gain [G]",p.auto_gain?"On":"Off");
|
row("Solver [F3]",pipe_solver_name(s.pipe_solver));row("Scene [R]",scene_name(s.audio_scene));row("Auto gain [G]",p.auto_gain?"On":"Off");
|
||||||
row("Buffer",std::to_string(p.buffered)+" / "+std::to_string(p.capacity));row("Underruns",std::to_string(p.underruns));
|
row("Buffer",std::to_string(p.buffered)+" / "+std::to_string(p.capacity));row("Underruns",std::to_string(p.underruns));
|
||||||
label(w,font,s.fuel_cut?"FUEL CUT":s.idle_control?"IDLE CONTROL":"RUNNING",x,y+8,12,s.fuel_cut?sf::Color(255,171,96):accent);
|
label(w,font,s.fuel_cut?"FUEL CUT":s.ignition_cut?"IGNITION CUT":s.idle_control?"IDLE CONTROL":"RUNNING",x,y+8,12,(s.fuel_cut||s.ignition_cut)?sf::Color(255,171,96):accent);
|
||||||
if(height>=800) label(w,font,"W/S Raise / lower pedal\nA/D Shift Z Neutral\nC Clutch V Starter\nArrows Load End Release\nPgUp/Dn Speed Space 1x",x,height-141,11,muted);
|
if(height>=800) label(w,font,"W/S Raise / lower pedal\nA/D Shift Z Neutral\nC Clutch V Starter\nArrows Load End Release\nPgUp/Dn Speed Space 1x",x,height-141,11,muted);
|
||||||
label(w,font,three_d_?"ENGINE VIEW / 3D":"ENGINE VIEW / SCHEMATIC",l.viewport.position.x+16,93,11,accent);
|
label(w,font,three_d_?"ENGINE VIEW / 3D":"ENGINE VIEW / SCHEMATIC",l.viewport.position.x+16,93,11,accent);
|
||||||
label(w,font,three_d_?"Drag: orbit Right: pan Scroll: zoom Home: fit Tab: 2D":"Drag: pan Scroll: zoom Home: reset Tab: 3D",l.viewport.position.x+16,117,11,muted);
|
label(w,font,three_d_?"Drag: orbit Right: pan Scroll: zoom Home: fit Tab: 2D":"Drag: pan Scroll: zoom Home: reset Tab: 3D",l.viewport.position.x+16,117,11,muted);
|
||||||
@@ -112,7 +121,7 @@ void DesktopUi::draw(sf::RenderWindow& w,const sf::Font* font,const ScenarioSnap
|
|||||||
const bool exhaust=s.two_stroke?s.valve1_exhaust_area>0:s.exhaust_areas[i]>0;
|
const bool exhaust=s.two_stroke?s.valve1_exhaust_area>0:s.exhaust_areas[i]>0;
|
||||||
label(w,font,std::to_string(i+1)+" "+number(pressure/1000)+" "+number(stroke*100)+"% "+(intake?"O":"-")+" / "+(exhaust?"O":"-"),ix+(s.cylinder_count>8?i/6*(l.inspector.size.x/2-4):0),iy+42+(s.cylinder_count>8?i%6:i)*15,10);
|
label(w,font,std::to_string(i+1)+" "+number(pressure/1000)+" "+number(stroke*100)+"% "+(intake?"O":"-")+" / "+(exhaust?"O":"-"),ix+(s.cylinder_count>8?i/6*(l.inspector.size.x/2-4):0),iy+42+(s.cylinder_count>8?i%6:i)*15,10);
|
||||||
}
|
}
|
||||||
label(w,font,"End: release pedal",20,height-29,11,muted);
|
label(w,font,"Esc: menu End: release pedal",20,height-29,11,muted);
|
||||||
label(w,font,"Pressure: blue vacuum / green atmosphere / red 1 MPa",l.viewport.position.x+2,height-29,11,muted);
|
label(w,font,"Pressure: blue vacuum / green atmosphere / red 1 MPa",l.viewport.position.x+2,height-29,11,muted);
|
||||||
w.display();
|
w.display();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ void Ecu::step(float dt, float pedal, float rpm) {
|
|||||||
if (rpm <= b.rpm) break;
|
if (rpm <= b.rpm) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(idle_ && !fixed_idle && idle_spark_gain>0) spark_=std::clamp(spark_+idle_spark_gain*error,idle_minimum_advance,35.0f);
|
if(idle_ && idle_spark_gain>0) spark_=std::clamp(spark_+idle_spark_gain*error,idle_minimum_advance,35.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float Ecu::fuel_for_air(float air) const {
|
float Ecu::fuel_for_air(float air) const {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ void FourStrokeCylinder::pre_step(Crankshaft& crank, float dt, float /*throttle*
|
|||||||
|
|
||||||
const float spark_angle = wrap_degrees(720.0f - spark_advance);
|
const float spark_angle = wrap_degrees(720.0f - spark_advance);
|
||||||
const bool crossed_spark = crossed_angle(previous_degrees,current_degrees,spark_angle);
|
const bool crossed_spark = crossed_angle(previous_degrees,current_degrees,spark_angle);
|
||||||
if (crossed_spark && fuel_injected && !combustion_active && !(ecu ? ecu->fuel_cut() : rev_limit_active) && random_unit() >= misfire_probability) {
|
if (crossed_spark && fuel_injected && !combustion_active && !(ecu ? ecu->ignition_cut() : rev_limit_active) && random_unit() >= misfire_probability) {
|
||||||
combustion_active = true;
|
combustion_active = true;
|
||||||
burn_fraction = 0.0f;
|
burn_fraction = 0.0f;
|
||||||
active_fuel_mass_ = transported_fuel ? std::min(fuel_mass, air_mass / 14.7f) : fuel_mass;
|
active_fuel_mass_ = transported_fuel ? std::min(fuel_mass, air_mass / 14.7f) : fuel_mass;
|
||||||
|
|||||||
526
src/engine_config.cpp
Normal file
526
src/engine_config.cpp
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
|
#include <toml.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fluidsim::port {
|
||||||
|
namespace {
|
||||||
|
class Reader {
|
||||||
|
const toml::table& table_;
|
||||||
|
std::set<std::string> used_;
|
||||||
|
std::string path_;
|
||||||
|
public:
|
||||||
|
Reader(const toml::table& table,std::string path):table_(table),path_(std::move(path)) {}
|
||||||
|
[[noreturn]] void fail(const std::string& message) const { throw std::runtime_error(path_+": "+message); }
|
||||||
|
void required(std::initializer_list<const char*> keys) const {
|
||||||
|
for(const auto* key:keys)if(!table_.contains(key))fail(std::string("missing required field '")+key+"'");
|
||||||
|
}
|
||||||
|
const toml::node* node(const std::string& key) { used_.insert(key);return table_.get(key); }
|
||||||
|
void field(const char* key,float& value,double scale=1) {
|
||||||
|
if(const auto* n=node(key)) {
|
||||||
|
auto number=n->value<double>();
|
||||||
|
if(!number || !std::isfinite(*number) || std::abs(*number*scale)>std::numeric_limits<float>::max())
|
||||||
|
fail(std::string(key)+" must be a finite number");
|
||||||
|
value=static_cast<float>(*number*scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void field(const char* key,int& value) {
|
||||||
|
if(const auto* n=node(key)) {
|
||||||
|
auto number=n->value<int64_t>();
|
||||||
|
if(!n->is_integer() || !number || *number<std::numeric_limits<int>::min() || *number>std::numeric_limits<int>::max())
|
||||||
|
fail(std::string(key)+" must be an integer");
|
||||||
|
value=static_cast<int>(*number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void field(const char* key,bool& value) {
|
||||||
|
if(const auto* n=node(key)) {
|
||||||
|
auto b=n->value<bool>();if(!b)fail(std::string(key)+" must be true or false");value=*b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void field(const char* key,std::string& value) {
|
||||||
|
if(const auto* n=node(key)) {
|
||||||
|
auto s=n->value<std::string>();if(!s)fail(std::string(key)+" must be a string");value=*s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string text(const char* key,std::string fallback={}) {field(key,fallback);return fallback;}
|
||||||
|
Reader section(const char* key,bool required=false) {
|
||||||
|
static const toml::table empty;
|
||||||
|
const auto* n=node(key);
|
||||||
|
if(n && !n->is_table())fail(std::string(key)+" must be a table");
|
||||||
|
if(!n && required)fail(std::string("missing [")+key+"]");
|
||||||
|
return Reader(n?*n->as_table():empty,path_+"."+key);
|
||||||
|
}
|
||||||
|
const toml::array* array(const char* key) {
|
||||||
|
const auto* n=node(key);
|
||||||
|
if(n && !n->is_array())fail(std::string(key)+" must be an array");
|
||||||
|
if(n && n->as_array()->size()>4096)fail(std::string(key)+" has too many entries");
|
||||||
|
return n?n->as_array():nullptr;
|
||||||
|
}
|
||||||
|
float number(const toml::node& n) const {
|
||||||
|
auto value=n.value<double>();
|
||||||
|
if(!value || !std::isfinite(*value) || std::abs(*value)>std::numeric_limits<float>::max())fail("array entries must be finite numbers");
|
||||||
|
return static_cast<float>(*value);
|
||||||
|
}
|
||||||
|
void field(const char* key,std::vector<float>& values) {
|
||||||
|
if(const auto* a=array(key)) {values.clear();for(const auto& n:*a)values.push_back(number(n));}
|
||||||
|
}
|
||||||
|
void finish() const {
|
||||||
|
for(const auto& [key,value]:table_)if(!used_.contains(std::string(key.str())))fail("unknown field '"+std::string(key.str())+"'");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T> void require(bool condition,const T& where,const char* message) {
|
||||||
|
if(!condition)throw std::runtime_error(std::string(where)+": "+message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar readers below keep the file units explicit at the conversion boundary.
|
||||||
|
void read_cylinder(Reader r,CylinderConfig& c) {
|
||||||
|
r.required({"bore_mm","stroke_mm","rod_mm","compression"});
|
||||||
|
r.field("afr",c.afr);
|
||||||
|
r.field("ambient_temperature_k",c.ambient_temperature);
|
||||||
|
r.field("bore_mm",c.bore,0.001);
|
||||||
|
r.field("burn_duration_deg",c.burn_duration);
|
||||||
|
r.field("burn_start_deg",c.burn_start);
|
||||||
|
r.field("compression",c.compression);
|
||||||
|
r.field("energy_variation",c.energy_variation);
|
||||||
|
r.field("exhaust_close_deg",c.exhaust_close);
|
||||||
|
r.field("exhaust_open_deg",c.exhaust_open);
|
||||||
|
r.field("exhaust_valve_count",c.exhaust_valve_count);
|
||||||
|
r.field("exhaust_valve_diameter_mm",c.exhaust_valve_diameter,0.001);
|
||||||
|
r.field("exhaust_valve_lift_mm",c.exhaust_valve_lift,0.001);
|
||||||
|
r.field("fuel_lhv_j_per_kg",c.fuel_lhv);
|
||||||
|
r.field("heat_transfer",c.heat_transfer);
|
||||||
|
r.field("intake_close_deg",c.intake_close);
|
||||||
|
r.field("intake_open_deg",c.intake_open);
|
||||||
|
r.field("intake_valve_count",c.intake_valve_count);
|
||||||
|
r.field("intake_valve_diameter_mm",c.intake_valve_diameter,0.001);
|
||||||
|
r.field("intake_valve_lift_mm",c.intake_valve_lift,0.001);
|
||||||
|
r.field("misfire_probability",c.misfire_probability);
|
||||||
|
r.field("rod_mm",c.rod,0.001);
|
||||||
|
r.field("stroke_mm",c.stroke,0.001);
|
||||||
|
r.field("wall_area",c.wall_area);
|
||||||
|
r.field("wiebe_a",c.wiebe_a);
|
||||||
|
r.field("wiebe_m",c.wiebe_m);
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_crankshaft(Reader r,CrankshaftConfig& c) {
|
||||||
|
r.field("friction_constant_nm",c.friction_constant);
|
||||||
|
r.field("friction_quadratic_nm_s2",c.friction_quadratic);
|
||||||
|
r.field("friction_viscous_nm_s",c.friction_viscous);
|
||||||
|
r.field("inertia_kg_m2",c.inertia);
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_ecu(Reader r,EcuConfig& c) {
|
||||||
|
r.field("idle_base_opening",c.idle_base_opening);
|
||||||
|
r.field("idle_ki",c.idle_ki);
|
||||||
|
r.field("idle_kp",c.idle_kp);
|
||||||
|
r.field("idle_max_opening",c.idle_max_opening);
|
||||||
|
r.field("idle_minimum_advance_deg",c.idle_minimum_advance);
|
||||||
|
r.field("idle_rpm",c.idle_rpm);
|
||||||
|
r.field("idle_spark_gain",c.idle_spark_gain);
|
||||||
|
r.field("overrun_cut_rpm",c.overrun_cut_rpm);
|
||||||
|
r.field("overrun_fuel_cut_enabled",c.overrun_fuel_cut_enabled);
|
||||||
|
r.field("overrun_ignition_cut_enabled",c.overrun_ignition_cut_enabled);
|
||||||
|
r.field("overrun_resume_rpm",c.overrun_resume_rpm);
|
||||||
|
r.field("power_afr",c.power_afr);
|
||||||
|
r.field("rev_limit_enabled",c.rev_limit_enabled);
|
||||||
|
r.field("rev_limit_fuel_cut_enabled",c.rev_limit_fuel_cut_enabled);
|
||||||
|
r.field("rev_limit_rpm",c.rev_limit_rpm);
|
||||||
|
r.field("rev_resume_rpm",c.rev_resume_rpm);
|
||||||
|
r.field("stoichiometric_afr",c.stoichiometric_afr);
|
||||||
|
r.field("throttle_closing_rate",c.throttle_closing_rate);
|
||||||
|
r.field("throttle_closing_time_constant_s",c.throttle_closing_time_constant);
|
||||||
|
r.field("throttle_rate",c.throttle_rate);
|
||||||
|
r.field("throttle_time_constant_s",c.throttle_time_constant);
|
||||||
|
const auto mode=r.text("idle_control","feedback");
|
||||||
|
if(mode!="fixed" && mode!="feedback")r.fail("idle_control must be fixed or feedback");
|
||||||
|
c.idle_control=mode=="fixed"?EcuConfig::IdleControl::FixedOpening:EcuConfig::IdleControl::Feedback;
|
||||||
|
if(const auto* curve=r.array("ignition_map")) {
|
||||||
|
c.ignition_map.clear();float previous=-1;
|
||||||
|
for(const auto& n:*curve) {
|
||||||
|
const auto* point=n.as_array();
|
||||||
|
if(!point || point->size()!=2)r.fail("ignition_map entries must be [RPM, degrees BTDC]");
|
||||||
|
const float rpm=r.number(*point->get(0)),advance=r.number(*point->get(1));
|
||||||
|
if(rpm<0 || rpm<=previous || advance < -90 || advance>90)r.fail("ignition_map needs increasing nonnegative RPM and advances within +/-90 degrees");
|
||||||
|
c.ignition_map.push_back({rpm,advance});previous=rpm;
|
||||||
|
}
|
||||||
|
if(c.ignition_map.empty())r.fail("ignition_map must not be empty");
|
||||||
|
}
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_vehicle(Reader r,VehicleConfig& c) {
|
||||||
|
r.field("air_density",c.air_density);
|
||||||
|
r.field("clutch_capacity",c.clutch_capacity);
|
||||||
|
r.field("clutch_engagement_rate",c.clutch_engagement_rate);
|
||||||
|
r.field("drag_coefficient",c.drag_coefficient);
|
||||||
|
r.field("final_drive_ratio",c.final_drive_ratio);
|
||||||
|
r.field("frontal_area",c.frontal_area);
|
||||||
|
r.field("gear_ratios",c.gear_ratios);
|
||||||
|
r.field("gravity",c.gravity);
|
||||||
|
r.field("mass_kg",c.mass);
|
||||||
|
r.field("primary_reduction",c.primary_reduction);
|
||||||
|
r.field("rolling_resistance",c.rolling_resistance);
|
||||||
|
r.field("rotational_inertia_kg_m2",c.rotational_inertia);
|
||||||
|
r.field("wheel_radius_mm",c.wheel_radius,0.001);
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_silencer(Reader r,SilencerConfig& c) {
|
||||||
|
r.field("chambers",c.chambers);
|
||||||
|
r.field("core_diameter_mm",c.core_diameter,0.001);
|
||||||
|
r.field("length_mm",c.length,0.001);
|
||||||
|
r.field("loss_coefficient",c.loss_coefficient);
|
||||||
|
r.field("passage_diameter_mm",c.passage_diameter,0.001);
|
||||||
|
r.field("resistance",c.resistance);
|
||||||
|
r.field("volume_litres",c.volume,0.001);
|
||||||
|
const auto type=r.text("type","straight_through");
|
||||||
|
if(type=="straight_through")c.type=SilencerType::StraightThrough;
|
||||||
|
else if(type=="chambered")c.type=SilencerType::Chambered;
|
||||||
|
else if(type=="expansion")c.type=SilencerType::ExpansionResonator;
|
||||||
|
else r.fail("unknown silencer type '"+type+"'");
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_silencers(Reader r,ExhaustConfig& c) {
|
||||||
|
r.field("cell_length_mm",c.cell_length,0.001);
|
||||||
|
r.field("connecting_length_mm",c.connecting_length,0.001);
|
||||||
|
r.field("diameter_mm",c.diameter,0.001);
|
||||||
|
r.field("enabled",c.enabled);
|
||||||
|
r.field("inlet_length_mm",c.inlet_length,0.001);
|
||||||
|
r.field("outlet_length_mm",c.outlet_length,0.001);
|
||||||
|
read_silencer(r.section("resonator"),c.resonator);
|
||||||
|
read_silencer(r.section("muffler"),c.muffler);
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_two_stroke(Reader r,TwoStrokeConfig& c) {
|
||||||
|
r.required({"bore_mm","stroke_mm","rod_mm","trapped_compression","carburetor_diameter_mm"});
|
||||||
|
r.field("initial_rpm",c.initial_rpm);
|
||||||
|
r.field("starter_torque_nm",c.starter_torque);
|
||||||
|
r.field("maximum_load_torque_nm",c.maximum_load_torque);
|
||||||
|
r.field("crankshaft_inertia_kg_m2",c.crankshaft_inertia);
|
||||||
|
r.field("bore_mm",c.bore,0.001);
|
||||||
|
r.field("carburetor_diameter_mm",c.carburetor_diameter,0.001);
|
||||||
|
r.field("crankcase_clearance_cc",c.crankcase_clearance,1e-06);
|
||||||
|
r.field("exhaust_cell_length_mm",c.exhaust_cell_length,0.001);
|
||||||
|
r.field("exhaust_duration_deg",c.exhaust_duration);
|
||||||
|
r.field("exhaust_width_mm",c.exhaust_width,0.001);
|
||||||
|
r.field("friction_bar",c.friction_bar);
|
||||||
|
r.field("friction_bar_per_mps",c.friction_bar_per_mps);
|
||||||
|
r.field("friction_bar_per_mps2",c.friction_bar_per_mps2);
|
||||||
|
r.field("idle_minimum_advance_deg",c.idle_minimum_advance);
|
||||||
|
r.field("idle_slide_opening",c.idle_slide_opening);
|
||||||
|
r.field("idle_spark_gain",c.idle_spark_gain);
|
||||||
|
r.field("jet_scale",c.jet_scale);
|
||||||
|
r.field("main_jet_scale",c.main_jet_scale);
|
||||||
|
r.field("minimum_burn_s",c.minimum_burn_seconds);
|
||||||
|
r.field("needle_jet_scale",c.needle_jet_scale);
|
||||||
|
r.field("needle_low_scale",c.needle_low_scale);
|
||||||
|
r.field("needle_mid_scale",c.needle_mid_scale);
|
||||||
|
r.field("needle_off_idle_scale",c.needle_off_idle_scale);
|
||||||
|
r.field("needle_quarter_scale",c.needle_quarter_scale);
|
||||||
|
r.field("needle_tip_scale",c.needle_tip_scale);
|
||||||
|
r.field("needle_transition_scale",c.needle_transition_scale);
|
||||||
|
r.field("needle_upper_scale",c.needle_upper_scale);
|
||||||
|
r.field("pilot_jet_scale",c.pilot_jet_scale);
|
||||||
|
r.field("pilot_tip_scale",c.pilot_tip_scale);
|
||||||
|
r.field("pilot_transition_scale",c.pilot_transition_scale);
|
||||||
|
r.field("rod_mm",c.rod,0.001);
|
||||||
|
r.field("stroke_mm",c.stroke,0.001);
|
||||||
|
r.field("transfer_duration_deg",c.transfer_duration);
|
||||||
|
r.field("transfer_width_mm",c.transfer_width,0.001);
|
||||||
|
r.field("trapped_compression",c.trapped_compression);
|
||||||
|
const auto exhaust=r.text("exhaust","racing");
|
||||||
|
if(exhaust!="racing" && exhaust!="straight")r.fail("exhaust must be racing or straight");
|
||||||
|
c.exhaust=exhaust=="racing"?TwoStrokeExhaust::Racing:TwoStrokeExhaust::Straight;
|
||||||
|
if(const auto* sections=r.array("chamber_sections_mm")) {
|
||||||
|
if(sections->size()!=c.racing_pipe.size())r.fail("chamber_sections_mm must have six sections");
|
||||||
|
for(std::size_t i=0;i<sections->size();++i) {
|
||||||
|
const auto* section=sections->get(i)->as_array();
|
||||||
|
if(!section || section->size()!=3)r.fail("chamber sections must be [length, inlet diameter, outlet diameter] in mm");
|
||||||
|
auto& p=c.racing_pipe[i];
|
||||||
|
p.length=r.number(*section->get(0))*.001f;
|
||||||
|
p.inlet_diameter=r.number(*section->get(1))*.001f;
|
||||||
|
p.outlet_diameter=r.number(*section->get(2))*.001f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_engine(Reader r,EngineConfig& c) {
|
||||||
|
r.required({"cylinders","layout","topology","phase_degrees"});
|
||||||
|
r.field("bank_angle_deg",c.bank_angle);
|
||||||
|
r.field("collector_volume_litres",c.collector_volume,0.001);
|
||||||
|
r.field("cylinders",c.cylinders);
|
||||||
|
r.field("initial_rpm",c.initial_rpm);
|
||||||
|
r.field("initialize_phased_charge",c.initialize_phased_charge);
|
||||||
|
r.field("maximum_load_torque_nm",c.maximum_load_torque);
|
||||||
|
r.field("minimum_substeps",c.minimum_substeps);
|
||||||
|
r.field("phase_degrees",c.phase_degrees);
|
||||||
|
r.field("pipe_damping",c.pipe_damping);
|
||||||
|
r.field("plenum_volume_litres",c.plenum_volume,0.001);
|
||||||
|
r.field("starter_torque_nm",c.starter_torque);
|
||||||
|
r.field("throttle_diameter_mm",c.throttle_diameter,0.001);
|
||||||
|
const auto layout=r.text("layout","inline"),topology=r.text("topology","collector");
|
||||||
|
if(layout=="inline")c.layout=EngineLayout::Inline;
|
||||||
|
else if(layout=="v")c.layout=EngineLayout::V;
|
||||||
|
else if(layout=="boxer")c.layout=EngineLayout::Boxer;
|
||||||
|
else r.fail("layout must be inline, v or boxer");
|
||||||
|
if(topology=="collector")c.topology=ExhaustTopology::Collector;
|
||||||
|
else if(topology=="staged")c.topology=ExhaustTopology::Staged;
|
||||||
|
else if(topology=="american_v8")c.topology=ExhaustTopology::AmericanV8;
|
||||||
|
else r.fail("unknown exhaust topology '"+topology+"'");
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
void read_fuel(Reader r,EngineConfig& c) {
|
||||||
|
const auto system=r.text("system","injection");
|
||||||
|
if(system!="injection" && system!="carburetor")r.fail("system must be injection or carburetor");
|
||||||
|
c.carbureted=system=="carburetor";
|
||||||
|
r.field("pilot_jet_diameter_mm",c.fuel.pilot_jet_diameter,0.001);
|
||||||
|
r.field("main_jet_diameter_mm",c.fuel.main_jet_diameter,0.001);
|
||||||
|
r.field("needle_jet_diameter_mm",c.fuel.needle_jet_diameter,0.001);
|
||||||
|
r.field("needle_low_scale",c.fuel.needle_low_scale);
|
||||||
|
r.field("needle_mid_scale",c.fuel.needle_mid_scale);
|
||||||
|
r.field("needle_off_idle_scale",c.fuel.needle_off_idle_scale);
|
||||||
|
r.field("needle_transition_scale",c.fuel.needle_transition_scale);
|
||||||
|
r.field("needle_tip_scale",c.fuel.needle_tip_scale);
|
||||||
|
r.field("needle_quarter_scale",c.fuel.needle_quarter_scale);
|
||||||
|
r.field("needle_upper_scale",c.fuel.needle_upper_scale);
|
||||||
|
r.field("pilot_transition_scale",c.fuel.pilot_transition_scale);
|
||||||
|
r.field("pilot_tip_scale",c.fuel.pilot_tip_scale);
|
||||||
|
r.field("fuel_density",c.fuel.fuel_density);
|
||||||
|
r.field("pilot_discharge_coefficient",c.fuel.pilot_discharge_coefficient);
|
||||||
|
r.field("main_discharge_coefficient",c.fuel.main_discharge_coefficient);
|
||||||
|
r.field("bowl_head_pressure",c.fuel.bowl_head_pressure);
|
||||||
|
r.field("film_fraction",c.fuel.film_fraction);
|
||||||
|
r.field("film_time_constant",c.fuel.film_time_constant);
|
||||||
|
r.field("overrun_rpm",c.fuel.overrun_rpm);
|
||||||
|
r.field("overrun_exit_rpm",c.fuel.overrun_exit_rpm);
|
||||||
|
r.field("bowl_capacity_kg",c.fuel.bowl_capacity);
|
||||||
|
r.field("bowl_refill_kg_per_s",c.fuel.bowl_refill_rate);
|
||||||
|
r.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate_ecu(const EcuConfig& c,const std::string& path) {
|
||||||
|
require(c.idle_rpm>0 && c.idle_base_opening>=0 && c.idle_base_opening<=1 &&
|
||||||
|
c.idle_max_opening>=c.idle_base_opening && c.idle_max_opening<=1,path,"ecu: invalid idle calibration");
|
||||||
|
require(c.rev_limit_rpm>c.rev_resume_rpm && c.rev_resume_rpm>0 &&
|
||||||
|
c.overrun_cut_rpm>c.overrun_resume_rpm && c.overrun_resume_rpm>0,path,"ecu: invalid cut/resume RPM");
|
||||||
|
require(c.throttle_time_constant>0 && c.throttle_rate>0 && c.idle_spark_gain>=0 &&
|
||||||
|
c.throttle_closing_time_constant>=0 && c.throttle_closing_rate>=0,path,"ecu: invalid actuator calibration");
|
||||||
|
require(c.stoichiometric_afr>0 && c.power_afr>0,path,"ecu: AFR targets must be positive");
|
||||||
|
}
|
||||||
|
void validate_vehicle(const VehicleConfig& c,const std::string& path) {
|
||||||
|
require(c.mass>0 && c.wheel_radius>0 && c.rotational_inertia>=0 &&
|
||||||
|
c.primary_reduction>0 && c.final_drive_ratio>0 && c.clutch_capacity>0 &&
|
||||||
|
c.clutch_engagement_rate>0 && c.rolling_resistance>=0 &&
|
||||||
|
c.drag_coefficient>=0 && c.frontal_area>0 && c.air_density>0 && c.gravity>0,
|
||||||
|
path,"vehicle: invalid dimensions, ratios or resistance");
|
||||||
|
require(!c.gear_ratios.empty() && c.gear_ratios.size()<=16,path,"vehicle: expected 1..16 gear ratios");
|
||||||
|
for(float ratio:c.gear_ratios)require(ratio>0,path,"vehicle: gear ratios must be positive");
|
||||||
|
}
|
||||||
|
void validate_two(const TwoStrokeConfig& c,const std::string& path) {
|
||||||
|
require(c.bore>0 && c.bore<=1 && c.stroke>0 && c.stroke<=1 && c.rod>c.stroke*.5f &&
|
||||||
|
c.trapped_compression>1 && c.trapped_compression<=50,path,"two_stroke: invalid cylinder geometry");
|
||||||
|
require(c.transfer_duration>0 && c.transfer_duration<c.exhaust_duration && c.exhaust_duration<360 &&
|
||||||
|
c.transfer_width>0 && c.exhaust_width>0 && c.crankcase_clearance>0,
|
||||||
|
path,"two_stroke: invalid port timing or crankcase geometry");
|
||||||
|
require(c.idle_slide_opening>=0 && c.idle_slide_opening<=1 && c.initial_rpm>0 && c.initial_rpm<=30000 &&
|
||||||
|
c.crankshaft_inertia>0 && c.starter_torque>=0 && c.maximum_load_torque>=0 && c.idle_spark_gain>=0,
|
||||||
|
path,"two_stroke: invalid idle or crankshaft calibration");
|
||||||
|
require(c.carburetor_diameter>0 && c.minimum_burn_seconds>0 &&
|
||||||
|
c.friction_bar>=0 && c.friction_bar_per_mps>=0 && c.friction_bar_per_mps2>=0,
|
||||||
|
path,"two_stroke: invalid carburetor, burn time or friction");
|
||||||
|
for(float value:{c.jet_scale,c.pilot_jet_scale,c.main_jet_scale,c.needle_jet_scale,
|
||||||
|
c.needle_low_scale,c.needle_mid_scale,c.needle_off_idle_scale,c.needle_transition_scale,
|
||||||
|
c.needle_tip_scale,c.needle_quarter_scale,c.needle_upper_scale,c.pilot_transition_scale,c.pilot_tip_scale})
|
||||||
|
require(value>0,path,"two_stroke: jet and needle scales must be positive");
|
||||||
|
require(c.exhaust_cell_length>=.002f && c.exhaust_cell_length<=1,path,"two_stroke: exhaust cell length outside 2..1000 mm");
|
||||||
|
float total=0;
|
||||||
|
for(auto p:c.racing_pipe) {
|
||||||
|
require(p.length>0 && p.inlet_diameter>0 && p.outlet_diameter>0,path,"two_stroke: invalid chamber section");
|
||||||
|
total+=p.length;
|
||||||
|
}
|
||||||
|
require(total/c.exhaust_cell_length<=4096,path,"two_stroke: chamber exceeds 4096 cells");
|
||||||
|
validate_ecu(c.ecu,path);validate_vehicle(c.vehicle,path);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void validate_engine_config(const EngineConfig& c,const std::string& path) {
|
||||||
|
require(c.cylinders>=1 && c.cylinders<=12 && (c.layout==EngineLayout::Inline || c.cylinders%2==0),
|
||||||
|
path,"engine: expected 1..12 cylinders and even banked cylinder counts");
|
||||||
|
const auto& cylinder=c.cylinder;
|
||||||
|
require(cylinder.bore>0 && cylinder.bore<=1 && cylinder.stroke>0 && cylinder.stroke<=1 &&
|
||||||
|
cylinder.rod>cylinder.stroke*.5f && cylinder.compression>1 && cylinder.compression<=50,
|
||||||
|
path,"cylinder: invalid bore, stroke, rod or compression");
|
||||||
|
require(cylinder.intake_valve_count>=1 && cylinder.intake_valve_count<=8 &&
|
||||||
|
cylinder.exhaust_valve_count>=1 && cylinder.exhaust_valve_count<=8 &&
|
||||||
|
cylinder.intake_valve_diameter>0 && cylinder.exhaust_valve_diameter>0 &&
|
||||||
|
cylinder.intake_valve_lift>0 && cylinder.exhaust_valve_lift>0,path,"cylinder: invalid valve geometry");
|
||||||
|
for(float angle:{cylinder.intake_open,cylinder.intake_close,cylinder.exhaust_open,cylinder.exhaust_close})
|
||||||
|
require(angle>=0 && angle<720,path,"cylinder: valve events must be in [0,720) degrees");
|
||||||
|
require(cylinder.intake_open!=cylinder.intake_close && cylinder.exhaust_open!=cylinder.exhaust_close &&
|
||||||
|
cylinder.burn_duration>0 && cylinder.wiebe_a>0 && cylinder.wiebe_m>=0 &&
|
||||||
|
cylinder.energy_variation>=0 && cylinder.energy_variation<=1 &&
|
||||||
|
cylinder.misfire_probability>=0 && cylinder.misfire_probability<=1 &&
|
||||||
|
cylinder.fuel_lhv>0 && cylinder.wall_area>0 && cylinder.heat_transfer>=0 && cylinder.ambient_temperature>0,
|
||||||
|
path,"cylinder: invalid timing or combustion calibration");
|
||||||
|
require(c.crankshaft.inertia>0 && c.crankshaft.friction_constant>=0 && c.crankshaft.friction_viscous>=0 &&
|
||||||
|
c.crankshaft.friction_quadratic>=0,path,"crankshaft: invalid inertia or friction");
|
||||||
|
require(c.initial_rpm>0 && c.initial_rpm<=30000 && c.starter_torque>=0 && c.maximum_load_torque>=0 &&
|
||||||
|
c.plenum_volume>0 && c.collector_volume>0 && c.throttle_diameter>0 &&
|
||||||
|
c.pipe_damping>=0 && c.minimum_substeps>=1 && c.minimum_substeps<=64 &&
|
||||||
|
c.bank_angle>0 && c.bank_angle<=180,path,"engine: invalid startup, intake or solver calibration");
|
||||||
|
require(c.phase_degrees.size()==static_cast<std::size_t>(c.cylinders),path,"engine: phase_degrees needs one value per cylinder");
|
||||||
|
for(float phase:c.phase_degrees)require(std::isfinite(phase) && phase>=0 && phase<720,path,"engine: invalid cylinder phase");
|
||||||
|
require(!c.american_headers() || (c.cylinders==8 && c.layout==EngineLayout::V),path,"engine: american_v8 topology requires a V8");
|
||||||
|
require(!c.staged_headers() || c.cylinders>=3,path,"engine: staged topology requires at least three cylinders");
|
||||||
|
const int expected=c.american_headers()?26:1+2*c.cylinders+(c.dual_outlets()?2:1)+(c.staged_headers()?c.header_groups():0);
|
||||||
|
int count=0,cells=0,silenced=0;std::set<std::string> ids;
|
||||||
|
for(const auto& p:c.pipes) {
|
||||||
|
require(!p.id.empty() && ids.insert(p.id).second,path,"pipes: missing or duplicate pipe group id");
|
||||||
|
require(p.count>=1 && p.count<=12,path,"pipes: count must be in 1..12");
|
||||||
|
if(p.silenced) {
|
||||||
|
require(c.has_silenced_exhaust() && c.topology==ExhaustTopology::Collector &&
|
||||||
|
count==1+2*c.cylinders && p.count==1,path,"pipes: silenced profile must be the collector's final outlet");
|
||||||
|
++silenced;
|
||||||
|
} else {
|
||||||
|
require(p.diameter>0 && p.diameter<=2 && p.length>0 && p.length<=100 && p.cells>=1 && p.cells<=4096,
|
||||||
|
path,"pipes: invalid diameter, length or cell count");
|
||||||
|
cells+=p.cells*p.count;
|
||||||
|
}
|
||||||
|
count+=p.count;
|
||||||
|
}
|
||||||
|
require(count==expected && count<=32 && cells<=32768,path,"pipes: groups do not match the selected topology or exceed mesh limits");
|
||||||
|
require(silenced<=1,path,"pipes: only one silenced outlet is supported");
|
||||||
|
if(c.carbureted) {
|
||||||
|
const auto& f=c.fuel;
|
||||||
|
for(float value:{f.pilot_jet_diameter,f.main_jet_diameter,f.needle_jet_diameter,f.needle_low_scale,
|
||||||
|
f.needle_mid_scale,f.needle_off_idle_scale,f.needle_transition_scale,f.needle_tip_scale,
|
||||||
|
f.needle_quarter_scale,f.needle_upper_scale,f.pilot_transition_scale,f.pilot_tip_scale,
|
||||||
|
f.fuel_density,f.pilot_discharge_coefficient,f.main_discharge_coefficient,f.bowl_capacity,f.bowl_refill_rate,f.film_time_constant})
|
||||||
|
require(value>0,path,"fuel: jets, scales, density and time constants must be positive");
|
||||||
|
require(f.film_fraction>=0 && f.film_fraction<=.8f && f.bowl_head_pressure>=0 &&
|
||||||
|
f.overrun_rpm>f.overrun_exit_rpm && f.overrun_exit_rpm>0,path,"fuel: invalid film or overrun calibration");
|
||||||
|
}
|
||||||
|
validate_ecu(c.ecu,path);validate_vehicle(c.vehicle,path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScenarioExample load_engine_file(const std::filesystem::path& path) {
|
||||||
|
toml::table table;
|
||||||
|
try { table=toml::parse_file(path.string()); }
|
||||||
|
catch(const toml::parse_error& error) {std::ostringstream message;message<<error;throw std::runtime_error(message.str());}
|
||||||
|
Reader root(table,path.string());
|
||||||
|
int version=0;root.field("schema_version",version);
|
||||||
|
if(version!=1)root.fail("schema_version must be 1");
|
||||||
|
ScenarioExample e;
|
||||||
|
root.field("id",e.id);root.field("name",e.title);root.field("category",e.category);
|
||||||
|
root.field("description",e.description);root.field("order",e.order);
|
||||||
|
if(e.id.empty() || e.title.empty() || e.category.empty())root.fail("id, name and category are required");
|
||||||
|
if(e.id.find_first_not_of("abcdefghijklmnopqrstuvwxyz0123456789-_")!=std::string::npos)root.fail("id must use lowercase letters, digits, - or _");
|
||||||
|
const auto cycle=root.text("cycle");
|
||||||
|
if(cycle=="two_stroke") {
|
||||||
|
e.kind=ScenarioKind::TwoStroke;
|
||||||
|
read_two_stroke(root.section("two_stroke",true),e.two);
|
||||||
|
read_ecu(root.section("ecu"),e.two.ecu);
|
||||||
|
read_vehicle(root.section("vehicle"),e.two.vehicle);
|
||||||
|
validate_two(e.two,path.string());
|
||||||
|
} else if(cycle=="four_stroke") {
|
||||||
|
read_engine(root.section("engine",true),e.engine);
|
||||||
|
read_cylinder(root.section("cylinder",true),e.engine.cylinder);
|
||||||
|
read_crankshaft(root.section("crankshaft",true),e.engine.crankshaft);
|
||||||
|
read_ecu(root.section("ecu",true),e.engine.ecu);
|
||||||
|
read_vehicle(root.section("vehicle",true),e.engine.vehicle);
|
||||||
|
read_fuel(root.section("fuel",true),e.engine);
|
||||||
|
read_silencers(root.section("silencers"),e.exhaust);
|
||||||
|
auto junctions=root.section("junctions");
|
||||||
|
junctions.field("h_pipe",e.junctions.h_pipe);junctions.field("junction_volume_cc",e.junctions.junction_volume,1e-6);
|
||||||
|
junctions.finish();
|
||||||
|
if(const auto* pipes=root.array("pipes"))for(const auto& value:*pipes) {
|
||||||
|
if(!value.is_table())root.fail("pipes entries must be tables");
|
||||||
|
Reader r(*value.as_table(),path.string()+".pipes");PipeConfig p;
|
||||||
|
r.field("id",p.id);r.field("count",p.count);
|
||||||
|
const auto profile=r.text("profile");
|
||||||
|
if(!profile.empty() && profile!="silenced")r.fail("unknown pipe profile '"+profile+"'");
|
||||||
|
p.silenced=profile=="silenced";
|
||||||
|
r.field("diameter_mm",p.diameter,.001);r.field("length_mm",p.length,.001);r.field("cells",p.cells);
|
||||||
|
r.finish();e.engine.pipes.push_back(std::move(p));
|
||||||
|
}
|
||||||
|
validate_engine_config(e.engine,path.string());
|
||||||
|
require(e.junctions.junction_volume>0 && e.junctions.junction_volume<=1,path.string(),"junction_volume_cc outside (0,1000000]");
|
||||||
|
if(e.engine.has_silenced_exhaust()) {
|
||||||
|
require(e.exhaust.cell_length>=.002f && e.exhaust.cell_length<=1,path.string(),"silencers: cell length outside 2..1000 mm");
|
||||||
|
require(e.exhaust.diameter>0 && e.exhaust.diameter<=2 && e.exhaust.inlet_length>0 &&
|
||||||
|
e.exhaust.outlet_length>0 && e.exhaust.connecting_length>0,
|
||||||
|
path.string(),"silencers: invalid connecting pipe geometry");
|
||||||
|
float length=e.exhaust.inlet_length+e.exhaust.outlet_length+e.exhaust.connecting_length;
|
||||||
|
for(const auto& s:{e.exhaust.resonator,e.exhaust.muffler}) {
|
||||||
|
require(s.volume>0 && s.volume<=1 && s.length>0 && s.core_diameter>0 && s.core_diameter<=2 &&
|
||||||
|
s.passage_diameter>0 && s.passage_diameter<=2 && s.chambers>=1 && s.chambers<=16 &&
|
||||||
|
s.resistance>=0 && s.loss_coefficient>=0,path.string(),"silencers: invalid chamber geometry or resistance");
|
||||||
|
length+=s.length;
|
||||||
|
}
|
||||||
|
require(length/e.exhaust.cell_length+6*(e.exhaust.resonator.chambers+e.exhaust.muffler.chambers)+6<=4096,
|
||||||
|
path.string(),"silencers: mesh exceeds 4096 cells");
|
||||||
|
try {const auto pipe=make_silenced_exhaust(e.exhaust);require(pipe.cells.size()<=4096,path.string(),"silencers: mesh exceeds 4096 cells");}
|
||||||
|
catch(const std::exception& error) {root.fail(std::string("silencers: ")+error.what());}
|
||||||
|
}
|
||||||
|
} else root.fail("cycle must be two_stroke or four_stroke");
|
||||||
|
root.finish();return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ScenarioExample> load_engine_directory(const std::filesystem::path& directory) {
|
||||||
|
require(std::filesystem::is_directory(directory),directory.string(),"engine directory does not exist");
|
||||||
|
std::vector<std::filesystem::path> files;
|
||||||
|
for(const auto& entry:std::filesystem::directory_iterator(directory))
|
||||||
|
if(entry.is_regular_file() && entry.path().extension()==".toml")files.push_back(entry.path());
|
||||||
|
std::sort(files.begin(),files.end());
|
||||||
|
std::vector<ScenarioExample> engines;std::set<std::string> ids;
|
||||||
|
for(const auto& file:files) {
|
||||||
|
auto engine=load_engine_file(file);
|
||||||
|
require(ids.insert(engine.id).second,file.string(),"duplicate engine id");
|
||||||
|
engines.push_back(std::move(engine));
|
||||||
|
}
|
||||||
|
require(!engines.empty(),directory.string(),"no .toml engine definitions found");
|
||||||
|
std::sort(engines.begin(),engines.end(),[](const auto& a,const auto& b) {
|
||||||
|
return a.order==b.order?a.id<b.id:a.order<b.order;
|
||||||
|
});
|
||||||
|
return engines;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path engine_directory() {
|
||||||
|
if(const char* override_path=std::getenv("FLUIDSIM_ENGINE_DIR");override_path && *override_path)return override_path;
|
||||||
|
std::vector<std::filesystem::path> candidates{std::filesystem::current_path()/"engines"};
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::wstring executable(32768,L'\0');
|
||||||
|
const auto size=GetModuleFileNameW(nullptr,executable.data(),static_cast<DWORD>(executable.size()));
|
||||||
|
if(size>0 && size<executable.size()) {executable.resize(size);candidates.push_back(std::filesystem::path(executable).parent_path()/"engines");}
|
||||||
|
#elif defined(__linux__)
|
||||||
|
std::error_code error;
|
||||||
|
const auto executable=std::filesystem::read_symlink("/proc/self/exe",error);
|
||||||
|
if(!error)candidates.push_back(executable.parent_path()/"engines");
|
||||||
|
#endif
|
||||||
|
candidates.emplace_back(FLUIDSIM_ENGINE_SOURCE_DIR);
|
||||||
|
for(const auto& candidate:candidates)if(std::filesystem::is_directory(candidate))return candidate;
|
||||||
|
throw std::runtime_error("Cannot find engines/. Set FLUIDSIM_ENGINE_DIR to the engine definitions directory.");
|
||||||
|
}
|
||||||
|
const std::vector<ScenarioExample>& scenario_examples() {
|
||||||
|
static const auto engines=load_engine_directory(engine_directory());return engines;
|
||||||
|
}
|
||||||
|
const ScenarioExample& scenario_example(const std::string& id) {
|
||||||
|
const auto& engines=scenario_examples();
|
||||||
|
const auto found=std::find_if(engines.begin(),engines.end(),[&](const auto& e) {return e.id==id;});
|
||||||
|
if(found==engines.end())throw std::runtime_error("Unknown engine id '"+id+"' in "+engine_directory().string());
|
||||||
|
return *found;
|
||||||
|
}
|
||||||
|
const ScenarioExample& preset_engine_definition(FourStrokePreset preset) {
|
||||||
|
return scenario_example(preset==FourStrokePreset::AmericanV8?"v8-h":preset==FourStrokePreset::SaabB205?"saab-straight":"twin");
|
||||||
|
}
|
||||||
|
} // namespace fluidsim::port
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
|
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
|
||||||
|
#include "FluidSim/ScenarioGeometry.hpp"
|
||||||
#include "FluidSim/Desktop/PressurePalette.hpp"
|
#include "FluidSim/Desktop/PressurePalette.hpp"
|
||||||
#include "FluidSim/Desktop/PipePath.hpp"
|
#include "FluidSim/Desktop/PipePath.hpp"
|
||||||
#include <SFML/OpenGL.hpp>
|
#include <SFML/OpenGL.hpp>
|
||||||
@@ -61,12 +62,7 @@ Vec3 sub(Vec3 a,Vec3 b) {return {a.x-b.x,a.y-b.y,a.z-b.z};}
|
|||||||
float length(Vec3 a) {return std::sqrt(a.x*a.x+a.y*a.y+a.z*a.z);}
|
float length(Vec3 a) {return std::sqrt(a.x*a.x+a.y*a.y+a.z*a.z);}
|
||||||
Vec3 pressure_color(float pressure) {return pressure_tint(pressure);}
|
Vec3 pressure_color(float pressure) {return pressure_tint(pressure);}
|
||||||
void color(Vec3 c) {glColor3f(c.x,c.y,c.z);}
|
void color(Vec3 c) {glColor3f(c.x,c.y,c.z);}
|
||||||
struct Route {std::array<Vec3,5> points{};std::array<float,5> fractions{};unsigned count{};};
|
using Route = PipeRoute;
|
||||||
// Scene points specify a fraction of the pipe's physical length, converted
|
|
||||||
// to metre-based PipeAnchor distances when constructing the cached surface.
|
|
||||||
Route route(std::initializer_list<PipeAnchor> p) {
|
|
||||||
Route r;for(auto v:p) {r.points[r.count]=v.position;r.fractions[r.count++]=v.distance;}return r;
|
|
||||||
}
|
|
||||||
Vec3 cross(Vec3 a,Vec3 b) {return {a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};}
|
Vec3 cross(Vec3 a,Vec3 b) {return {a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};}
|
||||||
float dot(Vec3 a,Vec3 b) {return a.x*b.x+a.y*b.y+a.z*b.z;}
|
float dot(Vec3 a,Vec3 b) {return a.x*b.x+a.y*b.y+a.z*b.z;}
|
||||||
Vec3 unit(Vec3 a) {float l=length(a);return l>1e-7f?Vec3{a.x/l,a.y/l,a.z/l}:Vec3{1,0,0};}
|
Vec3 unit(Vec3 a) {float l=length(a);return l>1e-7f?Vec3{a.x/l,a.y/l,a.z/l}:Vec3{1,0,0};}
|
||||||
@@ -162,55 +158,58 @@ struct EngineRenderer3D::Impl {
|
|||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
}
|
}
|
||||||
void engine(const ScenarioSnapshot& s) {
|
void engine(const ScenarioSnapshot& s) {
|
||||||
|
const auto routes=scenario_pipe_routes(s);
|
||||||
if(s.two_stroke) {
|
if(s.two_stroke) {
|
||||||
cylinder_part({0,0,0},s.cylinder_piston_fraction1,s.cylinder_pressure1,s.cylinder_angles[0]);
|
cylinder_part({0,0,0},s.cylinder_piston_fraction1,s.cylinder_pressure1,s.cylinder_angles[0]);
|
||||||
box({0,.07f,0},{.21f,.14f,.19f},pressure_color(s.crankcase_pressure));
|
box({0,.07f,0},{.21f,.14f,.19f},pressure_color(s.crankcase_pressure));
|
||||||
box({-.55f,.35f,0},{.18f,.12f,.15f},pressure_color(s.intake_pressure));
|
box({-.55f,.35f,0},{.18f,.12f,.15f},pressure_color(s.intake_pressure));
|
||||||
pipe(s,0,route({{0.0f,{-1.05f,.35f,0}},{1.0f,{-.64f,.35f,0}}}));
|
pipe(s,0,routes[0]);
|
||||||
pipe(s,1,route({{0.0f,{-.46f,.35f,0}},{0.3333333333333333f,{-.28f,.35f,0}},{0.6666666666666666f,{-.28f,.1f,0}},{1.0f,{-.105f,.1f,0}}}));
|
pipe(s,1,routes[1]);
|
||||||
pipe(s,2,route({{0.0f,{.105f,.09f,0}},{0.3333333333333333f,{.18f,.09f,-.12f}},{0.6666666666666666f,{.18f,.35f,-.12f}},{1.0f,{.05f,.35f,0}}}));
|
pipe(s,2,routes[2]);
|
||||||
pipe(s,3,route({{0.0f,{.055f,.40f,0}},{0.3333333333333333f,{.3f,.40f,0}},{0.6666666666666666f,{.58f,.23f,0}},{1.0f,{1.45f,.23f,0}}}));
|
pipe(s,3,routes[3]);
|
||||||
} else {
|
} else {
|
||||||
const int n=s.cylinder_count;const bool v8=s.v8_headers;
|
const int n=s.cylinder_count;const bool v8=s.v8_headers;
|
||||||
const bool banked=s.layout!=EngineLayout::Inline;const int per_bank=banked?n/2:n;
|
const bool banked=s.layout!=EngineLayout::Inline;const int per_bank=banked?n/2:n;
|
||||||
const float spacing=.21f,first=-(per_bank-1)*spacing*.5f;
|
const float spacing=.21f,first=-(per_bank-1)*spacing*.5f;
|
||||||
box({0,.64f,0},{per_bank*.21f,.12f,.15f},pressure_color(s.intake_pressure));
|
box({0,.64f,0},{per_bank*.21f,.12f,.15f},pressure_color(s.intake_pressure));
|
||||||
pipe(s,0,route({{0.0f,{-1.05f,.64f,0}},{0.25f,{-.5f,.64f,0}},{0.5f,{-.5f,.64f,-.1f}},{0.75f,{0,.64f,-.1f}},{1.0f,{0,.64f,0}}}));
|
pipe(s,0,routes[0]);
|
||||||
for(int i=0;i<n;++i) {
|
for(int i=0;i<n;++i) {
|
||||||
const float x=first+(i%per_bank)*spacing,z=0;
|
const float x=first+(i%per_bank)*spacing,z=0;
|
||||||
const float bank_tilt=s.layout==EngineLayout::Boxer?90.f:s.bank_angle*.5f;
|
const float bank_tilt=s.layout==EngineLayout::Boxer?90.f:s.bank_angle*.5f;
|
||||||
const float tilt=banked?(i<per_bank?-bank_tilt:bank_tilt):0;
|
const float tilt=banked?(i<per_bank?-bank_tilt:bank_tilt):0;
|
||||||
const auto port=[&](float y,float dx) {float a=tilt*pi3/180;return Vec3{x+dx,.1f+(y-.1f)*std::cos(a),z+(y-.1f)*std::sin(a)};};
|
|
||||||
cylinder_part({x,0,z},s.piston_fractions[i],s.cylinder_pressures[i],s.cylinder_angles[i],tilt);
|
cylinder_part({x,0,z},s.piston_fractions[i],s.cylinder_pressures[i],s.cylinder_angles[i],tilt);
|
||||||
const float cz=v8?(i<4?-.39f:.39f):.26f;
|
pipe(s,1+i,routes[1+i]);
|
||||||
const auto intake=port(.45f,-.055f),exhaust=port(.43f,.055f);
|
if(!v8) pipe(s,1+n+i,routes[1+n+i]);
|
||||||
pipe(s,1+i,route({{0,{x,.58f,0}},{.3f,{x,.55f,intake.z*.5f}},{.7f,{intake.x,.52f,intake.z}},{1,intake}}));
|
|
||||||
if(!v8) pipe(s,1+n+i,route({{0,exhaust},{.3f,{x+.12f,exhaust.y,cz}},{.6f,{x+.12f,.17f,cz}},{1,{.68f,.17f,cz}}}));
|
|
||||||
}
|
}
|
||||||
if(v8) {
|
if(v8) {
|
||||||
const std::array<int,8> pair{0,0,1,1,2,3,2,3};
|
|
||||||
for(int i=0;i<8;++i) {
|
for(int i=0;i<8;++i) {
|
||||||
const int bank=i/4, group=pair[i]%2;
|
pipe(s,9+i,routes[9+i]);
|
||||||
const float x=first+(i%4)*spacing, z=bank? .39f:-.39f;
|
|
||||||
const float a=(bank?28.f:-28.f)*pi3/180;
|
|
||||||
const Vec3 port{x+.055f,.1f+.33f*std::cos(a),.33f*std::sin(a)};
|
|
||||||
const float end_z=z+(group? .09f:-.09f);
|
|
||||||
pipe(s,9+i,route({{0,port},{.3f,{x+.12f,.34f,z}},{.65f,{.35f,.20f,end_z}},{1,{.74f,.17f,end_z}}}));
|
|
||||||
}
|
}
|
||||||
for(int i=0;i<4;++i) {
|
for(int i=0;i<4;++i) {
|
||||||
const float z=i<2?-.39f:.39f;
|
pipe(s,17+i,routes[17+i]);
|
||||||
pipe(s,17+i,route({{0,{.74f,.17f,z+(i%2?.09f:-.09f)}},{.5f,{.94f,.17f,z+(i%2?.06f:-.06f)}},{1,{1.14f,.17f,z}}}));
|
|
||||||
}
|
}
|
||||||
if(s.h_pipe) pipe(s,25,route({{0,{1.69f,.17f,-.39f}},{1,{1.69f,.17f,.39f}}}));
|
if(s.h_pipe) pipe(s,25,routes[25]);
|
||||||
|
}
|
||||||
|
if(s.staged_headers) {
|
||||||
|
const int exit=1+2*n, groups=n==3?1:2, secondary=exit+(s.dual_exhaust?2:1);
|
||||||
|
for(int group=0;group<groups;++group) {
|
||||||
|
const float z=group==0?-.26f:.26f;
|
||||||
|
pipe(s,secondary+group,routes[secondary+group]);
|
||||||
|
box({.68f,.17f,z},{.04f,.04f,.04f},pressure_color(s.pipe_states[secondary+group].front().pressure));
|
||||||
|
}
|
||||||
|
for(int bank=0;bank<(s.dual_exhaust?2:1);++bank) {
|
||||||
|
pipe(s,exit+bank,routes[exit+bank]);
|
||||||
|
}
|
||||||
|
rod({first-.07f,.1f,0},{first+(per_bank-1)*spacing+.07f,.1f,0},.022f,{.56f,.56f,.56f});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
for(int bank=0;bank<(v8?2:1);++bank) {
|
for(int bank=0;bank<(v8?2:1);++bank) {
|
||||||
float z=v8?(bank==0?-.39f:.39f):.26f;
|
float z=v8?(bank==0?-.39f:.39f):.26f;
|
||||||
if(!v8) box({.68f,.17f,z},{.12f,.10f,.12f},pressure_color(bank==0?s.exhaust_pressure:s.second_bank_pressure));
|
if(!v8) box({.68f,.17f,z},{.12f,.10f,.12f},pressure_color(bank==0?s.exhaust_pressure:s.second_bank_pressure));
|
||||||
if(s.silencers) {
|
if(s.silencers) {
|
||||||
const auto& c=s.exhaust_config;
|
const auto& c=s.exhaust_config;
|
||||||
const float total=c.inlet_length+c.resonator.length+c.connecting_length+c.muffler.length+c.outlet_length;
|
|
||||||
const float start=.74f;
|
const float start=.74f;
|
||||||
pipe(s,1+2*n,route({{0,{start,.17f,z}},{1,{start+total,.17f,z}}}));
|
pipe(s,1+2*n,routes[1+2*n]);
|
||||||
float x=start+c.inlet_length;
|
float x=start+c.inlet_length;
|
||||||
for(int index=0;index<2;++index) {
|
for(int index=0;index<2;++index) {
|
||||||
if(index) x+=c.connecting_length;
|
if(index) x+=c.connecting_length;
|
||||||
@@ -219,9 +218,9 @@ struct EngineRenderer3D::Impl {
|
|||||||
x+=silencer.length;
|
x+=silencer.length;
|
||||||
}
|
}
|
||||||
} else if(v8) {
|
} else if(v8) {
|
||||||
pipe(s,21+bank,route({{0,{1.14f,.17f,z}},{1,{1.69f,.17f,z}}}));
|
pipe(s,21+bank,routes[21+bank]);
|
||||||
pipe(s,23+bank,route({{0,{1.69f,.17f,z}},{1,{2.94f,.17f,z}}}));
|
pipe(s,23+bank,routes[23+bank]);
|
||||||
} else pipe(s,1+2*n,route({{0,{.74f,.17f,z}},{1,{1.45f,.17f,z}}}));
|
} else pipe(s,1+2*n,routes[1+2*n]);
|
||||||
if(bank==0)rod({first-.07f,.1f,0},{first+(per_bank-1)*spacing+.07f,.1f,0},.022f,{.56f,.56f,.56f});
|
if(bank==0)rod({first-.07f,.1f,0},{first+(per_bank-1)*spacing+.07f,.1f,0},.022f,{.56f,.56f,.56f});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,8 +235,8 @@ void EngineRenderer3D::draw(sf::RenderWindow& window,const ScenarioSnapshot& sta
|
|||||||
glViewport(x,y,w,h);glEnable(GL_SCISSOR_TEST);glScissor(x,y,w,h);glClear(GL_DEPTH_BUFFER_BIT);
|
glViewport(x,y,w,h);glEnable(GL_SCISSOR_TEST);glScissor(x,y,w,h);glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glDisable(GL_BLEND);glDisable(GL_TEXTURE_2D);glDisable(GL_CULL_FACE);
|
glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glDisable(GL_BLEND);glDisable(GL_TEXTURE_2D);glDisable(GL_CULL_FACE);
|
||||||
glEnable(GL_NORMALIZE);glEnable(GL_LIGHTING);glEnable(GL_LIGHT0);glEnable(GL_COLOR_MATERIAL);glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
|
glEnable(GL_NORMALIZE);glEnable(GL_LIGHTING);glEnable(GL_LIGHT0);glEnable(GL_COLOR_MATERIAL);glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
|
||||||
glMatrixMode(GL_PROJECTION);glLoadIdentity();const double near=.03,top=near*.41421356;
|
glMatrixMode(GL_PROJECTION);glLoadIdentity();const double nearClip=.03,halfHeight=nearClip*.41421356;
|
||||||
glFrustum(-top*w/h,top*w/h,-top,top,near,50);
|
glFrustum(-halfHeight*w/h,halfHeight*w/h,-halfHeight,halfHeight,nearClip,50);
|
||||||
glMatrixMode(GL_MODELVIEW);glLoadIdentity();
|
glMatrixMode(GL_MODELVIEW);glLoadIdentity();
|
||||||
const GLfloat light[]{-.4f,1,1,0},ambient[]{.35f,.35f,.35f,1},diffuse[]{1,1,1,1};
|
const GLfloat light[]{-.4f,1,1,0},ambient[]{.35f,.35f,.35f,1},diffuse[]{1,1,1,1};
|
||||||
glLightfv(GL_LIGHT0,GL_POSITION,light);glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
|
glLightfv(GL_LIGHT0,GL_POSITION,light);glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
|
||||||
|
|||||||
@@ -196,7 +196,31 @@ void draw_b205_scene(sf::RenderWindow& window, const sf::Font* font, const Scena
|
|||||||
|
|
||||||
void draw_schematic(sf::RenderWindow& window, const sf::Font* font, const ScenarioSnapshot& state) {
|
void draw_schematic(sf::RenderWindow& window, const sf::Font* font, const ScenarioSnapshot& state) {
|
||||||
|
|
||||||
if (state.cylinder_count == 8) {
|
if(state.staged_headers) {
|
||||||
|
const int n=state.cylinder_count, groups=n==3?1:2, split=n==3?n:(n+1)/2;
|
||||||
|
const int exit=1+2*n, secondary=exit+(state.dual_exhaust?2:1);
|
||||||
|
draw_label(window,font,"PULSE HEADERS / SMALL JUNCTION COLLECTORS",{25,185},sf::Color::White,16);
|
||||||
|
for(int i=0;i<n;++i) {
|
||||||
|
const int group=groups==1?0:(i<split?0:1);
|
||||||
|
const float x=40+(i%split)*145.f, top=group==0?235.f:500.f;
|
||||||
|
const std::string name="CYL "+std::to_string(i+1);
|
||||||
|
draw_cylinder(window,font,state.cylinder_pressures[i],state.piston_fractions[i],top,
|
||||||
|
name.c_str(),state.intake_areas[i]>1e-7f,state.exhaust_areas[i]>1e-7f,x);
|
||||||
|
draw_pipe(window,state,1+i,top-15,x-10,x+90);
|
||||||
|
draw_pipe(window,state,1+n+i,top+155,x-10,x+90);
|
||||||
|
draw_graph_line(window,{x+90,top+155},{950,top+155},{200,120,80});
|
||||||
|
}
|
||||||
|
for(int group=0;group<groups;++group) {
|
||||||
|
const float y=group==0?390.f:655.f;
|
||||||
|
draw_pipe(window,state,secondary+group,y,950,1080);
|
||||||
|
const float out_y=state.dual_exhaust?y:480.f;
|
||||||
|
draw_graph_line(window,{1080,y},{1110,out_y},{200,120,80});
|
||||||
|
}
|
||||||
|
for(int bank=0;bank<(state.dual_exhaust?2:1);++bank)
|
||||||
|
draw_pipe(window,state,exit+bank,state.dual_exhaust?(bank==0?390.f:655.f):480.f,1110,1260);
|
||||||
|
draw_label(window,font,state.dual_exhaust?"SEPARATE BANK OUTLETS":"STAGED MERGE / SINGLE OUTLET",{950,710},sf::Color::White,11);
|
||||||
|
}
|
||||||
|
else if (state.cylinder_count == 8) {
|
||||||
|
|
||||||
draw_label(window, font, "American 5.0 V8 | EFI / ECU | 7000 RPM | heavy flywheel", {25,185}, sf::Color::White, 16);
|
draw_label(window, font, "American 5.0 V8 | EFI / ECU | 7000 RPM | heavy flywheel", {25,185}, sf::Color::White, 16);
|
||||||
draw_pipe(window, state, 0, 205, 25, 115);
|
draw_pipe(window, state, 0, 205, 25, 115);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
#include "FluidSim/HelmholtzScenario.hpp"
|
#include "FluidSim/HelmholtzScenario.hpp"
|
||||||
#include "FluidSim/PulseTestScenario.hpp"
|
#include "FluidSim/PulseTestScenario.hpp"
|
||||||
#include "FluidSim/TwoStrokeScenario.hpp"
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
||||||
@@ -10,12 +11,12 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) try {
|
||||||
using namespace fluidsim::port;
|
using namespace fluidsim::port;
|
||||||
float duration_seconds = 1.0f;
|
float duration_seconds = 1.0f;
|
||||||
float throttle = 0.35f;
|
float throttle = 0.35f;
|
||||||
std::string scenario_name = "twin";
|
std::string scenario_name = "twin";
|
||||||
TwoStrokeConfig two_stroke_config;
|
auto two_stroke_config=scenario_example("yz-racing").two;
|
||||||
PipeSolver pipe_solver=PipeSolver::Rusanov;
|
PipeSolver pipe_solver=PipeSolver::Rusanov;
|
||||||
for (int i = 1; i + 1 < argc; ++i) {
|
for (int i = 1; i + 1 < argc; ++i) {
|
||||||
const std::string option = argv[i];
|
const std::string option = argv[i];
|
||||||
@@ -107,4 +108,7 @@ int main(int argc, char** argv) {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
} catch(const std::exception& error) {
|
||||||
|
std::cerr<<error.what()<<'\n';
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace {
|
|||||||
Vec3 sub(Vec3 a, Vec3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
|
Vec3 sub(Vec3 a, Vec3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
|
||||||
float length(Vec3 a) { return std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); }
|
float length(Vec3 a) { return std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); }
|
||||||
float distance(Vec3 a, Vec3 b) { return length(sub(a, b)); }
|
float distance(Vec3 a, Vec3 b) { return length(sub(a, b)); }
|
||||||
|
bool finite(Vec3 v) { return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); }
|
||||||
AudioFrame add(AudioFrame a, AudioFrame b) { return {a.left + b.left, a.right + b.right}; }
|
AudioFrame add(AudioFrame a, AudioFrame b) { return {a.left + b.left, a.right + b.right}; }
|
||||||
} // namespace
|
} // namespace
|
||||||
const char *scene_name(OutdoorScene scene) {
|
const char *scene_name(OutdoorScene scene) {
|
||||||
@@ -40,7 +41,10 @@ float OutdoorAudio::LowPass::process(float x) {
|
|||||||
OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), config_(config), late_reverb_(rate) {
|
OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), config_(config), late_reverb_(rate) {
|
||||||
if (rate < 8000 || !std::isfinite(config.sound_speed) || config.sound_speed <= 0 ||
|
if (rate < 8000 || !std::isfinite(config.sound_speed) || config.sound_speed <= 0 ||
|
||||||
!std::isfinite(config.monitor_cutoff_hz) || config.monitor_cutoff_hz <= 0 ||
|
!std::isfinite(config.monitor_cutoff_hz) || config.monitor_cutoff_hz <= 0 ||
|
||||||
!std::isfinite(config.pressure_gain) || config.pressure_gain < 0)
|
!std::isfinite(config.pressure_gain) || config.pressure_gain < 0 ||
|
||||||
|
!std::isfinite(config.ear_spacing) || config.ear_spacing < 0 ||
|
||||||
|
!finite(config.listener) || !finite(config.intake_position) || !finite(config.exhaust_position) ||
|
||||||
|
!finite(config.second_exhaust_position))
|
||||||
throw std::invalid_argument("invalid outdoor audio configuration");
|
throw std::invalid_argument("invalid outdoor audio configuration");
|
||||||
const float norm = length(config_.listener_right);
|
const float norm = length(config_.listener_right);
|
||||||
if (!std::isfinite(norm) || norm < 1e-6f)
|
if (!std::isfinite(norm) || norm < 1e-6f)
|
||||||
@@ -55,10 +59,40 @@ OutdoorAudio::OutdoorAudio(int rate, OutdoorAudioConfig config) : rate_(rate), c
|
|||||||
late_reverb_.tail_mix = 1;
|
late_reverb_.tail_mix = 1;
|
||||||
late_reverb_.set_decay_times(.75f, .45f, .22f);
|
late_reverb_.set_decay_times(.75f, .45f, .22f);
|
||||||
scene_alpha_ = 1 - std::exp(-1.0f / (rate_ * .02f));
|
scene_alpha_ = 1 - std::exp(-1.0f / (rate_ * .02f));
|
||||||
|
geometry_alpha_ = 1 - std::exp(-1.0f / (rate_ * .02f));
|
||||||
|
}
|
||||||
|
void OutdoorAudio::set_listener(AudioListener listener) {
|
||||||
|
const float norm=length(listener.right);
|
||||||
|
if(!finite(listener.position) || !std::isfinite(norm) || norm<1e-6f) return;
|
||||||
|
listener.right={listener.right.x/norm,listener.right.y/norm,listener.right.z/norm};
|
||||||
|
if(distance(config_.listener,listener.position)<1e-6f && distance(config_.listener_right,listener.right)<1e-6f)return;
|
||||||
|
config_.listener=listener.position;
|
||||||
|
config_.listener_right=listener.right;
|
||||||
|
update_geometry();
|
||||||
|
}
|
||||||
|
void OutdoorAudio::set_source_positions(const std::array<Vec3,3>& positions) {
|
||||||
|
for(auto p:positions)if(!finite(p))return;
|
||||||
|
config_.intake_position=positions[0];
|
||||||
|
config_.exhaust_position=positions[1];
|
||||||
|
config_.second_exhaust_position=positions[2];
|
||||||
|
update_geometry();
|
||||||
|
}
|
||||||
|
void OutdoorAudio::update_geometry() {
|
||||||
|
configure_emitter(emitters_[0],config_.intake_position);
|
||||||
|
configure_emitter(emitters_[1],config_.exhaust_position);
|
||||||
|
if(config_.dual_exhaust)configure_emitter(emitters_[2],config_.second_exhaust_position);
|
||||||
}
|
}
|
||||||
void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
|
void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
|
||||||
|
const bool initial=emitter.filtered.empty();
|
||||||
emitter.filtered.resize(std::bit_ceil(std::size_t(rate_)));
|
emitter.filtered.resize(std::bit_ceil(std::size_t(rate_)));
|
||||||
emitter.raw.resize(emitter.filtered.size()); // One second, validated path range.
|
emitter.raw.resize(emitter.filtered.size()); // Power-of-two propagation history, at least one second.
|
||||||
|
std::size_t path_index=0;
|
||||||
|
const float center_distance=std::max(distance(source,config_.listener),.1f);
|
||||||
|
const float max_delay=float(emitter.filtered.size()-2);
|
||||||
|
const float center_samples=center_distance*rate_/config_.sound_speed;
|
||||||
|
emitter.target_center_delay=std::min(center_samples,max_delay);
|
||||||
|
emitter.target_center_gain=center_samples<=max_delay?1/center_distance:0;
|
||||||
|
if(initial) {emitter.center_delay=emitter.target_center_delay;emitter.center_gain=emitter.target_center_gain;}
|
||||||
const auto ear = [&](float sign) {
|
const auto ear = [&](float sign) {
|
||||||
return Vec3{config_.listener.x + sign * config_.listener_right.x * config_.ear_spacing * .5f,
|
return Vec3{config_.listener.x + sign * config_.listener_right.x * config_.ear_spacing * .5f,
|
||||||
config_.listener.y + sign * config_.listener_right.y * config_.ear_spacing * .5f,
|
config_.listener.y + sign * config_.listener_right.y * config_.ear_spacing * .5f,
|
||||||
@@ -71,26 +105,31 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
|
|||||||
const float dl = std::max(distance(image, ear(-1)), .1f), dr = std::max(distance(image, ear(1)), .1f);
|
const float dl = std::max(distance(image, ear(-1)), .1f), dr = std::max(distance(image, ear(1)), .1f);
|
||||||
const float sl = (dl + extra) * rate_ / config_.sound_speed,
|
const float sl = (dl + extra) * rate_ / config_.sound_speed,
|
||||||
sr = (dr + extra) * rate_ / config_.sound_speed;
|
sr = (dr + extra) * rate_ / config_.sound_speed;
|
||||||
if (!std::isfinite(sl) || !std::isfinite(sr) || sl >= rate_ - 2 || sr >= rate_ - 2)
|
// Very distant camera pans fade out instead of wrapping the delay line.
|
||||||
throw std::invalid_argument("outdoor propagation path exceeds one second");
|
p.delay_left = p.target_delay_left = std::min(sl,max_delay);
|
||||||
p.delay_left = std::size_t(sl);
|
p.delay_right = p.target_delay_right = std::min(sr,max_delay);
|
||||||
p.delay_right = std::size_t(sr);
|
|
||||||
p.fraction_left = sl - p.delay_left;
|
|
||||||
p.fraction_right = sr - p.delay_right;
|
|
||||||
// A scatterer adds path length before the listener leg. Use spreading
|
// A scatterer adds path length before the listener leg. Use spreading
|
||||||
// over the complete reflected path rather than multiplying two
|
// over the complete reflected path rather than multiplying two
|
||||||
// inverse-distance losses, which made the woodland paths nearly
|
// inverse-distance losses, which made the woodland paths nearly
|
||||||
// inaudible.
|
// inaudible.
|
||||||
const float path_left = source_distance > 1.0f ? dl + source_distance : dl;
|
const float path_left = source_distance > 1.0f ? dl + source_distance : dl;
|
||||||
const float path_right = source_distance > 1.0f ? dr + source_distance : dr;
|
const float path_right = source_distance > 1.0f ? dr + source_distance : dr;
|
||||||
p.gain_left = coefficient / path_left;
|
p.gain_left = p.target_gain_left = sl<=max_delay?coefficient / path_left:0;
|
||||||
p.gain_right = coefficient / path_right;
|
p.gain_right = p.target_gain_right = sr<=max_delay?coefficient / path_right:0;
|
||||||
p.alpha = cutoff > 0 ? 1 - std::exp(-2 * pi * cutoff / rate_) : 1;
|
p.alpha = cutoff > 0 ? 1 - std::exp(-2 * pi * cutoff / rate_) : 1;
|
||||||
emitter.paths.push_back(p);
|
if(initial)emitter.paths.push_back(p);
|
||||||
|
else {
|
||||||
|
auto& current=emitter.paths[path_index];
|
||||||
|
current.target_delay_left=p.delay_left;current.target_delay_right=p.delay_right;
|
||||||
|
current.target_gain_left=p.gain_left;current.target_gain_right=p.gain_right;
|
||||||
|
}
|
||||||
|
++path_index;
|
||||||
};
|
};
|
||||||
path(source, 1, 0, 0);
|
path(source, 1, 0, 0);
|
||||||
path({source.x, 2 * config_.ground_y - source.y, source.z}, config_.ground_reflection, 1, 1800);
|
const bool ground_visible=(source.y-config_.ground_y)*(config_.listener.y-config_.ground_y)>=0;
|
||||||
path({2 * config_.wall_x - source.x, source.y, source.z}, config_.wall_reflection, 2, 4500);
|
const bool wall_visible=(source.x-config_.wall_x)*(config_.listener.x-config_.wall_x)>=0;
|
||||||
|
path({source.x, 2 * config_.ground_y - source.y, source.z}, ground_visible?config_.ground_reflection:0, 1, 1800);
|
||||||
|
path({2 * config_.wall_x - source.x, source.y, source.z}, wall_visible?config_.wall_reflection:0, 2, 4500);
|
||||||
// Single-bounce point scatterers: geometric paths, empirical scattering strengths.
|
// Single-bounce point scatterers: geometric paths, empirical scattering strengths.
|
||||||
for (Vec3 tree :
|
for (Vec3 tree :
|
||||||
std::array<Vec3, 6>{{{-4, 1, 3}, {3, 1, 5}, {5, 1, -2}, {-6, 2, -3}, {-2, 1, 7}, {6, 2, 6}}}) {
|
std::array<Vec3, 6>{{{-4, 1, 3}, {3, 1, 5}, {5, 1, -2}, {-6, 2, -3}, {-2, 1, 7}, {6, 2, 6}}}) {
|
||||||
@@ -102,20 +141,22 @@ void OutdoorAudio::configure_emitter(Emitter &emitter, Vec3 source) {
|
|||||||
// distinct before the diffuse FDN tail builds up.
|
// distinct before the diffuse FDN tail builds up.
|
||||||
constexpr float tunnel_width = 6.0f;
|
constexpr float tunnel_width = 6.0f;
|
||||||
constexpr float tunnel_height = 4.0f;
|
constexpr float tunnel_height = 4.0f;
|
||||||
|
const float tunnel_visible=config_.listener.y>=config_.ground_y && config_.listener.y<=tunnel_height &&
|
||||||
|
std::abs(config_.listener.z)<=tunnel_width*.5f?1.f:0.f;
|
||||||
const Vec3 ceiling{source.x, 2 * tunnel_height - source.y, source.z};
|
const Vec3 ceiling{source.x, 2 * tunnel_height - source.y, source.z};
|
||||||
const Vec3 left_wall{source.x, source.y, -tunnel_width - source.z};
|
const Vec3 left_wall{source.x, source.y, -tunnel_width - source.z};
|
||||||
const Vec3 right_wall{source.x, source.y, tunnel_width - source.z};
|
const Vec3 right_wall{source.x, source.y, tunnel_width - source.z};
|
||||||
path(ceiling, .82f, 4, 7200);
|
path(ceiling, .82f*tunnel_visible, 4, 7200);
|
||||||
path(left_wall, .82f, 4, 6200);
|
path(left_wall, .82f*tunnel_visible, 4, 6200);
|
||||||
path(right_wall, .82f, 4, 6200);
|
path(right_wall, .82f*tunnel_visible, 4, 6200);
|
||||||
path({ceiling.x, ceiling.y, left_wall.z}, .68f, 4, 5600);
|
path({ceiling.x, ceiling.y, left_wall.z}, .68f*tunnel_visible, 4, 5600);
|
||||||
path({ceiling.x, ceiling.y, right_wall.z}, .68f, 4, 5600);
|
path({ceiling.x, ceiling.y, right_wall.z}, .68f*tunnel_visible, 4, 5600);
|
||||||
path({source.x, source.y, -2 * tunnel_width + source.z}, .56f, 4, 4800);
|
path({source.x, source.y, -2 * tunnel_width + source.z}, .56f*tunnel_visible, 4, 4800);
|
||||||
path({source.x, source.y, 2 * tunnel_width + source.z}, .56f, 4, 4800);
|
path({source.x, source.y, 2 * tunnel_width + source.z}, .56f*tunnel_visible, 4, 4800);
|
||||||
const float cutoff = std::min(config_.monitor_cutoff_hz, rate_ * .45f);
|
const float cutoff = std::min(config_.monitor_cutoff_hz, rate_ * .45f);
|
||||||
const float w = 2 * pi * cutoff / rate_, cs = std::cos(w), alpha = std::sin(w) / std::sqrt(2.0f),
|
const float w = 2 * pi * cutoff / rate_, cs = std::cos(w), alpha = std::sin(w) / std::sqrt(2.0f),
|
||||||
a0 = 1 + alpha;
|
a0 = 1 + alpha;
|
||||||
emitter.filter = {(1 - cs) * .5f / a0, (1 - cs) / a0, (1 - cs) * .5f / a0, -2 * cs / a0,
|
if(initial)emitter.filter = {(1 - cs) * .5f / a0, (1 - cs) / a0, (1 - cs) * .5f / a0, -2 * cs / a0,
|
||||||
(1 - alpha) / a0};
|
(1 - alpha) / a0};
|
||||||
}
|
}
|
||||||
AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFrame &raw) {
|
AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFrame &raw) {
|
||||||
@@ -123,15 +164,31 @@ AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFr
|
|||||||
e.previous_flow = flow;
|
e.previous_flow = flow;
|
||||||
e.raw[e.position] = pressure;
|
e.raw[e.position] = pressure;
|
||||||
e.filtered[e.position] = monitor_filter_ ? e.filter.process(pressure) : pressure;
|
e.filtered[e.position] = monitor_filter_ ? e.filter.process(pressure) : pressure;
|
||||||
const auto read = [&](const std::vector<float> &data, std::size_t delay, float fraction) {
|
const auto read = [&](const std::vector<float> &data, float samples) {
|
||||||
|
const auto delay=std::size_t(samples);
|
||||||
|
const float fraction=samples-delay;
|
||||||
const auto i = (e.position + data.size() - delay) & (data.size() - 1);
|
const auto i = (e.position + data.size() - delay) & (data.size() - 1);
|
||||||
const auto j = (i + data.size() - 1) & (data.size() - 1);
|
const auto j = (i + data.size() - 1) & (data.size() - 1);
|
||||||
return data[i] + (data[j] - data[i]) * fraction;
|
return data[i] + (data[j] - data[i]) * fraction;
|
||||||
};
|
};
|
||||||
AudioFrame result{};
|
AudioFrame result{};
|
||||||
|
const auto smooth=[&](float& current,float target) {
|
||||||
|
const float next=current+(target-current)*geometry_alpha_;
|
||||||
|
// Finish the movement when float precision would stall the smoother.
|
||||||
|
current=next==current?target:next;
|
||||||
|
};
|
||||||
|
smooth(e.center_delay,e.target_center_delay);
|
||||||
|
smooth(e.center_gain,e.target_center_gain);
|
||||||
|
// The diffuse field is excited by a centre microphone, avoiding comb
|
||||||
|
// filtering from summing the differently delayed left and right ears.
|
||||||
|
reverb_input_+=read(e.filtered,e.center_delay)*e.center_gain;
|
||||||
for (auto &p : e.paths) {
|
for (auto &p : e.paths) {
|
||||||
const float l = read(e.filtered, p.delay_left, p.fraction_left) * p.gain_left;
|
smooth(p.delay_left,p.target_delay_left);
|
||||||
const float r = read(e.filtered, p.delay_right, p.fraction_right) * p.gain_right;
|
smooth(p.delay_right,p.target_delay_right);
|
||||||
|
smooth(p.gain_left,p.target_gain_left);
|
||||||
|
smooth(p.gain_right,p.target_gain_right);
|
||||||
|
const float l = read(e.filtered, p.delay_left) * p.gain_left;
|
||||||
|
const float r = read(e.filtered, p.delay_right) * p.gain_right;
|
||||||
p.state_left += p.alpha * (l - p.state_left);
|
p.state_left += p.alpha * (l - p.state_left);
|
||||||
p.state_right += p.alpha * (r - p.state_right);
|
p.state_right += p.alpha * (r - p.state_right);
|
||||||
if (std::abs(p.state_left) < 1e-25f)
|
if (std::abs(p.state_left) < 1e-25f)
|
||||||
@@ -142,8 +199,8 @@ AudioFrame OutdoorAudio::render(Emitter &e, float flow, AudioFrame &dry, AudioFr
|
|||||||
result.right += p.state_right * weights_[p.group];
|
result.right += p.state_right * weights_[p.group];
|
||||||
if (p.group == 0) {
|
if (p.group == 0) {
|
||||||
dry = add(dry, {l, r});
|
dry = add(dry, {l, r});
|
||||||
raw = add(raw, {read(e.raw, p.delay_left, p.fraction_left) * p.gain_left,
|
raw = add(raw, {read(e.raw, p.delay_left) * p.gain_left,
|
||||||
read(e.raw, p.delay_right, p.fraction_right) * p.gain_right});
|
read(e.raw, p.delay_right) * p.gain_right});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e.position = (e.position + 1) & (e.filtered.size() - 1);
|
e.position = (e.position + 1) & (e.filtered.size() - 1);
|
||||||
@@ -189,12 +246,13 @@ AudioFrame OutdoorAudio::process(float intake, float exhaust, float second_exhau
|
|||||||
tail_mix_ += (target_tail - tail_mix_) * scene_alpha_;
|
tail_mix_ += (target_tail - tail_mix_) * scene_alpha_;
|
||||||
last_dry_ = {};
|
last_dry_ = {};
|
||||||
last_raw_ = {};
|
last_raw_ = {};
|
||||||
|
reverb_input_ = 0;
|
||||||
const auto a = render(emitters_[0], intake, last_dry_, last_raw_);
|
const auto a = render(emitters_[0], intake, last_dry_, last_raw_);
|
||||||
const auto b = render(emitters_[1], exhaust, last_dry_, last_raw_);
|
const auto b = render(emitters_[1], exhaust, last_dry_, last_raw_);
|
||||||
const auto c = config_.dual_exhaust
|
const auto c = config_.dual_exhaust
|
||||||
? render(emitters_[2], second_exhaust, last_dry_, last_raw_)
|
? render(emitters_[2], second_exhaust, last_dry_, last_raw_)
|
||||||
: AudioFrame{};
|
: AudioFrame{};
|
||||||
const auto tail = late_reverb_.process_stereo((last_dry_.left + last_dry_.right) * .5f);
|
const auto tail = late_reverb_.process_stereo(reverb_input_);
|
||||||
return {a.left + b.left + c.left + tail.left * tail_mix_, a.right + b.right + c.right + tail.right * tail_mix_};
|
return {a.left + b.left + c.left + tail.left * tail_mix_, a.right + b.right + c.right + tail.right * tail_mix_};
|
||||||
}
|
}
|
||||||
} // namespace fluidsim::port
|
} // namespace fluidsim::port
|
||||||
|
|||||||
305
src/scenario.cpp
305
src/scenario.cpp
@@ -1,141 +1,62 @@
|
|||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/Scenario.hpp"
|
||||||
#include "FluidSim/Constants.hpp"
|
#include "FluidSim/Constants.hpp"
|
||||||
|
#include "FluidSim/ScenarioGeometry.hpp"
|
||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace fluidsim::port {
|
namespace fluidsim::port {
|
||||||
|
FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset preset)
|
||||||
|
: FourStrokeScenario(sample_rate,preset_engine_definition(preset)) {}
|
||||||
|
|
||||||
FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selected_preset, ExhaustConfig exhaust, V8ExhaustConfig v8_exhaust, EngineConfig engine)
|
FourStrokeScenario::FourStrokeScenario(int sample_rate,const ScenarioExample& definition)
|
||||||
: preset(selected_preset), exhaust_config(exhaust), v8_exhaust_config(v8_exhaust), engine_config(engine),
|
: FourStrokeScenario(sample_rate,definition.engine,definition.exhaust,definition.junctions) {}
|
||||||
intake_plenum(engine.cylinders ? .001f*engine.cylinders : selected_preset == FourStrokePreset::AmericanV8 ? 4e-3f : selected_preset == FourStrokePreset::SaabB205 ? 3e-3f : 1e-3f, 101325, 300),
|
|
||||||
exhaust_collector(engine.cylinders ? .00025f*engine.cylinders : selected_preset == FourStrokePreset::AmericanV8 ? 1.5e-3f : selected_preset == FourStrokePreset::SaabB205 ? .6e-3f : .15e-3f, 101325, 700),
|
FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configuration, ExhaustConfig exhaust, JunctionConfig fittings)
|
||||||
pipes(create_pipes(selected_preset, exhaust, v8_exhaust, engine)), dt_(1.f / std::max(sample_rate, 1)),
|
: exhaust_config(exhaust), junction_config(fittings), engine_config(std::move(configuration)),
|
||||||
boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate, [&] {
|
intake_plenum(engine_config.plenum_volume,101325,300),
|
||||||
|
exhaust_collector(engine_config.collector_volume,101325,700),
|
||||||
|
pipes(create_pipes(engine_config,exhaust)), dt_(1.f/std::max(sample_rate,1)),
|
||||||
|
boundaries_(pipes), solver_(pipes,boundaries_), audio_(sample_rate,[&] {
|
||||||
OutdoorAudioConfig config;
|
OutdoorAudioConfig config;
|
||||||
config.dual_exhaust = selected_preset == FourStrokePreset::AmericanV8;
|
config.dual_exhaust=engine_config.dual_outlets();
|
||||||
return config;
|
return config;
|
||||||
}()) {
|
}()) {
|
||||||
const bool saab = preset == FourStrokePreset::SaabB205;
|
const auto& engine=engine_config;
|
||||||
const bool v8 = preset == FourStrokePreset::AmericanV8;
|
const auto& junctions=junction_config;
|
||||||
const int count = cylinder_count();
|
const bool v8=engine.american_headers(),staged=engine.staged_headers();
|
||||||
if(engine.cylinders<0 || engine.cylinders>12 || (engine.layout!=EngineLayout::Inline && engine.cylinders%2)) throw std::invalid_argument("invalid engine cylinder layout");
|
const int count=cylinder_count();
|
||||||
starter_torque = v8 ? 180.f : saab ? 65.f : 18.f;
|
static_cast<CrankshaftConfig&>(crankshaft)=engine.crankshaft;
|
||||||
crankshaft.omega = (saab ? 1200 : 2000) * 2 * pi / 60;
|
static_cast<EcuConfig&>(ecu)=engine.ecu;
|
||||||
crankshaft.inertia = saab ? .18f : .04f;
|
static_cast<VehicleConfig&>(vehicle)=engine.vehicle;
|
||||||
crankshaft.friction_constant = saab ? 12.f : 5.f;
|
crankshaft.omega=engine.initial_rpm*2*pi/60;
|
||||||
crankshaft.friction_viscous = saab ? .012f : .003f;
|
starter_torque=engine.starter_torque;
|
||||||
if (saab) {
|
solver_.minimum_substeps=engine.minimum_substeps;
|
||||||
solver_.minimum_substeps = 2; // Acoustic CFL may subdivide further.
|
|
||||||
// Rough 9-5 wagon with driver; FM55 B05 overall reductions from WIS.
|
|
||||||
vehicle.mass = 1600 + 75;
|
|
||||||
vehicle.primary_reduction = 1;
|
|
||||||
vehicle.final_drive_ratio = 4.05f;
|
|
||||||
vehicle.gear_ratios = {13.70f/4.05f, 7.12f/4.05f, 4.77f/4.05f, 3.62f/4.05f, 2.67f/4.05f};
|
|
||||||
vehicle.wheel_radius = .317f;
|
|
||||||
vehicle.rotational_inertia = 3.6f;
|
|
||||||
vehicle.drag_coefficient = .32f;
|
|
||||||
vehicle.frontal_area = 2.2f;
|
|
||||||
vehicle.clutch_capacity = 300;
|
|
||||||
ecu.idle_rpm = 850;
|
|
||||||
ecu.idle_spark_gain = .025f;
|
|
||||||
ecu.idle_minimum_advance = -10;
|
|
||||||
ecu.rev_limit_rpm = 6500;
|
|
||||||
ecu.rev_resume_rpm = 6250;
|
|
||||||
ecu.overrun_cut_rpm = 2000;
|
|
||||||
ecu.overrun_resume_rpm = 1300;
|
|
||||||
}
|
|
||||||
if (v8) {
|
|
||||||
// Estimated mild 1980s 302-class pushrod small block and manual coupe.
|
|
||||||
crankshaft.omega = 1000 * 2*pi/60;
|
|
||||||
crankshaft.inertia = 1.1f;
|
|
||||||
crankshaft.friction_constant = 22;
|
|
||||||
crankshaft.friction_viscous = .025f;
|
|
||||||
vehicle.mass = 3000;
|
|
||||||
vehicle.primary_reduction = 1;
|
|
||||||
vehicle.final_drive_ratio = 6.16f / 1.7f; // 1.7x taller gearing in every gear.
|
|
||||||
vehicle.gear_ratios = {3.35f, 1.93f, 1.29f, 1.f, .68f};
|
|
||||||
vehicle.wheel_radius = .32f;
|
|
||||||
vehicle.clutch_capacity = 1100; // Hold V8 torque with the heavy flywheel/car.
|
|
||||||
vehicle.drag_coefficient = .4f;
|
|
||||||
vehicle.frontal_area = 2.f;
|
|
||||||
ecu.idle_rpm = 750;
|
|
||||||
ecu.idle_base_opening = .08f;
|
|
||||||
ecu.idle_spark_gain = .02f;
|
|
||||||
ecu.ignition_map = {{0, 10}, {750, 12}, {2500, 28}, {4000, 32}, {6000, 30}};
|
|
||||||
ecu.rev_limit_rpm = 7000;
|
|
||||||
ecu.rev_resume_rpm = 6750;
|
|
||||||
ecu.overrun_cut_rpm = 2000;
|
|
||||||
ecu.overrun_resume_rpm = 1100;
|
|
||||||
// Port-coupled fittings use one minimum substep; acoustic CFL may subdivide further.
|
|
||||||
solver_.minimum_substeps = 1;
|
|
||||||
}
|
|
||||||
if(engine.cylinders) {
|
|
||||||
solver_.minimum_substeps=1;
|
|
||||||
const float litres=count*pi*.25f*engine.bore*engine.bore*engine.stroke*1000;
|
|
||||||
crankshaft.omega=engine.idle_rpm*1.4f*2*pi/60;crankshaft.inertia=std::max(.04f,litres*.12f);
|
|
||||||
crankshaft.friction_constant=litres*4;crankshaft.friction_viscous=litres*.004f;
|
|
||||||
starter_torque=litres*25;vehicle.mass=count<=2?240:1400;vehicle.clutch_capacity=litres*220;
|
|
||||||
ecu.idle_rpm=engine.idle_rpm;ecu.rev_limit_rpm=engine.rev_limit;ecu.rev_resume_rpm=engine.rev_limit-250;
|
|
||||||
ecu.idle_spark_gain=.02f;ecu.idle_base_opening=.05f;
|
|
||||||
if(engine.carbureted) {
|
if(engine.carbureted) {
|
||||||
ecu.idle_control=Ecu::IdleControl::FixedOpening;ecu.idle_base_opening=engine.idle_opening;
|
carb_=std::make_unique<Carburetor>(engine.throttle_diameter);
|
||||||
|
static_cast<CarburetorConfig&>(*carb_)=engine.fuel;
|
||||||
|
// Carburetors keep metering fuel during both limiter and overrun cuts.
|
||||||
|
ecu.rev_limit_fuel_cut_enabled=false;
|
||||||
ecu.overrun_fuel_cut_enabled=false;
|
ecu.overrun_fuel_cut_enabled=false;
|
||||||
carb_=std::make_unique<Carburetor>(.085f);
|
ecu.overrun_ignition_cut_enabled=true;
|
||||||
const float jet_scale=engine.carb_jet_scale*std::sqrt(litres/.5f);
|
|
||||||
carb_->pilot_jet_diameter*=jet_scale*engine.carb_pilot_scale;carb_->main_jet_diameter*=jet_scale;carb_->needle_jet_diameter*=jet_scale;
|
|
||||||
carb_->bowl_capacity=.1f;carb_->bowl_refill_rate=.1f;
|
|
||||||
intake_plenum.add_fuel_mass(intake_plenum.mass()/14.7f);
|
intake_plenum.add_fuel_mass(intake_plenum.mass()/14.7f);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
vehicle.set_neutral();
|
vehicle.set_neutral();
|
||||||
vehicle.clutch_input = clutch;
|
vehicle.clutch_input = clutch;
|
||||||
const auto engines = cylinders();
|
const auto engines = cylinders();
|
||||||
const std::array<float, 8> phases{0, pi, 3*pi, 2*pi}; // I4 firing: 1-3-4-2
|
for (int i=0;i<count;++i) {
|
||||||
const std::array<int, 8> firing_order{1, 5, 4, 2, 6, 3, 7, 8};
|
auto& c=*engines[i];
|
||||||
for (int i = 0; i < count; ++i) {
|
static_cast<CylinderConfig&>(c)=engine.cylinder;
|
||||||
auto& c = *engines[i];
|
c.phase=engine.phase_degrees.at(i)*pi/180;
|
||||||
c.phase = phases[i%4];
|
|
||||||
if (v8) {
|
|
||||||
const auto position = std::find(firing_order.begin(), firing_order.end(), i+1) - firing_order.begin();
|
|
||||||
c.phase = std::fmod(4*pi - float(position)*pi*.5f, 4*pi);
|
|
||||||
c.bore = .1016f;
|
|
||||||
c.stroke = .0762f;
|
|
||||||
c.rod = .129f;
|
|
||||||
c.compression = 7.0f;
|
|
||||||
c.intake_open = 350; c.intake_close = 570;
|
|
||||||
c.exhaust_open = 140; c.exhaust_close = 370;
|
|
||||||
c.burn_duration = 65;
|
|
||||||
}
|
|
||||||
if (saab) {
|
|
||||||
c.bore = .090f;
|
|
||||||
c.stroke = .078f;
|
|
||||||
c.rod = .153f; // estimated, not a verified B205 rod measurement
|
|
||||||
c.compression = 8.8f;
|
|
||||||
c.intake_valve_count = c.exhaust_valve_count = 2;
|
|
||||||
}
|
|
||||||
c.intake_valve_diameter = saab ? .032f : .030f;
|
|
||||||
c.intake_valve_lift = saab ? .0085f : .008f;
|
|
||||||
c.exhaust_valve_diameter = saab ? .028f : .026f;
|
|
||||||
c.exhaust_valve_lift = saab ? .008f : .0075f;
|
|
||||||
if (v8) {
|
|
||||||
c.intake_valve_diameter = .045f; c.exhaust_valve_diameter = .037f;
|
|
||||||
c.intake_valve_lift = .010f; c.exhaust_valve_lift = .010f;
|
|
||||||
}
|
|
||||||
if(engine.cylinders) {
|
|
||||||
c.phase=engine.custom_phases?engine.phase_degrees[i]*pi/180:std::fmod(4*pi-i*4*pi/count,4*pi);
|
|
||||||
c.bore=engine.bore;c.stroke=engine.stroke;c.rod=engine.rod;c.compression=engine.compression;
|
|
||||||
c.intake_valve_diameter=engine.bore*.38f;c.exhaust_valve_diameter=engine.bore*.32f;
|
|
||||||
c.intake_valve_lift=engine.bore*.10f;c.exhaust_valve_lift=engine.bore*.09f;
|
|
||||||
c.transported_fuel=engine.carbureted;
|
c.transported_fuel=engine.carbureted;
|
||||||
}
|
c.rev_limit_enabled = ecu.rev_limit_enabled;
|
||||||
c.rev_limit_enabled = true;
|
|
||||||
c.rev_limit_rpm = ecu.rev_limit_rpm;
|
c.rev_limit_rpm = ecu.rev_limit_rpm;
|
||||||
c.rev_limit_hysteresis = 250;
|
c.rev_limit_hysteresis = 250;
|
||||||
c.rev_limit_cut_time = .020f;
|
c.rev_limit_cut_time = .020f;
|
||||||
c.ecu = &ecu;
|
c.ecu = &ecu;
|
||||||
c.initialize(crankshaft);
|
c.initialize(crankshaft);
|
||||||
if (saab || v8 || engine.cylinders) {
|
if (engine.initialize_phased_charge) {
|
||||||
// Initialize each phased cylinder at its actual piston position.
|
// Initialize each phased cylinder at its actual piston position.
|
||||||
c.volume = c.compute_volume(c.phase);
|
c.volume = c.compute_volume(c.phase);
|
||||||
c.air_mass = ambient_density * c.volume;
|
c.air_mass = ambient_density * c.volume;
|
||||||
@@ -148,13 +69,13 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
|||||||
header_area_ = pipes[1+count].area;
|
header_area_ = pipes[1+count].area;
|
||||||
tail_area_ = pipes[1+2*count].area;
|
tail_area_ = pipes[1+2*count].area;
|
||||||
for (auto& pipe : pipes) {
|
for (auto& pipe : pipes) {
|
||||||
pipe.damping_multiplier = saab ? 0.f : 1.f;
|
pipe.damping_multiplier = engine.pipe_damping;
|
||||||
pipe.energy_relaxation_rate = 0;
|
pipe.energy_relaxation_rate = 0;
|
||||||
}
|
}
|
||||||
auto& throttle_port = intake_plenum.create_port();
|
auto& throttle_port = intake_plenum.create_port();
|
||||||
throttle_port_=&throttle_port;
|
throttle_port_=&throttle_port;
|
||||||
solver_.add_component(intake_plenum);
|
solver_.add_component(intake_plenum);
|
||||||
if(!v8) solver_.add_component(exhaust_collector);
|
if(!v8 && !staged) solver_.add_component(exhaust_collector);
|
||||||
boundaries_.add_orifice(throttle_port, 0, false, throttle_area_, .7f);
|
boundaries_.add_orifice(throttle_port, 0, false, throttle_area_, .7f);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
auto& feed = intake_plenum.create_port();
|
auto& feed = intake_plenum.create_port();
|
||||||
@@ -164,11 +85,11 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
|||||||
boundaries_.add_orifice(feed, 1+i, true, runner_area_);
|
boundaries_.add_orifice(feed, 1+i, true, runner_area_);
|
||||||
boundaries_.add_orifice(engines[i]->intake, 1+i, false, intake_valve_areas_[i]);
|
boundaries_.add_orifice(engines[i]->intake, 1+i, false, intake_valve_areas_[i]);
|
||||||
boundaries_.add_orifice(engines[i]->exhaust, 1+count+i, true, exhaust_valve_areas_[i]);
|
boundaries_.add_orifice(engines[i]->exhaust, 1+count+i, true, exhaust_valve_areas_[i]);
|
||||||
if (!v8) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_);
|
if (!v8 && !staged) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_);
|
||||||
}
|
}
|
||||||
if(v8) {
|
if(v8) {
|
||||||
auto junction=[&](std::initializer_list<JunctionEnd> ends) {
|
auto junction=[&](std::initializer_list<JunctionEnd> ends) {
|
||||||
auto fitting=std::make_unique<Junction>(v8_exhaust.junction_volume,101325,700);
|
auto fitting=std::make_unique<Junction>(junctions.junction_volume,101325,700);
|
||||||
fitting->connect(boundaries_,pipes,std::span(ends.begin(),ends.size()));
|
fitting->connect(boundaries_,pipes,std::span(ends.begin(),ends.size()));
|
||||||
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
|
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
|
||||||
};
|
};
|
||||||
@@ -179,7 +100,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
|||||||
junction({{14,false},{16,false},{20,true}});
|
junction({{14,false},{16,false},{20,true}});
|
||||||
junction({{17,false},{18,false},{21,true}});
|
junction({{17,false},{18,false},{21,true}});
|
||||||
junction({{19,false},{20,false},{22,true}});
|
junction({{19,false},{20,false},{22,true}});
|
||||||
if(v8_exhaust.h_pipe) {
|
if(junctions.h_pipe) {
|
||||||
junction({{21,false},{23,true},{25,true}});
|
junction({{21,false},{23,true},{25,true}});
|
||||||
junction({{22,false},{24,true},{25,false}});
|
junction({{22,false},{24,true},{25,false}});
|
||||||
} else {
|
} else {
|
||||||
@@ -189,6 +110,33 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
|||||||
}
|
}
|
||||||
boundaries_.add_radiating_open_end(23,false,101325,pipes[23].area);
|
boundaries_.add_radiating_open_end(23,false,101325,pipes[23].area);
|
||||||
boundaries_.add_radiating_open_end(24,false,101325,pipes[24].area);
|
boundaries_.add_radiating_open_end(24,false,101325,pipes[24].area);
|
||||||
|
} else if(staged) {
|
||||||
|
const int exit=1+2*count;
|
||||||
|
const bool dual=engine.dual_outlets();
|
||||||
|
const int groups=engine.header_groups(), secondary=exit+(dual?2:1);
|
||||||
|
auto junction=[&](const std::vector<JunctionEnd>& ends) {
|
||||||
|
auto fitting=std::make_unique<Junction>(junctions.junction_volume,101325,700);
|
||||||
|
fitting->connect(boundaries_,pipes,ends);
|
||||||
|
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
|
||||||
|
};
|
||||||
|
for(int group=0;group<groups;++group) {
|
||||||
|
std::vector<JunctionEnd> ends;
|
||||||
|
for(int i=0;i<count;++i)
|
||||||
|
if(groups==1 || (i<(count+1)/2?0:1)==group) ends.push_back({1+count+i,false});
|
||||||
|
ends.push_back({secondary+group,true});junction(ends);
|
||||||
|
}
|
||||||
|
if(dual) {
|
||||||
|
// Preserve independent bank pulse trains all the way to their mouths.
|
||||||
|
for(int bank=0;bank<2;++bank) {
|
||||||
|
junction({{secondary+bank,false},{exit+bank,true}});
|
||||||
|
boundaries_.add_radiating_open_end(exit+bank,false,101325,pipes[exit+bank].area);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::vector<JunctionEnd> ends{{exit,true}};
|
||||||
|
for(int group=0;group<groups;++group) ends.push_back({secondary+group,false});
|
||||||
|
junction(ends);
|
||||||
|
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
auto& collector_out = exhaust_collector.create_port();
|
auto& collector_out = exhaust_collector.create_port();
|
||||||
const int exit=1+2*count;
|
const int exit=1+2*count;
|
||||||
@@ -196,11 +144,12 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
|||||||
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
|
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
|
||||||
}
|
}
|
||||||
boundaries_.add_radiating_open_end(0, true, 101325, pipes[0].area);
|
boundaries_.add_radiating_open_end(0, true, 101325, pipes[0].area);
|
||||||
|
audio_.set_source_positions(scenario_sound_sources(snapshot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFrame FourStrokeScenario::process() {
|
AudioFrame FourStrokeScenario::process() {
|
||||||
const std::lock_guard guard(mutex_);
|
const std::lock_guard guard(mutex_);
|
||||||
step_motorcycle(crankshaft, dt_, preset == FourStrokePreset::AmericanV8 ? 300.f : preset == FourStrokePreset::SaabB205 ? 200.f : 30.f);
|
step_motorcycle(crankshaft, dt_, engine_config.maximum_load_torque);
|
||||||
ecu.step(dt_, throttle, crankshaft.omega * 60 / (2*pi));
|
ecu.step(dt_, throttle, crankshaft.omega * 60 / (2*pi));
|
||||||
const auto engines = cylinders();
|
const auto engines = cylinders();
|
||||||
for (int i = 0; i < cylinder_count(); ++i) {
|
for (int i = 0; i < cylinder_count(); ++i) {
|
||||||
@@ -212,21 +161,21 @@ AudioFrame FourStrokeScenario::process() {
|
|||||||
}
|
}
|
||||||
const float opening = ecu.throttle_opening();
|
const float opening = ecu.throttle_opening();
|
||||||
const float angle = opening * pi * .5f;
|
const float angle = opening * pi * .5f;
|
||||||
throttle_area_ = area_for_diameter(engine_config.cylinders ? (engine_config.carbureted?.085f:.065f) : preset == FourStrokePreset::AmericanV8 ? .065f : preset == FourStrokePreset::SaabB205 ? .052f : .026f) * (1-std::cos(angle));
|
throttle_area_ = area_for_diameter(engine_config.throttle_diameter) * (1-std::cos(angle));
|
||||||
if(carb_) {
|
if(carb_) {
|
||||||
const auto inlet=pipes[0].state(int(pipes[0].cells.size())-1);
|
const auto inlet=pipes[0].state(int(pipes[0].cells.size())-1);
|
||||||
carb_->step(dt_,throttle,opening,crankshaft.omega*60/(2*pi),std::max(throttle_port_->mass_flow,0.f),inlet.rho,inlet.pressure,intake_plenum.pressure());
|
carb_->step(dt_,throttle,opening,crankshaft.omega*60/(2*pi),std::max(throttle_port_->mass_flow,0.f),inlet.rho,inlet.pressure,intake_plenum.pressure());
|
||||||
intake_plenum.add_fuel_mass(carb_->fuel_flow()*dt_);
|
intake_plenum.add_fuel_mass(carb_->fuel_flow()*dt_);
|
||||||
}
|
}
|
||||||
solver_.step(dt_);
|
solver_.step(dt_);
|
||||||
intake_flow = boundaries_.open_end_mass_flow(preset == FourStrokePreset::AmericanV8 ? 2 : 1);
|
intake_flow = boundaries_.open_end_mass_flow(engine_config.dual_outlets() ? 2 : 1);
|
||||||
exhaust_flow = boundaries_.open_end_mass_flow(0);
|
exhaust_flow = boundaries_.open_end_mass_flow(0);
|
||||||
const float other_exhaust = preset == FourStrokePreset::AmericanV8 ? boundaries_.open_end_mass_flow(1) : 0.f;
|
const float other_exhaust = engine_config.dual_outlets() ? boundaries_.open_end_mass_flow(1) : 0.f;
|
||||||
// Compact mouth radiation responds to volume velocity. Use ambient-equivalent
|
// Compact mouth radiation responds to volume velocity. Use ambient-equivalent
|
||||||
// mass flow so hot/cold donor-density changes do not enter the derivative.
|
// mass flow so hot/cold donor-density changes do not enter the derivative.
|
||||||
const float intake_source = ambient_density * boundaries_.open_end_volume_flow(preset == FourStrokePreset::AmericanV8 ? 2 : 1);
|
const float intake_source = ambient_density * boundaries_.open_end_volume_flow(engine_config.dual_outlets() ? 2 : 1);
|
||||||
const float exhaust_source = ambient_density * boundaries_.open_end_volume_flow(0);
|
const float exhaust_source = ambient_density * boundaries_.open_end_volume_flow(0);
|
||||||
const float other_source = preset == FourStrokePreset::AmericanV8 ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f;
|
const float other_source = engine_config.dual_outlets() ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f;
|
||||||
const auto rendered = audio_.process(intake_source, exhaust_source, other_source);
|
const auto rendered = audio_.process(intake_source, exhaust_source, other_source);
|
||||||
exhaust_flow += other_exhaust;
|
exhaust_flow += other_exhaust;
|
||||||
++sample_count;
|
++sample_count;
|
||||||
@@ -241,6 +190,9 @@ void FourStrokeScenario::set_pipe_solver(PipeSolver solver) {
|
|||||||
void FourStrokeScenario::set_audio_scene(OutdoorScene scene) {
|
void FourStrokeScenario::set_audio_scene(OutdoorScene scene) {
|
||||||
const std::lock_guard<std::mutex> guard(mutex_); audio_.set_scene(scene);
|
const std::lock_guard<std::mutex> guard(mutex_); audio_.set_scene(scene);
|
||||||
}
|
}
|
||||||
|
void FourStrokeScenario::set_listener(AudioListener listener) {
|
||||||
|
const std::lock_guard guard(mutex_); audio_.set_listener(listener);
|
||||||
|
}
|
||||||
void FourStrokeScenario::set_monitor_filter(bool enabled) {
|
void FourStrokeScenario::set_monitor_filter(bool enabled) {
|
||||||
const std::lock_guard<std::mutex> guard(mutex_); audio_.set_monitor_filter(enabled);
|
const std::lock_guard<std::mutex> guard(mutex_); audio_.set_monitor_filter(enabled);
|
||||||
}
|
}
|
||||||
@@ -283,107 +235,18 @@ void FourStrokeScenario::set_first_gear() {
|
|||||||
vehicle.set_first_gear();
|
vehicle.set_first_gear();
|
||||||
}
|
}
|
||||||
|
|
||||||
ScenarioSnapshot FourStrokeScenario::snapshot() const {
|
|
||||||
const std::lock_guard<std::mutex> guard(mutex_);
|
|
||||||
ScenarioSnapshot result;
|
|
||||||
result.layout=engine_config.cylinders?engine_config.layout:preset==FourStrokePreset::AmericanV8?EngineLayout::V:EngineLayout::Inline;
|
|
||||||
result.bank_angle=engine_config.cylinders?engine_config.bank_angle:56;
|
|
||||||
result.v8_headers=preset==FourStrokePreset::AmericanV8;
|
|
||||||
result.silencers = preset == FourStrokePreset::SaabB205 && exhaust_config.enabled;
|
|
||||||
result.exhaust_config = exhaust_config;
|
|
||||||
result.h_pipe = preset==FourStrokePreset::AmericanV8 && v8_exhaust_config.h_pipe;
|
|
||||||
result.crossover_pressures = preset==FourStrokePreset::AmericanV8 ? std::array<float,2>{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array<float,2>{};
|
|
||||||
result.pipe_solver = pipes.front().solver;
|
|
||||||
result.solver_substeps = solver_.substep_count();
|
|
||||||
result.boundary_iterations = boundaries_.nonlinear_iterations;
|
|
||||||
result.rpm = crankshaft.omega * 60.0f / (2.0f * pi);
|
|
||||||
result.speed_kmh = vehicle.speed * 3.6f;
|
|
||||||
result.kinetic_energy = vehicle.kinetic_energy();
|
|
||||||
result.average_power = crankshaft.average_power;
|
|
||||||
result.average_torque = crankshaft.average_torque;
|
|
||||||
result.cylinder_pressure1 = cylinder1.pressure;
|
|
||||||
result.cylinder_pressure2 = cylinder2.pressure;
|
|
||||||
result.cylinder_temperature1 = cylinder1.temperature();
|
|
||||||
result.cylinder_temperature2 = cylinder2.temperature();
|
|
||||||
result.cylinder_piston_fraction1 = (cylinder1.volume - cylinder1.clearance_volume()) / cylinder1.swept_volume();
|
|
||||||
result.cylinder_piston_fraction2 = (cylinder2.volume - cylinder2.clearance_volume()) / cylinder2.swept_volume();
|
|
||||||
result.valve1_intake_area = intake_valve_areas_[0];
|
|
||||||
result.valve1_exhaust_area = exhaust_valve_areas_[0];
|
|
||||||
result.valve2_intake_area = intake_valve_areas_[1];
|
|
||||||
result.valve2_exhaust_area = exhaust_valve_areas_[1];
|
|
||||||
result.intake_pressure = intake_plenum.pressure();
|
|
||||||
result.exhaust_pressure = preset==FourStrokePreset::AmericanV8 ? pipes[21].state(0).pressure : exhaust_collector.pressure();
|
|
||||||
result.second_bank_pressure = preset==FourStrokePreset::AmericanV8 ? pipes[22].state(0).pressure : 101325;
|
|
||||||
result.throttle = ecu.throttle_opening();
|
|
||||||
result.pedal = throttle;
|
|
||||||
result.target_afr = carb_?carb_->indicated_afr():ecu.target_afr();
|
|
||||||
result.idle_control = ecu.idle_active();
|
|
||||||
result.fuel_cut = ecu.fuel_cut();
|
|
||||||
result.audio_scene = audio_.scene();
|
|
||||||
result.clutch = clutch;
|
|
||||||
result.intake_flow = intake_flow;
|
|
||||||
result.exhaust_flow = exhaust_flow;
|
|
||||||
result.gear = vehicle.current_gear;
|
|
||||||
result.rev_limit1 = cylinder1.rev_limit_active;
|
|
||||||
result.rev_limit2 = cylinder2.rev_limit_active;
|
|
||||||
result.cylinder_count = cylinder_count();
|
|
||||||
result.rev_limit_rpm = ecu.rev_limit_rpm;
|
|
||||||
const std::array<const FourStrokeCylinder*, 12> engines{&cylinder1, &cylinder2, &cylinder3, &cylinder4,
|
|
||||||
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12};
|
|
||||||
for (int i = 0; i < cylinder_count(); ++i) {
|
|
||||||
result.cylinder_angles[i] = crankshaft.angle + engines[i]->phase;
|
|
||||||
result.cylinder_pressures[i] = engines[i]->pressure;
|
|
||||||
result.piston_fractions[i] = (engines[i]->volume-engines[i]->clearance_volume())/engines[i]->swept_volume();
|
|
||||||
result.intake_areas[i] = intake_valve_areas_[i];
|
|
||||||
result.exhaust_areas[i] = exhaust_valve_areas_[i];
|
|
||||||
}
|
|
||||||
result.capture_pipes(pipes);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
float FourStrokeScenario::area_for_diameter(float diameter) {
|
float FourStrokeScenario::area_for_diameter(float diameter) {
|
||||||
return pi * diameter * diameter * 0.25f;
|
return pi * diameter * diameter * 0.25f;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Pipe1D> FourStrokeScenario::create_pipes(FourStrokePreset preset, const ExhaustConfig& exhaust, const V8ExhaustConfig& v8_exhaust, const EngineConfig& engine) {
|
std::vector<Pipe1D> FourStrokeScenario::create_pipes(const EngineConfig& engine,const ExhaustConfig& exhaust) {
|
||||||
if(engine.cylinders) {
|
validate_engine_config(engine);
|
||||||
if(engine.cylinders<1||engine.cylinders>12||engine.bore<=0||engine.stroke<=0||engine.rod<=engine.stroke*.5f)throw std::invalid_argument("invalid custom engine dimensions");
|
std::vector<Pipe1D> pipes;
|
||||||
std::vector<Pipe1D> result;
|
for(const auto& pipe:engine.pipes)for(int i=0;i<pipe.count;++i) {
|
||||||
result.emplace_back(area_for_diameter(.075f),.20f,4);
|
if(pipe.silenced)pipes.push_back(make_silenced_exhaust(exhaust));
|
||||||
for(int i=0;i<engine.cylinders;++i)result.emplace_back(area_for_diameter(.035f),.25f,5);
|
else pipes.emplace_back(area_for_diameter(pipe.diameter),pipe.length,pipe.cells);
|
||||||
for(int i=0;i<engine.cylinders;++i)result.emplace_back(area_for_diameter(.038f),.65f,13);
|
|
||||||
result.emplace_back(area_for_diameter(.060f),1.5f,30);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
const bool saab = preset == FourStrokePreset::SaabB205;
|
return pipes;
|
||||||
if (preset == FourStrokePreset::AmericanV8) {
|
|
||||||
// One inlet/throttle -> shared plenum -> short individual runners.
|
|
||||||
// Bank collectors feed twin pipes joined by a finite-length H crossover.
|
|
||||||
// Cells stay long enough to avoid routine
|
|
||||||
// extra CFL substeps during high-vacuum coast-down.
|
|
||||||
std::vector<Pipe1D> result;
|
|
||||||
result.emplace_back(area_for_diameter(.070f), .18f, 3);
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
result.emplace_back(area_for_diameter(.038f), .12f, 2);
|
|
||||||
for (int i = 0; i < 8; ++i)
|
|
||||||
result.emplace_back(area_for_diameter(v8_exhaust.primary_diameter),v8_exhaust.primary_length,int(std::ceil(v8_exhaust.primary_length/.05f)));
|
|
||||||
for(int i=0;i<4;++i) result.emplace_back(area_for_diameter(.045f),.40f,8);
|
|
||||||
for(int i=0;i<2;++i) result.emplace_back(area_for_diameter(.050f),.55f,11);
|
|
||||||
for(int i=0;i<2;++i) result.emplace_back(area_for_diameter(.050f),1.25f,25);
|
|
||||||
result.emplace_back(area_for_diameter(.038f),.78f,16);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const int count = saab ? 4 : 2;
|
|
||||||
std::vector<Pipe1D> result;
|
|
||||||
result.reserve(4+2*count);
|
|
||||||
result.emplace_back(area_for_diameter(saab ? .060f : .0275f), saab ? .15f : .06f, saab ? 8 : 10);
|
|
||||||
for (int i = 0; i < count; ++i)
|
|
||||||
result.emplace_back(area_for_diameter(saab ? .032f : .0275f), saab ? .25f : .12f, 10);
|
|
||||||
for (int i = 0; i < count; ++i)
|
|
||||||
result.emplace_back(area_for_diameter(saab ? .034f : .028f), saab ? .28f : .20f, saab ? 10 : 6);
|
|
||||||
result.emplace_back(area_for_diameter(saab ? .060f : .028f), saab ? 1.4f : .40f, saab ? 40 : 10);
|
|
||||||
if (saab) result.back() = make_silenced_exhaust(exhaust);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fluidsim::port
|
} // namespace fluidsim::port
|
||||||
|
|||||||
84
src/scenario_geometry.cpp
Normal file
84
src/scenario_geometry.cpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#include "FluidSim/ScenarioGeometry.hpp"
|
||||||
|
#include "FluidSim/ScenarioSnapshot.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
|
namespace fluidsim::port {
|
||||||
|
namespace {
|
||||||
|
struct Anchor { float fraction; Vec3 point; };
|
||||||
|
PipeRoute route(std::initializer_list<Anchor> anchors) {
|
||||||
|
PipeRoute result;
|
||||||
|
for (auto a : anchors) {
|
||||||
|
result.fractions[result.count] = a.fraction;
|
||||||
|
result.points[result.count++] = a.point;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::array<PipeRoute, 32> scenario_pipe_routes(const ScenarioSnapshot& s) {
|
||||||
|
std::array<PipeRoute, 32> routes{};
|
||||||
|
if (s.two_stroke) {
|
||||||
|
routes[0]=route({{0,{-1.05f,.35f,0}},{1,{-.64f,.35f,0}}});
|
||||||
|
routes[1]=route({{0,{-.46f,.35f,0}},{1.f/3,{-.28f,.35f,0}},{2.f/3,{-.28f,.1f,0}},{1,{-.105f,.1f,0}}});
|
||||||
|
routes[2]=route({{0,{.105f,.09f,0}},{1.f/3,{.18f,.09f,-.12f}},{2.f/3,{.18f,.35f,-.12f}},{1,{.05f,.35f,0}}});
|
||||||
|
routes[3]=route({{0,{.055f,.40f,0}},{1.f/3,{.3f,.40f,0}},{2.f/3,{.58f,.23f,0}},{1,{1.45f,.23f,0}}});
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
const int n=s.cylinder_count;
|
||||||
|
const bool banked=s.layout!=EngineLayout::Inline;
|
||||||
|
const int per_bank=banked?n/2:n;
|
||||||
|
const float first=-(per_bank-1)*.21f*.5f;
|
||||||
|
routes[0]=route({{0,{-1.05f,.64f,0}},{.25f,{-.5f,.64f,0}},{.5f,{-.5f,.64f,-.1f}},{.75f,{0,.64f,-.1f}},{1,{0,.64f,0}}});
|
||||||
|
for(int i=0;i<n;++i) {
|
||||||
|
const float x=first+(i%per_bank)*.21f;
|
||||||
|
const float tilt=banked?(i<per_bank?-1.f:1.f)*(s.layout==EngineLayout::Boxer?90.f:s.bank_angle*.5f):0;
|
||||||
|
const auto port=[&](float y,float dx) {const float a=tilt*.017453293f;return Vec3{x+dx,.1f+(y-.1f)*std::cos(a),(y-.1f)*std::sin(a)};};
|
||||||
|
const auto intake=port(.45f,-.055f),exhaust=port(.43f,.055f);
|
||||||
|
routes[1+i]=route({{0,{x,.58f,0}},{.3f,{x,.55f,intake.z*.5f}},{.7f,{intake.x,.52f,intake.z}},{1,intake}});
|
||||||
|
const int group=s.staged_headers?(n==3?0:(i<(n+1)/2?0:1)):0;
|
||||||
|
const float z=s.staged_headers?(group==0?-.26f:.26f):.26f;
|
||||||
|
if(!s.v8_headers)routes[1+n+i]=route({{0,exhaust},{.3f,{x+.12f,exhaust.y,z}},{.6f,{x+.12f,.17f,z}},{1,{.68f,.17f,z}}});
|
||||||
|
}
|
||||||
|
if(s.v8_headers) {
|
||||||
|
const std::array<int,8> pair{0,0,1,1,2,3,2,3};
|
||||||
|
for(int i=0;i<8;++i) {
|
||||||
|
const float x=first+(i%4)*.21f,z=i/4?.39f:-.39f,a=(i/4?28.f:-28.f)*.017453293f;
|
||||||
|
const float end_z=z+(pair[i]%2?.09f:-.09f);
|
||||||
|
routes[9+i]=route({{0,{x+.055f,.1f+.33f*std::cos(a),.33f*std::sin(a)}},{.3f,{x+.12f,.34f,z}},{.65f,{.35f,.20f,end_z}},{1,{.74f,.17f,end_z}}});
|
||||||
|
}
|
||||||
|
for(int i=0;i<4;++i) {
|
||||||
|
const float z=i<2?-.39f:.39f;
|
||||||
|
routes[17+i]=route({{0,{.74f,.17f,z+(i%2?.09f:-.09f)}},{.5f,{.94f,.17f,z+(i%2?.06f:-.06f)}},{1,{1.14f,.17f,z}}});
|
||||||
|
}
|
||||||
|
if(s.h_pipe)routes[25]=route({{0,{1.69f,.17f,-.39f}},{1,{1.69f,.17f,.39f}}});
|
||||||
|
}
|
||||||
|
if(s.staged_headers) {
|
||||||
|
const int exit=1+2*n,groups=n==3?1:2,secondary=exit+(s.dual_exhaust?2:1);
|
||||||
|
for(int group=0;group<groups;++group) {
|
||||||
|
const float z=group==0?-.26f:.26f;
|
||||||
|
routes[secondary+group]=route({{0,{.68f,.17f,z}},{1,{1.20f,.17f,s.dual_exhaust?z:0.f}}});
|
||||||
|
}
|
||||||
|
for(int bank=0;bank<(s.dual_exhaust?2:1);++bank) {
|
||||||
|
const float z=s.dual_exhaust?(bank==0?-.26f:.26f):0;
|
||||||
|
routes[exit+bank]=route({{0,{1.20f,.17f,z}},{1,{2.7f,.17f,z}}});
|
||||||
|
}
|
||||||
|
} else for(int bank=0;bank<(s.v8_headers?2:1);++bank) {
|
||||||
|
const float z=s.v8_headers?(bank==0?-.39f:.39f):.26f;
|
||||||
|
if(s.silencers) {
|
||||||
|
const auto& c=s.exhaust_config;
|
||||||
|
const float total=c.inlet_length+c.resonator.length+c.connecting_length+c.muffler.length+c.outlet_length;
|
||||||
|
routes[1+2*n]=route({{0,{.74f,.17f,z}},{1,{.74f+total,.17f,z}}});
|
||||||
|
} else if(s.v8_headers) {
|
||||||
|
routes[21+bank]=route({{0,{1.14f,.17f,z}},{1,{1.69f,.17f,z}}});
|
||||||
|
routes[23+bank]=route({{0,{1.69f,.17f,z}},{1,{2.94f,.17f,z}}});
|
||||||
|
} else routes[1+2*n]=route({{0,{.74f,.17f,z}},{1,{1.45f,.17f,z}}});
|
||||||
|
}
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
std::array<Vec3,3> scenario_sound_sources(const ScenarioSnapshot& s) {
|
||||||
|
const auto routes=scenario_pipe_routes(s);
|
||||||
|
const int exhaust=s.two_stroke?3:s.v8_headers?23:1+2*s.cylinder_count;
|
||||||
|
const auto end=[&](int index) {const auto& r=routes[index];return r.points[r.count-1];};
|
||||||
|
return {routes[0].points[0],end(exhaust),s.dual_exhaust?end(exhaust+1):end(exhaust)};
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/scenario_snapshot.cpp
Normal file
69
src/scenario_snapshot.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace fluidsim::port {
|
||||||
|
ScenarioSnapshot FourStrokeScenario::snapshot() const {
|
||||||
|
const std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
ScenarioSnapshot result;
|
||||||
|
result.layout=engine_config.layout;
|
||||||
|
result.bank_angle=engine_config.bank_angle;
|
||||||
|
result.v8_headers=engine_config.american_headers();
|
||||||
|
result.staged_headers=engine_config.staged_headers();
|
||||||
|
result.dual_exhaust=engine_config.dual_outlets();
|
||||||
|
result.silencers = engine_config.has_silenced_exhaust() && exhaust_config.enabled;
|
||||||
|
result.exhaust_config = exhaust_config;
|
||||||
|
result.h_pipe = engine_config.american_headers() && junction_config.h_pipe;
|
||||||
|
result.crossover_pressures = engine_config.american_headers() ? std::array<float,2>{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array<float,2>{};
|
||||||
|
result.pipe_solver = pipes.front().solver;
|
||||||
|
result.solver_substeps = solver_.substep_count();
|
||||||
|
result.boundary_iterations = boundaries_.nonlinear_iterations;
|
||||||
|
result.rpm = crankshaft.omega * 60.0f / (2.0f * pi);
|
||||||
|
result.speed_kmh = vehicle.speed * 3.6f;
|
||||||
|
result.kinetic_energy = vehicle.kinetic_energy();
|
||||||
|
result.average_power = crankshaft.average_power;
|
||||||
|
result.average_torque = crankshaft.average_torque;
|
||||||
|
result.cylinder_pressure1 = cylinder1.pressure;
|
||||||
|
result.cylinder_pressure2 = cylinder2.pressure;
|
||||||
|
result.cylinder_temperature1 = cylinder1.temperature();
|
||||||
|
result.cylinder_temperature2 = cylinder2.temperature();
|
||||||
|
result.cylinder_piston_fraction1 = (cylinder1.volume - cylinder1.clearance_volume()) / cylinder1.swept_volume();
|
||||||
|
result.cylinder_piston_fraction2 = (cylinder2.volume - cylinder2.clearance_volume()) / cylinder2.swept_volume();
|
||||||
|
result.valve1_intake_area = intake_valve_areas_[0];
|
||||||
|
result.valve1_exhaust_area = exhaust_valve_areas_[0];
|
||||||
|
result.valve2_intake_area = intake_valve_areas_[1];
|
||||||
|
result.valve2_exhaust_area = exhaust_valve_areas_[1];
|
||||||
|
result.intake_pressure = intake_plenum.pressure();
|
||||||
|
const int exit=1+2*cylinder_count();
|
||||||
|
result.exhaust_pressure = result.v8_headers ? pipes[21].state(0).pressure :
|
||||||
|
result.staged_headers ? pipes[exit].state(0).pressure : exhaust_collector.pressure();
|
||||||
|
result.second_bank_pressure = result.v8_headers ? pipes[22].state(0).pressure :
|
||||||
|
result.dual_exhaust ? pipes[exit+1].state(0).pressure : 101325;
|
||||||
|
result.throttle = ecu.throttle_opening();
|
||||||
|
result.pedal = throttle;
|
||||||
|
result.target_afr = carb_?carb_->indicated_afr():ecu.target_afr();
|
||||||
|
result.idle_control = ecu.idle_active();
|
||||||
|
result.fuel_cut = ecu.fuel_cut();
|
||||||
|
result.ignition_cut = ecu.ignition_cut();
|
||||||
|
result.audio_scene = audio_.scene();
|
||||||
|
result.clutch = clutch;
|
||||||
|
result.intake_flow = intake_flow;
|
||||||
|
result.exhaust_flow = exhaust_flow;
|
||||||
|
result.gear = vehicle.current_gear;
|
||||||
|
result.rev_limit1 = cylinder1.rev_limit_active;
|
||||||
|
result.rev_limit2 = cylinder2.rev_limit_active;
|
||||||
|
result.cylinder_count = cylinder_count();
|
||||||
|
result.rev_limit_rpm = ecu.rev_limit_rpm;
|
||||||
|
const std::array<const FourStrokeCylinder*, 12> engines{&cylinder1, &cylinder2, &cylinder3, &cylinder4,
|
||||||
|
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12};
|
||||||
|
for (int i = 0; i < cylinder_count(); ++i) {
|
||||||
|
result.cylinder_angles[i] = crankshaft.angle + engines[i]->phase;
|
||||||
|
result.cylinder_pressures[i] = engines[i]->pressure;
|
||||||
|
result.piston_fractions[i] = (engines[i]->volume-engines[i]->clearance_volume())/engines[i]->swept_volume();
|
||||||
|
result.intake_areas[i] = intake_valve_areas_[i];
|
||||||
|
result.exhaust_areas[i] = exhaust_valve_areas_[i];
|
||||||
|
}
|
||||||
|
result.capture_pipes(pipes);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ void TwoStrokeCylinder::handle_cycle_events(float previous_degrees, float curren
|
|||||||
const float spark_angle = wrap_degrees(360.0f - spark_advance, 360.0f);
|
const float spark_angle = wrap_degrees(360.0f - spark_advance, 360.0f);
|
||||||
const bool crossed_spark = crossed_angle(previous_degrees,current_degrees,spark_angle);
|
const bool crossed_spark = crossed_angle(previous_degrees,current_degrees,spark_angle);
|
||||||
if (crossed_spark && fuel_injected && !combustion_active &&
|
if (crossed_spark && fuel_injected && !combustion_active &&
|
||||||
!(ecu ? ecu->fuel_cut() : rev_limit_active) &&
|
!(ecu ? ecu->ignition_cut() : rev_limit_active) &&
|
||||||
random_unit() >= misfire_probability) {
|
random_unit() >= misfire_probability) {
|
||||||
combustion_active = true;
|
combustion_active = true;
|
||||||
burn_fraction = 0.0f;
|
burn_fraction = 0.0f;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "FluidSim/TwoStrokeScenario.hpp"
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
||||||
#include "FluidSim/Constants.hpp"
|
#include "FluidSim/Constants.hpp"
|
||||||
|
#include "FluidSim/ScenarioGeometry.hpp"
|
||||||
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -18,15 +20,20 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config)
|
|||||||
boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate) {
|
boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate) {
|
||||||
if (!std::isfinite(config.idle_slide_opening) || config.idle_slide_opening<0 || config.idle_slide_opening>1)
|
if (!std::isfinite(config.idle_slide_opening) || config.idle_slide_opening<0 || config.idle_slide_opening>1)
|
||||||
throw std::invalid_argument("carb idle slide opening must be in [0,1]");
|
throw std::invalid_argument("carb idle slide opening must be in [0,1]");
|
||||||
|
static_cast<EcuConfig&>(ecu)=config.ecu;
|
||||||
|
static_cast<VehicleConfig&>(vehicle)=config.vehicle;
|
||||||
ecu.idle_control = Ecu::IdleControl::FixedOpening;
|
ecu.idle_control = Ecu::IdleControl::FixedOpening;
|
||||||
carburetor.pilot_jet_diameter*=config.jet_scale;
|
carburetor.pilot_jet_diameter*=config.jet_scale*config.pilot_jet_scale;
|
||||||
carburetor.main_jet_diameter*=config.jet_scale;
|
carburetor.main_jet_diameter*=config.jet_scale*config.main_jet_scale;
|
||||||
carburetor.needle_jet_diameter*=config.jet_scale;
|
carburetor.needle_jet_diameter*=config.jet_scale*config.needle_jet_scale;
|
||||||
ecu.throttle_time_constant = .0002f;
|
carburetor.needle_low_scale=config.needle_low_scale;carburetor.needle_mid_scale=config.needle_mid_scale;
|
||||||
ecu.throttle_rate = 5000;
|
carburetor.needle_transition_scale=config.needle_transition_scale;
|
||||||
crankshaft.omega = 3000.0f * 2.0f * pi / 60.0f;
|
carburetor.needle_off_idle_scale=config.needle_off_idle_scale;carburetor.needle_tip_scale=config.needle_tip_scale;
|
||||||
crankshaft.inertia = 0.01f*(config.bore*config.bore*config.stroke)/(.054f*.054f*.0545f);
|
carburetor.pilot_transition_scale=config.pilot_transition_scale;carburetor.pilot_tip_scale=config.pilot_tip_scale;
|
||||||
starter_torque = 8.f;
|
carburetor.needle_quarter_scale=config.needle_quarter_scale;carburetor.needle_upper_scale=config.needle_upper_scale;
|
||||||
|
crankshaft.omega=config.initial_rpm*2*pi/60;
|
||||||
|
crankshaft.inertia=config.crankshaft_inertia;
|
||||||
|
starter_torque=config.starter_torque;
|
||||||
const float pressure_to_torque = cylinder.swept_volume() * 1e5f / (2 * pi);
|
const float pressure_to_torque = cylinder.swept_volume() * 1e5f / (2 * pi);
|
||||||
const float piston_speed_per_omega = config.stroke / pi;
|
const float piston_speed_per_omega = config.stroke / pi;
|
||||||
crankshaft.friction_constant = pressure_to_torque * config.friction_bar;
|
crankshaft.friction_constant = pressure_to_torque * config.friction_bar;
|
||||||
@@ -37,24 +44,14 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config)
|
|||||||
cylinder.transfer_width = config.transfer_width;
|
cylinder.transfer_width = config.transfer_width;
|
||||||
cylinder.exhaust_width = config.exhaust_width;
|
cylinder.exhaust_width = config.exhaust_width;
|
||||||
cylinder.minimum_burn_seconds = config.minimum_burn_seconds;
|
cylinder.minimum_burn_seconds = config.minimum_burn_seconds;
|
||||||
// Yamaha's 2022 YZ125 published ratios; mass includes a nominal 75 kg rider.
|
cylinder.rev_limit_enabled=ecu.rev_limit_enabled;
|
||||||
vehicle.primary_reduction = 64.f / 19;
|
|
||||||
vehicle.final_drive_ratio = 49.f / 13;
|
|
||||||
vehicle.gear_ratios = {31.f/13, 29.f/15, 27.f/17, 23.f/17, 20.f/17, 19.f/18};
|
|
||||||
vehicle.mass = 95 + 75;
|
|
||||||
vehicle.wheel_radius = .3313f; // nominal 100/90-19 geometry; loaded radius unmeasured
|
|
||||||
// Estimated two-stroke ignition calibration, not a factory CDI map.
|
|
||||||
ecu.ignition_map = {{0, 10}, {3000, 18}, {6000, 24}, {9000, 18},
|
|
||||||
{11000, 12}, {13000, 6}, {15000, 2}, {18000, 0}};
|
|
||||||
ecu.idle_rpm = 3000;
|
|
||||||
ecu.rev_limit_enabled = false;
|
|
||||||
cylinder.rev_limit_enabled = false;
|
|
||||||
ecu.idle_base_opening = config.idle_slide_opening;
|
ecu.idle_base_opening = config.idle_slide_opening;
|
||||||
ecu.idle_max_opening = .3f;
|
ecu.idle_max_opening = .3f;
|
||||||
ecu.idle_spark_gain=0;
|
ecu.idle_spark_gain=config.idle_spark_gain;
|
||||||
ecu.overrun_cut_rpm = 5000;
|
ecu.idle_minimum_advance=config.idle_minimum_advance;
|
||||||
ecu.overrun_resume_rpm = 3500;
|
ecu.rev_limit_fuel_cut_enabled = false;
|
||||||
ecu.overrun_fuel_cut_enabled = false;
|
ecu.overrun_fuel_cut_enabled = false;
|
||||||
|
ecu.overrun_ignition_cut_enabled = true;
|
||||||
cylinder.ecu = &ecu;
|
cylinder.ecu = &ecu;
|
||||||
cylinder.initialize(crankshaft);
|
cylinder.initialize(crankshaft);
|
||||||
cylinder.set_crankcase(crankcase);
|
cylinder.set_crankcase(crankcase);
|
||||||
@@ -92,6 +89,9 @@ TwoStrokeScenario::TwoStrokeScenario(int sample_rate, TwoStrokeConfig config)
|
|||||||
solver_.add_component(intake_plenum);
|
solver_.add_component(intake_plenum);
|
||||||
solver_.add_component(crankcase);
|
solver_.add_component(crankcase);
|
||||||
solver_.add_component(cylinder);
|
solver_.add_component(cylinder);
|
||||||
|
ScenarioSnapshot geometry;
|
||||||
|
geometry.two_stroke=true;
|
||||||
|
audio_.set_source_positions(scenario_sound_sources(geometry));
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFrame TwoStrokeScenario::process() {
|
AudioFrame TwoStrokeScenario::process() {
|
||||||
@@ -105,7 +105,7 @@ AudioFrame TwoStrokeScenario::process() {
|
|||||||
carb_inlet.pressure, intake_plenum.pressure());
|
carb_inlet.pressure, intake_plenum.pressure());
|
||||||
intake_plenum.add_fuel_mass(carburetor.fuel_flow() * dt_);
|
intake_plenum.add_fuel_mass(carburetor.fuel_flow() * dt_);
|
||||||
max_throttle_area_ = slide_area(configuration.carburetor_diameter, ecu.throttle_opening());
|
max_throttle_area_ = slide_area(configuration.carburetor_diameter, ecu.throttle_opening());
|
||||||
step_motorcycle(crankshaft, dt_, 10.0f);
|
step_motorcycle(crankshaft, dt_, configuration.maximum_load_torque);
|
||||||
crankcase.pre_step(dt_);
|
crankcase.pre_step(dt_);
|
||||||
cylinder.pre_step(crankshaft, dt_, ecu.throttle_opening());
|
cylinder.pre_step(crankshaft, dt_, ecu.throttle_opening());
|
||||||
transfer_valve_area_ = cylinder.valve_area(crankshaft.angle * 180.0f / pi, true);
|
transfer_valve_area_ = cylinder.valve_area(crankshaft.angle * 180.0f / pi, true);
|
||||||
@@ -137,6 +137,7 @@ TwoStrokeSnapshot TwoStrokeScenario::snapshot() const {
|
|||||||
cylinder.valve_area(crankshaft.angle * 180.0f / pi, false),
|
cylinder.valve_area(crankshaft.angle * 180.0f / pi, false),
|
||||||
reed_open_,
|
reed_open_,
|
||||||
ecu.fuel_cut(),
|
ecu.fuel_cut(),
|
||||||
|
ecu.ignition_cut(),
|
||||||
throttle,
|
throttle,
|
||||||
audio_.scene()};
|
audio_.scene()};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,5 +33,18 @@ int main() {
|
|||||||
camera.orbit({40,20});camera.pan({20,15},500);camera.zoom(2);
|
camera.orbit({40,20});camera.pan({20,15},500);camera.zoom(2);
|
||||||
check(camera.pitch<=80 && camera.pitch>=-75 && camera.distance>.35f,"camera bounds");
|
check(camera.pitch<=80 && camera.pitch>=-75 && camera.distance>.35f,"camera bounds");
|
||||||
check(camera.target.x!=before.x || camera.target.y!=before.y,"camera panning must move target");
|
check(camera.target.x!=before.x || camera.target.y!=before.y,"camera panning must move target");
|
||||||
|
for(float yaw:{-90.f,-28.f,0.f,90.f,180.f})for(float pitch:{-60.f,0.f,70.f}) {
|
||||||
|
camera.yaw=yaw;camera.pitch=pitch;camera.target={.7f,.4f,-.2f};camera.distance=3;
|
||||||
|
const auto listener=camera.listener(1.8f,1.1f);
|
||||||
|
const float y=yaw*.017453293f,p=pitch*.017453293f;
|
||||||
|
const float dx=listener.position.x-camera.target.x-1.1f;
|
||||||
|
const float dy=listener.position.y-camera.target.y,dz=listener.position.z-camera.target.z;
|
||||||
|
const float x1=std::cos(y)*dx+std::sin(y)*dz,z1=-std::sin(y)*dx+std::cos(y)*dz;
|
||||||
|
check(std::abs(x1)<2e-6f && std::abs(std::cos(p)*dy-std::sin(p)*z1)<2e-6f &&
|
||||||
|
std::abs(std::sin(p)*dy+std::cos(p)*z1-5.4f)<2e-6f,
|
||||||
|
"audio listener must coincide with the rendered camera including fit offsets");
|
||||||
|
check(std::abs(std::cos(y)*listener.right.x+std::sin(y)*listener.right.z-1)<1e-6f,
|
||||||
|
"audio ears must rotate with the camera right axis");
|
||||||
|
}
|
||||||
std::cout<<"Desktop layout, spring pedal, keyboard travel and orbit camera checks passed\n";
|
std::cout<<"Desktop layout, spring pedal, keyboard travel and orbit camera checks passed\n";
|
||||||
}
|
}
|
||||||
|
|||||||
78
tests/engine_config_tests.cpp
Normal file
78
tests/engine_config_tests.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
|
#include "FluidSim/DesktopScenario.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
using namespace fluidsim::port;
|
||||||
|
namespace {
|
||||||
|
void check(bool condition,const std::string& message) {if(!condition)throw std::runtime_error(message);}
|
||||||
|
std::string read(const std::filesystem::path& path) {
|
||||||
|
std::ifstream file(path);std::ostringstream text;text<<file.rdbuf();return text.str();
|
||||||
|
}
|
||||||
|
void replace(std::string& text,const std::string& from,const std::string& to) {
|
||||||
|
const auto pos=text.find(from);check(pos!=std::string::npos,"test fixture missing "+from);text.replace(pos,from.size(),to);
|
||||||
|
}
|
||||||
|
struct TemporaryDirectory {
|
||||||
|
std::filesystem::path path=std::filesystem::temp_directory_path()/
|
||||||
|
("fluidsim-engine-config-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||||
|
TemporaryDirectory() {std::filesystem::create_directory(path);}
|
||||||
|
~TemporaryDirectory() {std::error_code error;std::filesystem::remove_all(path,error);}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
int main() {
|
||||||
|
const auto& catalog=scenario_examples();
|
||||||
|
check(catalog.size()>=32,"bundled engines missing");
|
||||||
|
const auto& thumper=scenario_example("single-450");
|
||||||
|
check(thumper.engine.carbureted && std::abs(thumper.engine.cylinder.bore-.096f)<1e-7f,
|
||||||
|
"four-stroke units or fuel type changed");
|
||||||
|
check(std::abs(scenario_example("yz-racing").two.idle_slide_opening-.03f)<1e-7f,
|
||||||
|
"125 idle tune lost");
|
||||||
|
for(const auto& e:catalog) {
|
||||||
|
DesktopScenario s(e);s.set_controls(.35f,0,1);
|
||||||
|
for(int i=0;i<256;++i) {
|
||||||
|
const auto audio=s.process();
|
||||||
|
check(std::isfinite(audio.left) && std::isfinite(audio.right),e.id+": invalid audio after TOML load");
|
||||||
|
}
|
||||||
|
const auto state=s.snapshot();
|
||||||
|
check(state.rpm>0 && state.pipe_positivity_repairs==0,e.id+": startup changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
TemporaryDirectory temp;
|
||||||
|
const auto file=temp.path/"test.toml";
|
||||||
|
const auto original=read(engine_directory()/"twin.toml");
|
||||||
|
const auto reject=[&](std::string text,const std::string& expected) {
|
||||||
|
{std::ofstream output(file);output<<text;}
|
||||||
|
bool rejected=false;
|
||||||
|
try {load_engine_file(file);}
|
||||||
|
catch(const std::exception& error) {
|
||||||
|
const std::string message=error.what();
|
||||||
|
check(message.find("test.toml")!=std::string::npos,"diagnostic lost the file path");
|
||||||
|
check(message.find(expected)!=std::string::npos,"unexpected diagnostic: "+message);
|
||||||
|
rejected=true;
|
||||||
|
}
|
||||||
|
check(rejected,"invalid config accepted: "+expected);
|
||||||
|
};
|
||||||
|
auto changed=original;replace(changed,"schema_version = 1","schema_version = 99");reject(changed,"schema_version");
|
||||||
|
changed=original;replace(changed,"bore_mm = 75.0","bore_mn = 75.0");reject(changed,"bore_mm");
|
||||||
|
changed=original;replace(changed,"bore_mm = 75.0","bore_mm = -75.0");reject(changed,"cylinder");
|
||||||
|
changed=original;replace(changed,"cylinders = 2","cylinders = 2.5");reject(changed,"integer");
|
||||||
|
changed=original;replace(changed,"layout = \"inline\"","layout = \"flatbread\"");reject(changed,"layout");
|
||||||
|
changed=original;replace(changed,"topology = \"collector\"","topology = \"american_v8\"");reject(changed,"V8");
|
||||||
|
changed=original;replace(changed,"cells = 10","cells = 1000000");reject(changed,"cell");
|
||||||
|
changed=original;replace(changed,"phase_degrees = [0.0, 180.0]","phase_degrees = [0.0]");reject(changed,"phase_degrees");
|
||||||
|
reject(original+"\n[unknown_section]\nvalue = 1\n","unknown field");
|
||||||
|
reject("schema_version = [","Error");
|
||||||
|
|
||||||
|
{std::ofstream output(file);output<<original;}
|
||||||
|
std::filesystem::copy_file(file,temp.path/"duplicate.toml");
|
||||||
|
bool duplicate=false;
|
||||||
|
try {load_engine_directory(temp.path);}catch(const std::exception& error) {
|
||||||
|
duplicate=std::string(error.what()).find("duplicate engine id")!=std::string::npos;
|
||||||
|
}
|
||||||
|
check(duplicate,"duplicate engine IDs accepted");
|
||||||
|
std::cout<<catalog.size()<<" engine imports, startup smoke checks and invalid-config diagnostics passed\n";
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
#include "FluidSim/OutdoorAudio.hpp"
|
#include "FluidSim/OutdoorAudio.hpp"
|
||||||
#include "FluidSim/ReedValve.hpp"
|
#include "FluidSim/ReedValve.hpp"
|
||||||
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
#include "FluidSim/ScenarioGeometry.hpp"
|
||||||
|
#include "FluidSim/DesktopScenario.hpp"
|
||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -98,6 +102,85 @@ int main() {
|
|||||||
}
|
}
|
||||||
check(right < left, "source on right does not reach right ear first");
|
check(right < left, "source on right does not reach right ear first");
|
||||||
|
|
||||||
|
// A moved listener must converge to the same propagation as one created
|
||||||
|
// there, including the reversed ear orientation and the reflected field.
|
||||||
|
auto moved_config=stereo_config;
|
||||||
|
moved_config.listener={-.5f,1.5f,4};
|
||||||
|
moved_config.listener_right={-1,0,0};
|
||||||
|
OutdoorAudio moving(44100,stereo_config),destination(44100,moved_config);
|
||||||
|
moving.set_listener({moved_config.listener,moved_config.listener_right});
|
||||||
|
for(auto* p:{&moving,&destination})p->set_monitor_filter(false);
|
||||||
|
for(int i=0;i<44100;++i) {moving.process(0,0);destination.process(0,0);}
|
||||||
|
for(int i=0;i<6000;++i) {
|
||||||
|
const auto a=moving.process(0,.001f),b=destination.process(0,.001f);
|
||||||
|
check(std::abs(a.left-b.left)<2e-7f && std::abs(a.right-b.right)<2e-7f,
|
||||||
|
"moving listener does not update direct/reflected stereo paths");
|
||||||
|
}
|
||||||
|
// Exercise rapid pose changes while a wet tail is still ringing.
|
||||||
|
for(int i=0;i<22050;++i) {
|
||||||
|
if(i%735==0) {
|
||||||
|
const float angle=i*.0002f;
|
||||||
|
moving.set_listener({{3*std::sin(angle),1,3*std::cos(angle)},
|
||||||
|
{std::cos(angle),0,-std::sin(angle)}});
|
||||||
|
}
|
||||||
|
const auto a=moving.process(0,.001f);
|
||||||
|
check(std::isfinite(a.left) && std::isfinite(a.right) &&
|
||||||
|
std::abs(a.left)<1 && std::abs(a.right)<1,"moving wet paths became unstable");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source relocation must use geometry without resetting audio state.
|
||||||
|
auto source_config=config();source_config.exhaust_position={2,1,-1};
|
||||||
|
OutdoorAudio relocated(44100,config()),fixed_source(44100,source_config);
|
||||||
|
relocated.set_source_positions({source_config.intake_position,source_config.exhaust_position,
|
||||||
|
source_config.second_exhaust_position});
|
||||||
|
for(int i=0;i<44100;++i) {relocated.process(0,0);fixed_source.process(0,0);}
|
||||||
|
for(int i=0;i<3000;++i) {
|
||||||
|
const auto a=relocated.process(0,.001f),b=fixed_source.process(0,.001f);
|
||||||
|
check(std::abs(a.left-b.left)<2e-7f && std::abs(a.right-b.right)<2e-7f,
|
||||||
|
"relocated mouth does not match world source position");
|
||||||
|
}
|
||||||
|
ScenarioSnapshot geometry;
|
||||||
|
geometry.two_stroke=true;
|
||||||
|
auto mouths=scenario_sound_sources(geometry);
|
||||||
|
check(mouths[0].x==-1.05f && mouths[1].x==1.45f && mouths[1].y==.23f,
|
||||||
|
"two-stroke mouths are not on the drawn pipe ends");
|
||||||
|
geometry.two_stroke=false;geometry.cylinder_count=8;geometry.layout=EngineLayout::V;
|
||||||
|
geometry.v8_headers=true;geometry.dual_exhaust=true;
|
||||||
|
mouths=scenario_sound_sources(geometry);
|
||||||
|
check(mouths[1].x==2.94f && mouths[1].z==-.39f && mouths[2].z==.39f,
|
||||||
|
"dual V8 outlets are not at their separate tailpipe mouths");
|
||||||
|
geometry.v8_headers=false;geometry.dual_exhaust=false;geometry.silencers=true;
|
||||||
|
geometry.cylinder_count=4;geometry.layout=EngineLayout::Inline;
|
||||||
|
const auto& e=geometry.exhaust_config;
|
||||||
|
mouths=scenario_sound_sources(geometry);
|
||||||
|
check(std::abs(mouths[1].x-(.74f+e.inlet_length+e.resonator.length+e.connecting_length+
|
||||||
|
e.muffler.length+e.outlet_length))<1e-6f,
|
||||||
|
"silenced exhaust source does not follow the configured pipe length");
|
||||||
|
// Crossing a reflecting plane must not turn its virtual image into a
|
||||||
|
// source that arrives before the actual pipe mouth.
|
||||||
|
auto behind_config=config();behind_config.listener={-4,1,0};
|
||||||
|
OutdoorAudio behind_wall(44100,behind_config);
|
||||||
|
behind_wall.set_monitor_filter(false);
|
||||||
|
for(int i=0;i<4410;++i)behind_wall.process(0,0);
|
||||||
|
int first_behind=-1;
|
||||||
|
for(int i=0;i<1000;++i) {
|
||||||
|
const auto a=behind_wall.process(0,.001f);
|
||||||
|
if(first_behind<0 && std::abs(a.left)>1e-9f)first_behind=i;
|
||||||
|
}
|
||||||
|
check(first_behind==514,"reflection crosses wall and precedes the physical source");
|
||||||
|
|
||||||
|
for(const auto& example:scenario_examples()) {
|
||||||
|
DesktopScenario scenario(example.kind,example.two,example.exhaust,example.junctions,example.engine);
|
||||||
|
scenario.set_listener({{2,1,3},{1,0,0}});
|
||||||
|
const auto snapshot=scenario.snapshot();
|
||||||
|
const auto sources=scenario_sound_sources(snapshot);
|
||||||
|
for(auto source:sources)
|
||||||
|
check(std::isfinite(source.x) && std::isfinite(source.y) && std::isfinite(source.z),
|
||||||
|
"catalog scenario has an invalid sound source");
|
||||||
|
const auto frame=scenario.process();
|
||||||
|
check(std::isfinite(frame.left) && std::isfinite(frame.right),"catalog listener wiring failed");
|
||||||
|
}
|
||||||
|
|
||||||
ReedValve reed;
|
ReedValve reed;
|
||||||
for (int i = 0; i < 10000; i++)
|
for (int i = 0; i < 10000; i++)
|
||||||
reed.step(1.f / 44100, 100);
|
reed.step(1.f / 44100, 100);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ int main() {
|
|||||||
std::set<std::string> ids;
|
std::set<std::string> ids;
|
||||||
for(const auto& example:scenario_examples()) {
|
for(const auto& example:scenario_examples()) {
|
||||||
if(!ids.insert(example.id).second)throw std::runtime_error("duplicate example id");
|
if(!ids.insert(example.id).second)throw std::runtime_error("duplicate example id");
|
||||||
DesktopScenario scenario(example.kind,example.two,example.exhaust,example.v8,example.engine,example.title);
|
DesktopScenario scenario(example.kind,example.two,example.exhaust,example.junctions,example.engine,example.title);
|
||||||
scenario.set_audio_scene(OutdoorScene::Dry);
|
scenario.set_audio_scene(OutdoorScene::Dry);
|
||||||
const auto started=std::chrono::steady_clock::now();
|
const auto started=std::chrono::steady_clock::now();
|
||||||
float peak_power=0;
|
float peak_power=0;
|
||||||
@@ -25,9 +25,17 @@ int main() {
|
|||||||
if(example.id=="big-carb" && peak_power<10000)throw std::runtime_error("big carb V8 failed to produce power");
|
if(example.id=="big-carb" && peak_power<10000)throw std::runtime_error("big carb V8 failed to produce power");
|
||||||
if(!std::isfinite(snapshot.rpm)||snapshot.rpm<=0)throw std::runtime_error(example.id+" invalid engine speed");
|
if(!std::isfinite(snapshot.rpm)||snapshot.rpm<=0)throw std::runtime_error(example.id+" invalid engine speed");
|
||||||
if(example.engine.cylinders && (snapshot.cylinder_count!=example.engine.cylinders || snapshot.layout!=example.engine.layout))throw std::runtime_error("custom cylinder layout lost");
|
if(example.engine.cylinders && (snapshot.cylinder_count!=example.engine.cylinders || snapshot.layout!=example.engine.layout))throw std::runtime_error("custom cylinder layout lost");
|
||||||
|
const bool custom_v8=example.engine.american_headers();
|
||||||
|
if(custom_v8 && (!snapshot.v8_headers || snapshot.h_pipe!=example.junctions.h_pipe || snapshot.pipe_states[25].empty()))
|
||||||
|
throw std::runtime_error("custom V8 lost American junction headers/crossover");
|
||||||
|
if(example.engine.staged_headers() && example.engine.layout==EngineLayout::Inline) {
|
||||||
|
if(!snapshot.staged_headers || snapshot.dual_exhaust)
|
||||||
|
throw std::runtime_error("inline engine lost staged junction collectors");
|
||||||
|
}
|
||||||
|
if(example.engine.staged_headers() && example.engine.layout==EngineLayout::V && (!snapshot.dual_exhaust || !snapshot.staged_headers))
|
||||||
|
throw std::runtime_error("large V engine lost separate bank outlets");
|
||||||
if(snapshot.example_name!=example.title)throw std::runtime_error("example name lost");
|
if(snapshot.example_name!=example.title)throw std::runtime_error("example name lost");
|
||||||
if(example.kind==ScenarioKind::AmericanV8 && snapshot.h_pipe!=example.v8.h_pipe)throw std::runtime_error("example crossover selection lost");
|
if(example.engine.has_silenced_exhaust() && snapshot.silencers!=example.exhaust.enabled)throw std::runtime_error("example silencer selection lost");
|
||||||
if(example.kind==ScenarioKind::SaabB205 && snapshot.silencers!=example.exhaust.enabled)throw std::runtime_error("example silencer selection lost");
|
|
||||||
std::cout<<example.id<<" passed, rpm="<<snapshot.rpm<<", wall/sim="<<std::chrono::duration<double>(std::chrono::steady_clock::now()-started).count()/2<<std::endl;
|
std::cout<<example.id<<" passed, rpm="<<snapshot.rpm<<", wall/sim="<<std::chrono::duration<double>(std::chrono::steady_clock::now()-started).count()/2<<std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
third_party/tomlplusplus/LICENSE
vendored
Normal file
16
third_party/tomlplusplus/LICENSE
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
8
third_party/tomlplusplus/README.md
vendored
Normal file
8
third_party/tomlplusplus/README.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# toml++
|
||||||
|
|
||||||
|
Vendored single-header parser, version **3.4.0**, from
|
||||||
|
[marzer/tomlplusplus](https://github.com/marzer/tomlplusplus/tree/v3.4.0).
|
||||||
|
|
||||||
|
`toml.hpp` is the upstream amalgamated header. Its MIT license is retained in
|
||||||
|
`LICENSE`. Only `src/engine_config.cpp` includes the third-party parser; public
|
||||||
|
configuration headers use project types.
|
||||||
17748
third_party/tomlplusplus/toml.hpp
vendored
Normal file
17748
third_party/tomlplusplus/toml.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
#include "FluidSim/Scenario.hpp"
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
#include "FluidSim/TwoStrokeScenario.hpp"
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -65,9 +66,13 @@ struct Metrics {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
template <class Scenario>
|
template <class Scenario>
|
||||||
void capture(const std::string &prefix, int frames, float pedal, OutdoorScene scene, TwoStrokeConfig config, PipeSolver solver, float fixed_rpm, float warmup) {
|
void capture(const std::string &prefix, int frames, float pedal, OutdoorScene scene, TwoStrokeConfig config, PipeSolver solver, float fixed_rpm, float warmup, const ScenarioExample* example=nullptr) {
|
||||||
std::unique_ptr<Scenario> s;
|
std::unique_ptr<Scenario> s;
|
||||||
if constexpr (std::is_same_v<Scenario, TwoStrokeScenario>) s = std::make_unique<Scenario>(44100, config);
|
if constexpr (std::is_same_v<Scenario, TwoStrokeScenario>) s = std::make_unique<Scenario>(44100, config);
|
||||||
|
else if constexpr (std::is_same_v<Scenario, FourStrokeScenario>) {
|
||||||
|
if(example) s=std::make_unique<Scenario>(44100,*example);
|
||||||
|
else s=std::make_unique<Scenario>();
|
||||||
|
}
|
||||||
else s = std::make_unique<Scenario>();
|
else s = std::make_unique<Scenario>();
|
||||||
s->set_pipe_solver(solver);
|
s->set_pipe_solver(solver);
|
||||||
s->throttle = pedal;
|
s->throttle = pedal;
|
||||||
@@ -101,6 +106,7 @@ void capture(const std::string &prefix, int frames, float pedal, OutdoorScene sc
|
|||||||
} // namespace
|
} // namespace
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
try {
|
try {
|
||||||
|
std::string example_id;
|
||||||
std::string scenario = "twin", prefix = "build/audio/twin", scene = "wall";
|
std::string scenario = "twin", prefix = "build/audio/twin", scene = "wall";
|
||||||
float seconds = 6, pedal = 1, fixed_rpm = 0, warmup = 0;
|
float seconds = 6, pedal = 1, fixed_rpm = 0, warmup = 0;
|
||||||
TwoStrokeConfig config;
|
TwoStrokeConfig config;
|
||||||
@@ -110,7 +116,8 @@ int main(int argc, char **argv) {
|
|||||||
if (i + 1 >= argc)
|
if (i + 1 >= argc)
|
||||||
throw std::runtime_error("missing option value");
|
throw std::runtime_error("missing option value");
|
||||||
std::string value = argv[++i];
|
std::string value = argv[++i];
|
||||||
if (key == "--scenario")
|
if(key=="--example") example_id=value;
|
||||||
|
else if (key == "--scenario")
|
||||||
scenario = value;
|
scenario = value;
|
||||||
else if (key == "--out")
|
else if (key == "--out")
|
||||||
prefix = value;
|
prefix = value;
|
||||||
@@ -153,7 +160,14 @@ int main(int argc, char **argv) {
|
|||||||
if (!std::filesystem::path(prefix).parent_path().empty())
|
if (!std::filesystem::path(prefix).parent_path().empty())
|
||||||
std::filesystem::create_directories(std::filesystem::path(prefix).parent_path());
|
std::filesystem::create_directories(std::filesystem::path(prefix).parent_path());
|
||||||
const int frames = int(std::lround(seconds * 44100));
|
const int frames = int(std::lround(seconds * 44100));
|
||||||
if (scenario == "twin")
|
const auto examples=scenario_examples();
|
||||||
|
if(!example_id.empty()) {
|
||||||
|
const auto example=std::find_if(examples.begin(),examples.end(),[&](const auto& e){return e.id==example_id;});
|
||||||
|
if(example==examples.end()) throw std::runtime_error("unknown example: "+example_id);
|
||||||
|
if(example->kind==ScenarioKind::TwoStroke)
|
||||||
|
capture<TwoStrokeScenario>(prefix,frames,pedal,selected,example->two,solver,fixed_rpm,warmup);
|
||||||
|
else capture<FourStrokeScenario>(prefix,frames,pedal,selected,config,solver,fixed_rpm,warmup,&*example);
|
||||||
|
} else if (scenario == "twin")
|
||||||
capture<FourStrokeTwinScenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
|
capture<FourStrokeTwinScenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
|
||||||
else if (scenario == "b205")
|
else if (scenario == "b205")
|
||||||
capture<SaabB205Scenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
|
capture<SaabB205Scenario>(prefix, frames, pedal, selected, config, solver, fixed_rpm, warmup);
|
||||||
|
|||||||
80
tools/carb_calibration.cpp
Normal file
80
tools/carb_calibration.cpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#include "FluidSim/ScenarioCatalog.hpp"
|
||||||
|
#include "FluidSim/Scenario.hpp"
|
||||||
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
using namespace fluidsim::port;
|
||||||
|
int main(int argc,char** argv) {
|
||||||
|
const bool sweep=argc>1 && std::string(argv[1])=="--sweep";
|
||||||
|
const bool check=argc>1 && std::string(argv[1])=="--check";
|
||||||
|
bool failed=false;
|
||||||
|
int examples_checked=0;
|
||||||
|
std::cout<<"example,pedal,rpm,opening,carb_afr,charge_afr,free_idle_rpm,free_idle_afr,repairs\n";
|
||||||
|
for(auto e:scenario_examples()) {
|
||||||
|
if(e.kind!=ScenarioKind::TwoStroke && !e.engine.carbureted) continue;
|
||||||
|
if(sweep && argc>2 && e.id!=argv[2]) continue;
|
||||||
|
if(argc>1 && !check && !sweep && e.id!=argv[1]) continue;
|
||||||
|
++examples_checked;
|
||||||
|
const bool two=e.kind==ScenarioKind::TwoStroke;
|
||||||
|
if(!sweep && argc>2) {float k=std::stof(argv[2]);e.two.pilot_jet_scale*=k;e.engine.fuel.pilot_jet_diameter*=k;}
|
||||||
|
if(!sweep && argc>3) {float k=std::stof(argv[3]);e.two.main_jet_scale*=k;e.engine.fuel.main_jet_diameter*=k;}
|
||||||
|
if(!sweep && argc>4) {float k=std::stof(argv[4]);e.two.needle_jet_scale*=k;e.engine.fuel.needle_jet_diameter*=k;}
|
||||||
|
if(!sweep && argc>5) {float k=std::stof(argv[5]);e.two.idle_slide_opening=k;e.engine.ecu.idle_base_opening=k;}
|
||||||
|
if(!sweep && argc>6) {float k=std::stof(argv[6]);e.two.needle_low_scale*=k;e.engine.fuel.needle_low_scale*=k;}
|
||||||
|
if(!sweep && argc>7) {float k=std::stof(argv[7]);e.two.needle_mid_scale*=k;e.engine.fuel.needle_mid_scale*=k;}
|
||||||
|
for(int index=0;index<((sweep||check)?8:4);++index) {
|
||||||
|
const float pedal=(sweep||check)?std::array<float,8>{0,.05f,.1f,.15f,.25f,.5f,.75f,1}[index]:std::array<float,4>{0,.15f,.5f,1}[index];
|
||||||
|
const int point=pedal==0?0:pedal<.3f?1:pedal<.8f?2:3;
|
||||||
|
const float rpm=two?std::array<float,4>{3000,4000,6500,9000}[point]:
|
||||||
|
e.engine.cylinders==1?std::array<float,4>{1400,2500,4000,6000}[point]:std::array<float,4>{800,1800,3000,5000}[point];
|
||||||
|
std::unique_ptr<TwoStrokeScenario> t;
|
||||||
|
std::unique_ptr<FourStrokeScenario> f;
|
||||||
|
if(two) {t=std::make_unique<TwoStrokeScenario>(44100,e.two);t->set_controls(pedal,0,1);}
|
||||||
|
else {f=std::make_unique<FourStrokeScenario>(44100,e);f->set_controls(pedal,0,1);}
|
||||||
|
double afr=0,charge=0,opening=0;std::uint64_t repairs=0;
|
||||||
|
for(int i=0;i<55125;++i) {
|
||||||
|
if(two) {t->crankshaft.omega=rpm*2*pi/60;t->process();}
|
||||||
|
else {f->crankshaft.omega=rpm*2*pi/60;f->process();}
|
||||||
|
if(i>=44100 && i%10==0) {
|
||||||
|
afr+=(two?t->carburetor.indicated_afr():f->snapshot().target_afr)/1103.;
|
||||||
|
charge+=(two?t->crankcase.mass()*t->crankcase.air_fraction()/std::max(t->crankcase.fuel_mass(),1e-12f):f->intake_plenum.mass()*f->intake_plenum.air_fraction()/std::max(f->intake_plenum.fuel_mass(),1e-12f))/1103.;
|
||||||
|
opening+=(two?t->ecu.throttle_opening():f->ecu.throttle_opening())/1103.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!std::isfinite(afr) || afr<11.5 || afr>(point==3?14.5:15.5)) {
|
||||||
|
std::cerr<<e.id<<": AFR outside calibration envelope at pedal "<<pedal<<": "<<afr<<'\n';failed=true;
|
||||||
|
}
|
||||||
|
float free_rpm=0,free_afr=0;
|
||||||
|
if(point==0) {
|
||||||
|
for(int i=0;i<132300;++i) {
|
||||||
|
if(two)t->process();else f->process();
|
||||||
|
if(i>=110250 && i%10==0) free_afr+=(two?t->carburetor.indicated_afr():f->snapshot().target_afr)/2205.;
|
||||||
|
}
|
||||||
|
free_rpm=(two?t->crankshaft.omega:f->crankshaft.omega)*60/(2*pi);
|
||||||
|
}
|
||||||
|
for(const auto& p:two?t->pipes:f->pipes)repairs+=p.positivity_repairs;
|
||||||
|
if(point==0) {
|
||||||
|
float idle_min=3000, idle_max=4000;
|
||||||
|
if(e.id=="fifty") {idle_min=3200;idle_max=4000;}
|
||||||
|
if(e.id=="fivehundred") {idle_min=2800;idle_max=3500;}
|
||||||
|
if(e.id=="single-450") {idle_min=1200;idle_max=1600;}
|
||||||
|
if(e.id=="big-carb") {idle_min=700;idle_max=900;}
|
||||||
|
if(!std::isfinite(free_rpm) || free_rpm<idle_min || free_rpm>idle_max ||
|
||||||
|
!std::isfinite(free_afr) || free_afr<11.5 || free_afr>15.5) {
|
||||||
|
std::cerr<<e.id<<": idle outside target RPM/AFR ("<<free_rpm<<", "<<free_afr<<")\n";failed=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(repairs) {std::cerr<<e.id<<": pipe positivity repairs\n";failed=true;}
|
||||||
|
if(e.id=="yz-racing" && std::abs(opening-e.two.idle_slide_opening)>1e-6) {
|
||||||
|
if(point==0) {std::cerr<<"two-stroke idle stop did not reach its catalog setting\n";failed=true;}
|
||||||
|
}
|
||||||
|
if(e.id=="single-450" && (!f->cylinder1.transported_fuel || !e.engine.carbureted)) {
|
||||||
|
std::cerr<<"450 single must transport carburetor fuel\n";failed=true;
|
||||||
|
}
|
||||||
|
std::cout<<e.id<<','<<pedal<<','<<rpm<<','<<opening<<','<<afr<<','<<charge<<','<<free_rpm<<','<<free_afr<<','<<repairs<<std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!examples_checked) {std::cerr<<"No matching carbureted scenario\n";return 1;}
|
||||||
|
return (check||sweep) && failed ? 1 : 0;
|
||||||
|
}
|
||||||
71
verification/CARB_CALIBRATION.md
Normal file
71
verification/CARB_CALIBRATION.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Carburetor calibration
|
||||||
|
|
||||||
|
The catalog contains seven carbureted two-strokes and two carbureted
|
||||||
|
four-strokes. The 450 cc four-stroke is now carbureted; the 650 cc stays EFI.
|
||||||
|
The 125 variants use a 3% fixed slide stop plus closed-throttle ignition trim;
|
||||||
|
the 50/500 singles use 4.5%. The big-block uses 7%.
|
||||||
|
|
||||||
|
## Measurement
|
||||||
|
|
||||||
|
The `fluidsim_carb_calibration` tool imposes crankshaft RPM with an external
|
||||||
|
motoring/absorbing dyno, runs 1 s to settle, and averages the following 0.25 s.
|
||||||
|
Each operating point starts from a fresh warm scenario. At idle, the dyno is then
|
||||||
|
removed for a further 3 s to check that the engine continues running without
|
||||||
|
starter assistance or artificial sustaining torque.
|
||||||
|
|
||||||
|
| Family | Idle RPM | Low RPM | Mid RPM | Full-throttle RPM |
|
||||||
|
| --- | ---: | ---: | ---: | ---: |
|
||||||
|
| Two-strokes | 3000 | 4000 | 6500 | 9000 |
|
||||||
|
| 450 single | 1400 | 2500 | 4000 | 6000 |
|
||||||
|
| Big-block | 800 | 1800 | 3000 | 5000 |
|
||||||
|
|
||||||
|
Low points use 5%, 10%, 15% and 25% pedal; mid points use 50% and 75%.
|
||||||
|
The mechanical stop remains in force if pedal demand is lower than it.
|
||||||
|
AFR acceptance is 11.5?15.5, narrowed to 11.5?14.5 at full throttle. Free-idle
|
||||||
|
AFR must also stay within 11.5?15.5, with positive running RPM (300?10000).
|
||||||
|
The fixed slide sets idle airflow. Closed-throttle ignition timing trims speed
|
||||||
|
toward 3,000 RPM on the 125s, 3,000 RPM on the 50, and 3,000 RPM on the 500.
|
||||||
|
Four-stroke targets are 1,400 RPM for the 450 and 800 RPM for the big-block.
|
||||||
|
|
||||||
|
## Settled idle and carb-outlet AFR
|
||||||
|
|
||||||
|
| Scenario | Stop | Unloaded idle RPM | Idle AFR | 15% AFR | 50% AFR | Full AFR |
|
||||||
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| Racing 125 | 3% | 3603 | 13.49 | 14.56 | 13.56 | 13.27 |
|
||||||
|
| Straight 125 | 3% | 3754 | 13.48 | 14.70 | 13.32 | 13.27 |
|
||||||
|
| Long chamber 125 | 3% | 3598 | 13.37 | 14.64 | 13.42 | 13.21 |
|
||||||
|
| Short chamber 125 | 3% | 3657 | 13.61 | 14.58 | 13.66 | 13.26 |
|
||||||
|
| Wide chamber 125 | 3% | 3679 | 13.44 | 14.37 | 13.58 | 13.27 |
|
||||||
|
| 50 two-stroke | 4.5% | 3591 | 14.46 | 14.71 | 13.64 | 12.92 |
|
||||||
|
| 500 two-stroke | 4.5% | 3128 | 13.63 | 13.23 | 13.85 | 13.26 |
|
||||||
|
| 450 four-stroke | 4.5% | 1362 | 13.54 | 14.10 | 13.67 | 13.07 |
|
||||||
|
| Big-block | 7% | 763 | 13.52 | 14.68 | 13.12 | 13.17 |
|
||||||
|
|
||||||
|
The previous 125 calibration measured approximately 7.6 at idle, 22.6 at low
|
||||||
|
pedal and 20.2 at full pedal. The previous big-block reached approximately 28.7
|
||||||
|
at full pedal. Independent pilot and main jet dimensions plus a continuous,
|
||||||
|
monotone needle annulus profile correct the off-idle and upper-throttle gaps.
|
||||||
|
These are effective flow calibrations, not measured commercial jet sizes.
|
||||||
|
|
||||||
|
The tool reports `charge_afr` separately: crankcase air/fuel for two-strokes,
|
||||||
|
intake-plenum air/fuel for four-strokes. Reverse flow, residual oxygen depletion,
|
||||||
|
wet-wall transients and transported unburned fuel make that differ from fresh
|
||||||
|
carb-outlet AFR. It is diagnostic rather than the calibration target. Combustion
|
||||||
|
still consumes only the oxygen and fuel actually present; AFR is not clamped to
|
||||||
|
hide a bad mixture and no feedback loop forces the target values.
|
||||||
|
|
||||||
|
## Reproduction
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cmake --build --preset windows
|
||||||
|
ctest --preset windows -R carb_calibration --output-on-failure
|
||||||
|
./out/build/windows-gcc/fluidsim_carb_calibration.exe --sweep yz-racing
|
||||||
|
```
|
||||||
|
|
||||||
|
The calibration sweep includes all eight throttle positions for every carb scenario,
|
||||||
|
target idle RPM and AFR, zero pipe positivity repairs, the catalog slide-stop settings,
|
||||||
|
and transported fueling on the 450. Catalog and two-stroke regressions also
|
||||||
|
exercise throttle changes, coast-down, clutch/gear operation and finite audio.
|
||||||
|
|
||||||
|
[Four-point AFR, charge, and idle measurements](carb_calibration.csv) include charge AFR,
|
||||||
|
actual idle opening, free-running idle RPM and pipe repair counts.
|
||||||
38
verification/CUSTOM_EXHAUST_CHARACTER.md
Normal file
38
verification/CUSTOM_EXHAUST_CHARACTER.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Custom exhaust pulse character
|
||||||
|
|
||||||
|
Compared the previous `src/scenario.cpp` from HEAD with the updated implementation
|
||||||
|
using the same capture tool, default Rusanov solver, dry scene, 55% pedal,
|
||||||
|
3,000 RPM, 0.6 s warmup and 0.5 s recording. No gain normalization or added
|
||||||
|
noise/distortion was applied. Captures are in `out/audio-character/`.
|
||||||
|
|
||||||
|
Energy fractions below use both channels, discard the first 20% of the recording,
|
||||||
|
remove DC and apply a Hann window. The denominator is spectral energy from 20 Hz
|
||||||
|
to 6 kHz. Upper harmonics are above 1.5 times the aggregate firing frequency
|
||||||
|
(RPM / 120 * cylinders); low orders are below 0.8 times that frequency. These
|
||||||
|
are engineering checks on pulse character, not a perceptual realism score.
|
||||||
|
|
||||||
|
| Example | Upper harmonics, before ? after | Low orders, before ? after |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Inline three | 78.6% ? 85.6% | <0.1% ? <0.1% |
|
||||||
|
| F20 inline four | 53.2% ? 87.0% | <0.1% ? 1.1% |
|
||||||
|
| Inline five | 51.6% ? 92.5% | <0.1% ? 0.3% |
|
||||||
|
| RB26 inline six | 21.4% ? 88.4% | <0.1% ? <0.1% |
|
||||||
|
| Long-stroke inline six | 29.0% ? 81.3% | <0.1% ? <0.1% |
|
||||||
|
| Big-block V8 | 40.3% ? 59.6% | <0.1% ? 23.2% |
|
||||||
|
| V10 | 84.6% ? 31.0% | <0.1% ? 14.4% |
|
||||||
|
| V12 | 52.2% ? 55.1% | 0.1% ? 19.7% |
|
||||||
|
|
||||||
|
The V10's energy shifts toward lower bank orders rather than simply becoming
|
||||||
|
brighter. All eight updated examples also completed fixed-RPM captures at 1,000
|
||||||
|
and 6,000 RPM using the same warmup/duration/pedal. All 24 updated captures
|
||||||
|
produced finite audio with zero clipping samples. Maximum rendered absolute
|
||||||
|
amplitude across these captures was 0.524 full scale.
|
||||||
|
|
||||||
|
The catalog regression runs each example through one second of throttle and one
|
||||||
|
second of pedal release. It checks finite audio, sustained positive RPM, zero
|
||||||
|
pipe positivity repairs, big-block power, and the new topology flags. The desktop
|
||||||
|
UI and physics regression tests cover the existing controls and base simulation.
|
||||||
|
|
||||||
|
For listening comparisons, match playback loudness: removing large collector
|
||||||
|
volumes increases level as well as changing spectrum. Geometry and calibration
|
||||||
|
remain estimates; these measurements do not establish a match to recorded engines.
|
||||||
37
verification/carb_calibration.csv
Normal file
37
verification/carb_calibration.csv
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
example,pedal,rpm,opening,carb_afr,charge_afr,free_idle_rpm,free_idle_afr,repairs
|
||||||
|
yz-racing,0,3000,0.03,13.4852,17.6506,3602.68,13.2998,0
|
||||||
|
yz-racing,0.15,4000,0.15,14.5615,14.5141,0,0,0
|
||||||
|
yz-racing,0.5,6500,0.5,13.5614,11.0273,0,0,0
|
||||||
|
yz-racing,1,9000,1,13.2665,9.14679,0,0,0
|
||||||
|
yz-straight,0,3000,0.03,13.4847,13.8691,3753.7,13.4844,0
|
||||||
|
yz-straight,0.15,4000,0.15,14.6983,14.6936,0,0,0
|
||||||
|
yz-straight,0.5,6500,0.5,13.3192,8.37308,0,0,0
|
||||||
|
yz-straight,1,9000,1,13.2662,8.6134,0,0,0
|
||||||
|
yz-torque,0,3000,0.03,13.3698,17.2726,3597.72,13.4608,0
|
||||||
|
yz-torque,0.15,4000,0.15,14.6447,14.6351,0,0,0
|
||||||
|
yz-torque,0.5,6500,0.5,13.4205,9.57616,0,0,0
|
||||||
|
yz-torque,1,9000,1,13.2114,9.72874,0,0,0
|
||||||
|
yz-short,0,3000,0.03,13.6119,18.2684,3657.15,12.7677,0
|
||||||
|
yz-short,0.15,4000,0.15,14.5772,14.5825,0,0,0
|
||||||
|
yz-short,0.5,6500,0.5,13.6625,11.2674,0,0,0
|
||||||
|
yz-short,1,9000,1,13.258,8.56493,0,0,0
|
||||||
|
yz-fat,0,3000,0.03,13.4371,18.0872,3679.1,13.3084,0
|
||||||
|
yz-fat,0.15,4000,0.15,14.3698,14.3344,0,0,0
|
||||||
|
yz-fat,0.5,6500,0.5,13.583,11.1147,0,0,0
|
||||||
|
yz-fat,1,9000,1,13.2664,9.04608,0,0,0
|
||||||
|
fifty,0,3000,0.045,14.4588,18.5582,3590.97,14.3795,0
|
||||||
|
fifty,0.15,4000,0.15,14.7126,14.8354,0,0,0
|
||||||
|
fifty,0.5,6500,0.5,13.6415,13.2838,0,0,0
|
||||||
|
fifty,1,9000,1,12.9222,9.32989,0,0,0
|
||||||
|
fivehundred,0,3000,0.045,13.6326,15.6191,3128.38,13.6017,0
|
||||||
|
fivehundred,0.15,4000,0.15,13.2298,13.1972,0,0,0
|
||||||
|
fivehundred,0.5,6500,0.5,13.8488,12.409,0,0,0
|
||||||
|
fivehundred,1,9000,1,13.2616,10.1366,0,0,0
|
||||||
|
single-450,0,1400,0.0449999,13.5449,14.5374,1362.41,13.4177,0
|
||||||
|
single-450,0.15,2500,0.15,14.103,14.0662,0,0,0
|
||||||
|
single-450,0.5,4000,0.499999,13.668,13.9308,0,0,0
|
||||||
|
single-450,1,6000,0.999999,13.0708,10.7667,0,0,0
|
||||||
|
big-carb,0,800,0.0699998,13.5196,13.7285,763.257,13.5412,0
|
||||||
|
big-carb,0.15,1800,0.15,14.6817,14.6857,0,0,0
|
||||||
|
big-carb,0.5,3000,0.499999,13.1217,13.1227,0,0,0
|
||||||
|
big-carb,1,5000,0.999999,13.1704,13.174,0,0,0
|
||||||
|
Reference in New Issue
Block a user