Repository: ange-yaghi/engine-sim Branch: master Commit: 85f7c3b959a9 Files: 273 Total size: 971.4 KB Directory structure: gitextract_1h23g_5j/ ├── .github/ │ └── workflows/ │ └── cmake.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── art/ │ └── assets.blend ├── assets/ │ ├── assets.dia │ ├── assets.ysce │ ├── engines/ │ │ ├── atg-video-1/ │ │ │ ├── 01_honda_trx520.mr │ │ │ ├── 02_kohler_ch750.mr │ │ │ ├── 03_harley_davidson_shovelhead.mr │ │ │ ├── 04_hayabusa.mr │ │ │ ├── 05_honda_vtec.mr │ │ │ ├── 06_subaru_ej25.mr │ │ │ ├── 07_audi_i5.mr │ │ │ ├── 08_radial_5.mr │ │ │ ├── README.md │ │ │ └── radial.mr │ │ ├── atg-video-2/ │ │ │ ├── 01_subaru_ej25_eh.mr │ │ │ ├── 02_subaru_ej25_uh.mr │ │ │ ├── 03_2jz.mr │ │ │ ├── 04_60_degree_v6.mr │ │ │ ├── 05_odd_fire_v6.mr │ │ │ ├── 06_even_fire_v6.mr │ │ │ ├── 07_gm_ls.mr │ │ │ ├── 08_ferrari_f136_v8.mr │ │ │ ├── 09_radial_9.mr │ │ │ ├── 10_lfa_v10.mr │ │ │ ├── 11_merlin_v12.mr │ │ │ ├── 12_ferrari_412_t2.mr │ │ │ ├── README.md │ │ │ └── radial.mr │ │ ├── audi/ │ │ │ └── i5.mr │ │ ├── bmw/ │ │ │ └── M52B28.mr │ │ ├── chevrolet/ │ │ │ ├── chev_truck_454.mr │ │ │ └── engine_03_for_e1.mr │ │ └── kohler/ │ │ └── kohler_ch750.mr │ ├── main.mr │ ├── part-library/ │ │ ├── part_library.mr │ │ └── parts/ │ │ ├── cam_lobes.mr │ │ ├── camshafts.mr │ │ ├── heads.mr │ │ ├── ignition_modules.mr │ │ └── intakes.mr │ └── themes/ │ ├── amateur.mr │ ├── bubble_gum.mr │ ├── default.mr │ ├── minimalistic.mr │ ├── night_vision.mr │ └── paper.mr ├── configuration/ │ └── delta.conf ├── dependencies/ │ ├── CMakeLists.txt │ ├── discord/ │ │ ├── CMakeLists.txt │ │ ├── Discord.cpp │ │ ├── Discord.h │ │ ├── LICENSE │ │ ├── discord_register.h │ │ ├── discord_rpc.h │ │ └── lib/ │ │ └── discord-rpc.lib │ └── submodules/ │ └── CMakeLists.txt ├── es/ │ ├── actions/ │ │ └── actions.mr │ ├── constants/ │ │ ├── constants.mr │ │ └── units.mr │ ├── engine_sim.mr │ ├── infrastructure/ │ │ └── infrastructure.mr │ ├── objects/ │ │ └── objects.mr │ ├── part-library/ │ │ ├── part_library.mr │ │ └── parts/ │ │ ├── cam_lobes.mr │ │ ├── camshafts.mr │ │ ├── heads.mr │ │ ├── ignition_modules.mr │ │ └── intakes.mr │ ├── settings/ │ │ └── application_settings.mr │ ├── sound-library/ │ │ └── impulse_responses.mr │ ├── types/ │ │ ├── atomic_types.mr │ │ ├── conversions.mr │ │ └── operations.mr │ └── utilities/ │ └── utilities.mr ├── include/ │ ├── adaptive_volume_filter.h │ ├── afr_cluster.h │ ├── application_settings.h │ ├── audio_buffer.h │ ├── butterworth_low_pass_filter.h │ ├── camshaft.h │ ├── combustion_chamber.h │ ├── combustion_chamber_object.h │ ├── connecting_rod.h │ ├── connecting_rod_object.h │ ├── constants.h │ ├── convolution_filter.h │ ├── crankshaft.h │ ├── crankshaft_object.h │ ├── csv_io.h │ ├── cylinder_bank.h │ ├── cylinder_bank_object.h │ ├── cylinder_head.h │ ├── cylinder_head_object.h │ ├── cylinder_pressure_gauge.h │ ├── cylinder_temperature_gauge.h │ ├── delay_filter.h │ ├── delta.h │ ├── derivative_filter.h │ ├── direct_throttle_linkage.h │ ├── dtv.h │ ├── dynamometer.h │ ├── engine.h │ ├── engine_sim_application.h │ ├── engine_view.h │ ├── exhaust_system.h │ ├── feedback_comb_filter.h │ ├── filter.h │ ├── firing_order_display.h │ ├── fuel.h │ ├── fuel_cluster.h │ ├── function.h │ ├── gas_system.h │ ├── gauge.h │ ├── gaussian_filter.h │ ├── geometry_generator.h │ ├── governor.h │ ├── ignition_module.h │ ├── impulse_response.h │ ├── info_cluster.h │ ├── intake.h │ ├── jitter_filter.h │ ├── labeled_gauge.h │ ├── leveling_filter.h │ ├── load_simulation_cluster.h │ ├── low_pass_filter.h │ ├── mixer_cluster.h │ ├── oscilloscope.h │ ├── oscilloscope_cluster.h │ ├── part.h │ ├── performance_cluster.h │ ├── piston.h │ ├── piston_engine_simulator.h │ ├── piston_object.h │ ├── preemphasis_filter.h │ ├── right_gauge_cluster.h │ ├── ring_buffer.h │ ├── scs.h │ ├── shaders.h │ ├── simulation_object.h │ ├── simulator.h │ ├── standard_valvetrain.h │ ├── starter_motor.h │ ├── synthesizer.h │ ├── throttle.h │ ├── throttle_display.h │ ├── transmission.h │ ├── ui_button.h │ ├── ui_element.h │ ├── ui_manager.h │ ├── ui_math.h │ ├── ui_utilities.h │ ├── units.h │ ├── utilities.h │ ├── valvetrain.h │ ├── vehicle.h │ ├── vehicle_drag_constraint.h │ └── vtec_valvetrain.h ├── scripting/ │ ├── include/ │ │ ├── actions.h │ │ ├── camshaft_node.h │ │ ├── channel_types.h │ │ ├── compiler.h │ │ ├── connecting_rod_node.h │ │ ├── crankshaft_node.h │ │ ├── cylinder_bank_node.h │ │ ├── cylinder_head_node.h │ │ ├── engine_context.h │ │ ├── engine_node.h │ │ ├── engine_sim.h │ │ ├── exhaust_system_node.h │ │ ├── fuel_node.h │ │ ├── function_node.h │ │ ├── ignition_module_node.h │ │ ├── ignition_wire_node.h │ │ ├── impulse_response_node.h │ │ ├── intake_node.h │ │ ├── language_rules.h │ │ ├── node.h │ │ ├── object_reference_node.h │ │ ├── object_reference_node_output.h │ │ ├── piranha.h │ │ ├── piston_node.h │ │ ├── rod_journal_node.h │ │ ├── standard_valvetrain_node.h │ │ ├── throttle_nodes.h │ │ ├── transmission_node.h │ │ ├── valvetrain_node.h │ │ ├── vehicle_node.h │ │ └── vtec_valvetrain_node.h │ └── src/ │ ├── channel_types.cpp │ ├── compiler.cpp │ ├── engine_context.cpp │ └── language_rules.cpp ├── src/ │ ├── afr_cluster.cpp │ ├── audio_buffer.cpp │ ├── camshaft.cpp │ ├── combustion_chamber.cpp │ ├── combustion_chamber_object.cpp │ ├── connecting_rod.cpp │ ├── connecting_rod_object.cpp │ ├── convolution_filter.cpp │ ├── crankshaft.cpp │ ├── crankshaft_object.cpp │ ├── cylinder_bank.cpp │ ├── cylinder_bank_object.cpp │ ├── cylinder_head.cpp │ ├── cylinder_head_object.cpp │ ├── cylinder_pressure_gauge.cpp │ ├── cylinder_temperature_gauge.cpp │ ├── delay_filter.cpp │ ├── derivative_filter.cpp │ ├── direct_throttle_linkage.cpp │ ├── dynamometer.cpp │ ├── engine.cpp │ ├── engine_sim_application.cpp │ ├── engine_view.cpp │ ├── exhaust_system.cpp │ ├── feedback_comb_filter.cpp │ ├── filter.cpp │ ├── firing_order_display.cpp │ ├── fuel.cpp │ ├── fuel_cluster.cpp │ ├── function.cpp │ ├── gas_system.cpp │ ├── gauge.cpp │ ├── gaussian_filter.cpp │ ├── geometry_generator.cpp │ ├── governor.cpp │ ├── ignition_module.cpp │ ├── impulse_response.cpp │ ├── info_cluster.cpp │ ├── intake.cpp │ ├── jitter_filter.cpp │ ├── labeled_gauge.cpp │ ├── leveling_filter.cpp │ ├── load_simulation_cluster.cpp │ ├── low_pass_filter.cpp │ ├── main.cpp │ ├── mixer_cluster.cpp │ ├── oscilloscope.cpp │ ├── oscilloscope_cluster.cpp │ ├── part.cpp │ ├── performance_cluster.cpp │ ├── piston.cpp │ ├── piston_engine_simulator.cpp │ ├── piston_object.cpp │ ├── right_gauge_cluster.cpp │ ├── shaders.cpp │ ├── simulation_object.cpp │ ├── simulator.cpp │ ├── standard_valvetrain.cpp │ ├── starter_motor.cpp │ ├── synthesizer.cpp │ ├── throttle.cpp │ ├── throttle_display.cpp │ ├── transmission.cpp │ ├── ui_button.cpp │ ├── ui_element.cpp │ ├── ui_manager.cpp │ ├── ui_math.cpp │ ├── ui_utilities.cpp │ ├── utilities.cpp │ ├── valvetrain.cpp │ ├── vehicle.cpp │ ├── vehicle_drag_constraint.cpp │ └── vtec_valvetrain.cpp └── test/ ├── function_test.cpp ├── gas_system_tests.cpp └── synthesizer_tests.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/cmake.yml ================================================ name: CMake on: push: branches: [ "master" ] pull_request: branches: [ "master" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: build: # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: windows-latest env: # Set your boost version BOOST_VERSION: 1.78.0 # Set you boost path to the default one (I don't know if you can use variables here) BOOST_PATH: ${{github.workspace}}/boost/ steps: - uses: actions/checkout@v3 - name: Checkout submodules run: git submodule update --init --recursive # Retrieve the cache, uses cache@v2 - name: Cache boost uses: actions/cache@v2 id: cache-boost with: # Set the path to cache path: ${{env.BOOST_PATH}} # Use the version as the key to only cache the correct version key: boost-${{env.BOOST_VERSION}} # Actual install step (only runs if the cache is empty) - name: Install boost if: steps.cache-boost.outputs.cache-hit != 'true' uses: MarkusJx/install-boost@v2.3.0 with: # Set the boost version (required) boost_version: ${{env.BOOST_VERSION}} # Set the install directory boost_install_dir: ${{env.BOOST_PATH}} # Set your platform version platform_version: 2022 # Set the toolset toolset: msvc - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} env: BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} - name: Build # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} ================================================ FILE: .gitignore ================================================ # Ignore user's workspace workspace/ # Build directory build/ # clangd cache and code completion files .cache compile_commands.json # Blender temporary files *.blend1 ================================================ FILE: .gitmodules ================================================ [submodule "dependencies/submodules/delta-studio"] path = dependencies/submodules/delta-studio url = https://github.com/ange-yaghi/delta-studio [submodule "dependencies/submodules/simple-2d-constraint-solver"] path = dependencies/submodules/simple-2d-constraint-solver url = https://github.com/ange-yaghi/simple-2d-constraint-solver [submodule "dependencies/submodules/direct-to-video"] path = dependencies/submodules/direct-to-video url = https://github.com/ange-yaghi/direct-to-video [submodule "dependencies/submodules/csv-io"] path = dependencies/submodules/csv-io url = https://github.com/ange-yaghi/csv-io [submodule "dependencies/submodules/piranha"] path = dependencies/submodules/piranha url = https://github.com/ange-yaghi/piranha.git ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) option(DTV "Enable video output" OFF) option(PIRANHA_ENABLED "Enable scripting input" ON) option(DISCORD_ENABLED "Enable Discord Rich Presence" ON) if (DTV) add_compile_definitions(ATG_ENGINE_SIM_VIDEO_CAPTURE) endif (DTV) if (PIRANHA_ENABLED) add_compile_definitions(ATG_ENGINE_SIM_PIRANHA_ENABLED) endif (PIRANHA_ENABLED) if (DISCORD_ENABLED) add_compile_definitions(ATG_ENGINE_SIM_DISCORD_ENABLED) endif (DISCORD_ENABLED) # Enable group projects in folders set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "cmake") project(engine-sim) set(CMAKE_CXX_STANDARD 17) # ======================================================== # GTEST include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) set_property(TARGET gmock PROPERTY FOLDER "gtest") set_property(TARGET gmock_main PROPERTY FOLDER "gtest") set_property(TARGET gtest PROPERTY FOLDER "gtest") set_property(TARGET gtest_main PROPERTY FOLDER "gtest") # ======================================================== add_library(engine-sim STATIC # Source files src/audio_buffer.cpp src/camshaft.cpp src/crankshaft.cpp src/combustion_chamber.cpp src/connecting_rod.cpp src/convolution_filter.cpp src/cylinder_bank.cpp src/cylinder_head.cpp src/delay_filter.cpp src/derivative_filter.cpp src/direct_throttle_linkage.cpp src/dynamometer.cpp src/engine.cpp src/exhaust_system.cpp src/feedback_comb_filter.cpp src/filter.cpp src/fuel.cpp src/function.cpp src/gas_system.cpp src/gaussian_filter.cpp src/governor.cpp src/ignition_module.cpp src/impulse_response.cpp src/intake.cpp src/jitter_filter.cpp src/leveling_filter.cpp src/low_pass_filter.cpp src/part.cpp src/piston.cpp src/piston_engine_simulator.cpp src/simulator.cpp src/standard_valvetrain.cpp src/starter_motor.cpp src/synthesizer.cpp src/throttle.cpp src/transmission.cpp src/utilities.cpp src/valvetrain.cpp src/vehicle.cpp src/vehicle_drag_constraint.cpp src/vtec_valvetrain.cpp # Include files include/audio_buffer.h include/application_settings.h include/camshaft.h include/crankshaft.h include/combustion_chamber.h include/connecting_rod.h include/convolution_filter.h include/cylinder_bank.h include/cylinder_head.h include/delay_filter.h include/derivative_filter.h include/direct_throttle_linkage.h include/dynamometer.h include/engine.h include/exhaust_system.h include/feedback_comb_filter.h include/filter.h include/fuel.h include/function.h include/gas_system.h include/gaussian_filter.h include/governor.h include/ignition_module.h include/impulse_response.h include/intake.h include/jitter_filter.h include/leveling_filter.h include/low_pass_filter.h include/part.h include/piston.h include/piston_engine_simulator.h include/simulator.h include/standard_valvetrain.h include/starter_motor.h include/synthesizer.h include/throttle.h include/transmission.h include/units.h include/utilities.h include/valvetrain.h include/vehicle.h include/vehicle_drag_constraint.h include/vtec_valvetrain.h ) target_link_libraries(engine-sim simple-2d-constraint-solver csv-io delta-basic) target_include_directories(engine-sim PUBLIC dependencies/submodules) if (PIRANHA_ENABLED) add_library(engine-sim-script-interpreter STATIC # Source files scripting/src/channel_types.cpp scripting/src/compiler.cpp scripting/src/engine_context.cpp scripting/src/language_rules.cpp # Include files scripting/include/actions.h scripting/include/camshaft_node.h scripting/include/channel_types.h scripting/include/compiler.h scripting/include/connecting_rod_node.h scripting/include/crankshaft_node.h scripting/include/cylinder_bank_node.h scripting/include/cylinder_head_node.h scripting/include/engine_context.h scripting/include/engine_node.h scripting/include/engine_sim.h scripting/include/exhaust_system_node.h scripting/include/intake_node.h scripting/include/function_node.h scripting/include/ignition_module_node.h scripting/include/ignition_wire_node.h scripting/include/impulse_response_node.h scripting/include/intake_node.h scripting/include/language_rules.h scripting/include/node.h scripting/include/object_reference_node.h scripting/include/object_reference_node_output.h scripting/include/piranha.h scripting/include/piston_node.h scripting/include/rod_journal_node.h scripting/include/standard_valvetrain_node.h scripting/include/transmission_node.h scripting/include/valvetrain_node.h scripting/include/vtec_valvetrain_node.h scripting/include/vehicle_node.h ) target_include_directories(engine-sim-script-interpreter PUBLIC dependencies/submodules) target_link_libraries(engine-sim-script-interpreter csv-io piranha) endif (PIRANHA_ENABLED) if (DISCORD_ENABLED) add_library(discord STATIC # Source files dependencies/discord/Discord.cpp # Include files dependencies/discord/Discord.h dependencies/discord/discord_register.h dependencies/discord/discord_rpc.h ) endif (DISCORD_ENABLED) add_executable(engine-sim-app WIN32 # Source files src/main.cpp src/engine_sim_application.cpp src/geometry_generator.cpp src/simulation_object.cpp src/piston_object.cpp src/connecting_rod_object.cpp src/ui_element.cpp src/ui_manager.cpp src/cylinder_pressure_gauge.cpp src/ui_math.cpp src/gauge.cpp src/crankshaft_object.cpp src/cylinder_bank_object.cpp src/cylinder_head_object.cpp src/ui_button.cpp src/ui_utilities.cpp src/combustion_chamber_object.cpp src/oscilloscope.cpp src/shaders.cpp src/engine_view.cpp src/right_gauge_cluster.cpp src/cylinder_temperature_gauge.cpp src/labeled_gauge.cpp src/throttle_display.cpp src/afr_cluster.cpp src/fuel_cluster.cpp src/oscilloscope_cluster.cpp src/performance_cluster.cpp src/firing_order_display.cpp src/load_simulation_cluster.cpp src/mixer_cluster.cpp src/info_cluster.cpp # Include files include/delta.h include/dtv.h include/engine_sim_application.h include/geometry_generator.h include/simulation_object.h include/piston_object.h include/connecting_rod_object.h include/ui_element.h include/ui_manager.h include/cylinder_pressure_gauge.h include/ui_math.h include/units.h include/crankshaft_object.h include/cylinder_bank_object.h include/cylinder_head_object.h include/ui_button.h include/ui_utilities.h include/combustion_chamber_object.h include/oscilloscope.h include/shaders.h include/engine_view.h include/right_gauge_cluster.h include/cylinder_temperature_gauge.h include/labeled_gauge.h include/throttle_display.h include/afr_cluster.h include/fuel_cluster.h include/oscilloscope_cluster.h include/performance_cluster.h include/firing_order_display.h include/load_simulation_cluster.h include/mixer_cluster.h include/info_cluster.h ) target_link_libraries(engine-sim-app engine-sim ) if (DTV) target_link_libraries(engine-sim-app direct-to-video) endif (DTV) if (PIRANHA_ENABLED) target_link_libraries(engine-sim-app engine-sim-script-interpreter) endif (PIRANHA_ENABLED) if (DISCORD_ENABLED) add_library(discord-rpc STATIC IMPORTED) set_property(TARGET discord-rpc PROPERTY IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/dependencies/discord/lib/discord-rpc.lib) target_link_libraries(engine-sim-app discord discord-rpc) endif (DISCORD_ENABLED) target_include_directories(engine-sim-app PUBLIC dependencies/submodules) add_subdirectory(dependencies) # GTEST enable_testing() add_executable(engine-sim-test # Source files test/gas_system_tests.cpp test/function_test.cpp test/synthesizer_tests.cpp ) target_link_libraries(engine-sim-test gtest_main engine-sim ) include(GoogleTest) gtest_discover_tests(engine-sim-test) ================================================ FILE: LICENSE ================================================ Copyright 2022 AngeTheGreat (Ange Yaghi) 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. ================================================ FILE: README.md ================================================ # Engine Simulator ![Alt text](docs/public/screenshots/screenshot_v01.png?raw=true) --- # Engine Simulator has moved! To get the newest releases of the game, [click here](https://github.com/Engine-Simulator/engine-sim-community-edition). --- ## What is this? This is a real-time internal combustion engine simulation **designed specifically to produce engine audio and simulate engine response characteristics.** It is NOT a scientific tool and cannot be expected to provide accurate figures for the purposes of engineering or engine tuning. ## How do I install it? This is a code repository and might not look like other software that you're used to downloading and installing (if you're not familiar with programming). To download a ready-to-use version of the application, navigate to the [releases page](https://github.com/ange-yaghi/engine-sim/releases), find the most recent release (ex. `v0.1.5a`), click "Assets" and download the .zip file with a name that starts with `engine-sim-build`. Unzip this file, then run `bin/engine-sim-app.exe`. The simulator should then start normally. Check out [our Frequently Asked Questions](https://github.com/ange-yaghi/engine-sim/wiki/Frequently-Asked-Questions) if you need more details. ## How do I use it? The UI is extremely minimalistic and there are only a few controls used to interact with the engine: | Key/Input | Action | | :---: | :---: | | A | Toggle ignition | | S | Hold for starter | | D | Enable dyno | | H | Enable RPM hold (see below for instructions) | | G + Scroll | Change hold speed | | F | Enter fullscreen mode | | I | Display dyno stats in the information panel | | Shift | Clutch (hold spacebar at the same time to slowly engage/disengage) | | Up Arrow | Up Gear | | Down Arrow | Down Gear | | Z + Scroll | Volume | | X + Scroll | Convolution Level | | C + Scroll | High frequency gain | | V + Scroll | Low frequency noise | | B + Scroll | High frequency noise | | N + Scroll | Simulation frequency | | M | Increase view layer | | , | Decrease view layer | | Enter | Reload engine script | | Escape | Exit the program | | Q, W, E, R | Change throttle position | | Space + Scroll | Fine throttle adjustment | | 1, 2, 3, 4, 5 | Simulation time warp | | Tab | Change screen | ### Using the RPM hold The RPM hold feature will hold the engine at a specific RPM and also measure the engine's horsepower and torque at that RPM. You can enable RPM hold by pressing the `H` key. **You must then enable the dynomometer** (press the `D` key) in order for the RPM hold to take effect. To change the hold speed, hold the `G` key and scroll with the mouse wheel. The RPM hold will be shown on the `DYNO. SPEED` gauge in the lower left of the screen. ## Why is the code so sloppy? I wrote this to demo in a [YouTube video](https://youtu.be/RKT-sKtR970), not as a real product. If you would like it to become a usable product please reach out to me or join my Discord (link can be found in the description of the aforementioned YouTube video). I use this codebase for my own purposes and so it might change frequently and without warning. ## How do I build it? (Ignore this section if you're not a developer!) **Note: this project currently only builds on Windows!** ### Step 1 - Clone the repository ```git clone --recurse-submodules https://github.com/ange-yaghi/engine-sim``` ### Step 2 - Install CMake Install the latest version of CMake [here](https://cmake.org/) if it's not already installed. ### Step 3 - Install Dependencies You will need to install the following dependencies and CMake will need to be able to locate them (ie. they need to be listed on your PATH): 1. SDL2 2. SDL2_image 3. Boost (make sure to build the optional dependencies) 4. Flex and Bison ### Step 4 - Build and Run From the root directory of the project, run the following commands: ``` mkdir build cd build cmake .. cmake --build . ``` If these steps are successful, a Visual Studio solution will be generated in ```build```. You can open this project with Visual Studio and then run the ```engine-sim-app``` project. If you encounter an error telling you that you're missing DLLs, you will have to copy those DLLs to your EXE's directory. ## Patreon Supporters This project was made possible by the generous donations of the following individuals! ### Grease Monkeys |||||| |-|-|-|-|-| |Devin@Hondatuningsuite|nut|Devin C Martinez|WelcomeCat|Saints Sasha| |Ida 8858|Emily|Steelorse |Kruddy|Sgt. Fluff| |darcuter|FatFluffyFox|Benton1234|Jim C K Flaten|The Zuck| |Blade Skydancer|Ye' old apple|Hayden Henderson|AlphaX|Lucas Martins Bündchen| |Jay Dog|damo|IBS-IS-CRAP|Snowy|Noah Greenberg| |Eisberg|Brendan M.|Alex Layton|Lukas Bartee|Thibaut Dubuisson| |The Cheeze Ity|JoeJimTom|MichaelB450|Björn|Bartdavy| |sasha bandelier|Caleb Black|COOKIES|Andrew Cooper|asimo3089| |Vim Wizard|Kevin Arsenault|Carl Linden|Kele Tappi|Kroklethon| |labourateur|viperfan7|SlimmyJimmy|Jason Becker|Sascha Kamp| |ves|Supernalboot |BeamNG|Paul Harrison|Tyler Russell (Nytelife26)| |nicholas jacobs|DrDotMadness|AVeryPlainTyler|Zach Perez|Paul Schaefer| |Clay Bauer|CR33DYM0N14|julien nadeau|Patt313|Philip Edwards| |RegularRuby670|Mateusz Ładosz|FémLol Stúdió|Crazy Yany|Elden| |Tristan Walker|Matthew McDonald|Jan-Sander Huiting|Mitchell Almstedt|Dylan Lebiedz| |Name Here|LoganBoi FNAF|Epic Randomness|MrPiThon|mike | |dung|Alvaro ArroyoZamora|Skinna Godwin|BeppoBarone|レナVA| |Sabata |Brady Fulham|Powerpuncher |NK10K|Gavin Osowski| |Orbitstrider|Steven Doyle|Jaksu2696|Toni |Devin Abolins| ### Tuners |||||| |-|-|-|-|-| |Boosted Media|Matthew McLennan|Venican|Lyan le Golmuth|Alberto R.| |BetaToaster|Akira Takemoto|J Anderson|Apolly007|LexLuther| |xilophor|Robert K|viktor lind|Adrian Kucinski|sarowie .| |Chris Fischer|Marlod|Chase Hansen|Aidan Szalanski|Andrew Taylor| |Jason Hwang|Juuso Natunen|Ian Moss|PickleRick |Beljim46| |RSOFT92|UCD|Sped|OldManJenkins|James Hart| |Kalle Nilsson|XxBrasta455xX |Colin Sandage|Dakota Mackinnon|Carter Kopp| |Jakub Kozak|CJ Plessas|Loizeau|Charles Mills|YellowLight| |Didrik Esbjug|Alessandro Dal Pino|Carter Williams|Robert D|Cadence Plume| |BLANK|Provenance EMU|Dylan Engler|Nathan Rojas|Cornelius| |Acid|larsloveslegos|Maxime Desages|GM|BreadForMen| |Devin Freeman|Lieven RYCKEBOER|Amelia Taylor|Jelle Plukker|sodmo | |Jimmy Briscoe|Cirithor|Martin .K|DMartland|Lucas Diem| |Richard Budíček|Jack Sheppeard|Meemen|Anderson Huynh|NPException| |Mattia Villa|C|AIDAN POWELL|Brenn_the_Otter|Lane Mosier| |Ceze |oranjest1|Jw|ISON |Mathew Graham| |MACHINA|John Crowell|Asher Blythe|Cronos Skies|Matt Amott| |CpTKugelHagel|Simon Krayer|Caleb Bek|Monster Man25|GeneralMoineau| |EsuKurimu|Caleb Sartin|Jared L.|Hunter Wood|Ben Poole| |Steven Victoria|Jordan Zondlak|Agelessgod|Christopher Fahs|Jonathan Vincent| |Dalton Guillot|Simon Stojanovic|Andrew Urbanczyk|deniaL|Tyler Hughes| |vPam |Justin Kruithof|Curtis C Coomber|Sawyer Clark|Mike Hart| |Ciro Rancourt|Miles Guo|Rewind |E=mc^2|Keaton Call| |J.Es|Jeremy B|Chance Hall|Jack Tompkins|Race Sim Studio| |Quentin ZAOUI|Floyd Henderson|James Haylow|Milkshiekh |Wyatt Todd| |User 2820|Leon Schutte|CYBERBUG_JR|sebiii|Keegan| |Victor Cosiuga|Rolly !|Elias Pettersson|Tyson Makovec|Bill McDermott| |Phontonic|Simon Armstrong|avec |KidozyGAME (Dead)|Stephan Cote| |Justin Biggerstaff|Jabba Jubba|notD34THNIGHT|Inventor|Wesley Bear| |Supersonic2510|Pixel|Simon Bernhardt|Bas Vangermeersch|ToyotaCipra| |kyle crawford|ApatheticWood|Ben Vaughn|Erich Westhoven|Zack Myers| |Tbjoern|Vetle Høgås|Derek Thom|Aaron Beck|| ### Junior Mechanics |||||| |-|-|-|-|-| |Karol Szép|Leon Jordan|Nathan Higginson|Patrick F|Samuel Picard| |Alexander Fritsch|Lucas Scarpi|Jack Humbert|G2Eneko|SweCreations| |Marius Becker|Cedric Wille|infernap12 |Julian Dinges|Wamuthas| |Alex Mason|Hawar Karem|Melonenstrauch|Jacek Dębski|Alex Eastman| |Darren Taing|Po Wang|Giorgio Iannucci|Levis|Eden| |Alin Chiparatu|Arjun Mandakath|A.M. |Dylan Ryan|Noah Entrekin| |GT130|Josh D|generic|Henrik Cohrs|Nic Yetter| |Dan Fredriksen|153AN1MJ|Rasmus|EpicEcho|Kaur Hendrikson| |Maddox Partridge|L33TIFY_|Zack Fletcher|teiiio|Mike Zaite| |Evan Sonin|Christopher Zimmerman|PrefacedVase |funtomr|Triton Alabaster| |appelpie|Julien Ferluc|AnomalousFerret_|Miles Orozco|Spencer Teeter| |ThatCanadian|Harry Prabowo|Dylan Rogerson|Jaedyn Allen|Zephyr Sefira| |Alexander Stone|Mason Little|Wojciech Czop|ryzen5 |Kosta Diamantis| |Karol Stodolak|Tim van der Linde|Loïc Ruttner|jonthefuzz|AsgarK| |James Morgan|Elijah |1ntl|Tobias Johansson|Mome | |P|SOPA_|Shingekuro|Sean King|Russell Marsh| |Alyx Ranas|Naters305 |ChrisakaMrXD |Nic |sean| |Zach Hagedorn|Jhon lenon|Everett Butts|Kyan|ranger Nation| |Hiago Oliveira|Texi|MrRhody|Inglorious Bastard|Marty Mitchell| |Justin Chao|ManuelS|Cornelius Rössing|Pedro Freire|Anthony Stuart| |Hubba Nubba|Skychii|Joe Underwood|Xander_|Notbigdank| |Sander D.|Lars Joosten|Danksa|Metrostation |Myles Wommack| |Derrick Sampson|Corey Hannen|Matteo La Corte|Octothorp Obelus|David Baril| |Soyuz Kafire|Ivan Coha|BigElbowski|Apolepth|Julian Krad| |David Soulieres|Eric Huang|Léo Vias|Riccardo Mariani|Vic Viper| |Shinkaaaa|Mumaransa |Michael Banovsky|Hendrik Voss|Inverted Blackhat| |skipyC |Tobias Moor|jaky3 .|Clément LEGRAND|Ian C. Simpson| |Challier|Jan Przemysław Drabik|Dsand23|Smooth DLX|The German Dude| |CrazyEagle |Jordon Goodman|HenryWithaG .|Oscar Krula|Brayden Moore| |Steven|Nall Wolfert|papajonk|Andrew|Ben Kingston| |Julian Vogl|Maxime Lubrano|MrMekouil|Doudimme|Jacob Hultberg| |Nolan Orloff|Mike|tobi9899 |Danila Frolkin|Xecotcovach| |Aj|Carcar404|John Martin|Dominik Greinert|Lukas Stadler| |Oliver Yang|sonax51|Marcel Kliment|Chris|David Rush| |LethalVenom13|Dave Osterhoff|Anto1709|Ben|Morgan Munroe| |Ivor Forrest|Sam Hopkins|Atte |Dax |William Bergström| |homelessmeme|Thanleft|Zaxerg |Robeloox|Maximilian-Lukas Marz| |Morgn|Seth Monteleone|playfulmean videos|Lanimations LA|Bram G| |Benoit Fournier|Bernar Lepiller|Nicolas Baur|the |Snekers| |Darkmount|Tobiasz Michalik|Aidas Ri|Daniel Postler|Skim_Beeblez| |Impetus|Thunderbird324|Fred Joss|Krzysztof Radowski|Azerrty| |Harrison Speck|Matt Baker|BigLynch|Markus Pelto|IMBIBE| |James L Plummer|Rose Giles|Jonas Brekka|HASTRX|Lepoucehumain| |Naomi Humin|qkrrudgks|Johann Gross|Janis Knappich|WhatTheDuck| |테루|Glimple Bort|Jacob Tudisco|Tanner|Julian kaspi| |nathan gould|Randal Rainis Kruus|Beppierre|Craig Martin|Thomas Bukovsky| |Colaxe|Robert Oram|Matsuy15 L|Kacpe|Alex Sedlic| |Mark Benson|Mhenn!|Anders Nelson|Dingus|Rustle| |Marco Schulz|stratum |brochier gabriel|Thomas|brody of hillcountry| |Thomas Afford|Brody Blaskie|Martien Gaming|Adrien MC|William A Grubbs| |Trevo Ph.D.|Donovan Gibson|Polish R3t4rd|Keith Price|LAWL CAKE| |Rhien Schultz|FireThrow13|Seraphim|Titus Standing|Matt Miklos| |B Dub|Jonathan Ekman|Al Pomeroy|Vestii|Wil| |adrian|Airatise|TJ Sinkoski|Shotts SilverStone|Reagan Carbaugh| |WarAestheticsRebooted|Aidan Case|Casey Bryant Goodwin|Konrad|Adam Larcher| |Kazar Xin Xiao|Riccardo Marcaccio|William S.|Francis Filion|Loïc | |Kenny Deane|Blackspots|mike |MXT|Joshua Gibson| |milky boi|Hagen|gunmaster929 |jgvan |Benny 282| |Sean Wehner|Christian Poole|Ethan|Tsukiyama Shuu|Ooof_uhhh_haah| |sano ken ch|Diego Martinez|Chuck|GalaxyFrogs|TheGeForce | |Chriphost|Carthage|Greg L|Chipskate|Muhammed Mehmood| |Hamilton Sjoberg|Amina Moh|vSiiFT|Jeremy Wren|Esteban Acosta| |John A Ullenberg|Michael Morozov|Andrew Webberley|Nathaniel Lim|Aaron Ksicinski| |Apocalypt|Josh batuzich|Ed|Hunter |Gene Brockoff| |Redheadspellslinger.|Pablo Magariños|Nilz|Jose Manuel Silva Calvo|AJ| |Ethan Wille |Aurora|Derek Shunia|Jan|Nope Mircea| |Giancarlo Cestari|Tanner Edge|brad.|Connor Merrick|Martin Scholer| |Deppy|Dan Smith|Tyson |Jac Comeau|Itemfinder | |Tischer Games|Pedro Henrique|BeenWashedUp|martin wolff|Kurt Houben| |Thomas Onslow|Brendan Puglisi|Kai Anquetil|Rudolph Ignatenko|CloudHackIX| |Zach Carreau|Jonathan Vanderlyn|Krobivnov|ienergy|Leifster| |Mikael Kaaronen|Glen|H.Helsing|ange|The Nobles| |Johnathan Johnson|Juha Merentie|Jim Fares|Tom Marshall|Superferrariman| |Zakary Zisa|JustTy|晟道 杜|Dnialibr Williams|Takumi Fujiwara| |Koen van Hal|Jonathan Hill|Marco Siciliano|Kevindosenfutter|Angry Prawn| |Natharic 67|Rafael Monteiro|Jacob Ashline|ChironTheFloof|Caleb Dauphinee| |Tony |Zac L|AlainMoto FPV|eirik johan johnsen|Elderet| |Miles Longmore|lemon head|Viccy|Casey|Kajetan Cupa| |Conejero00|Bill Gricko|A cow wearing a turban|Danni Nowicki|Udo Schmidt| |Tyler Swords|Constellation Gaming|Manimo|valentine|Jules Schattenberg| |Brandon Crotts|Philipp Popetschnigg|Tiziano Della Fazia|goodgamer1109|Joshua Thomas| |Jeff Testa|Avery Snyder|Josh Kern|Triptagram|Bayon Antoine| |Iván Juárez Núñez|Amery Martinat|ElArGee|Cory Green|lucas Di lorenzo| |Caleb Sandersier||||| ================================================ FILE: assets/engines/atg-video-1/01_honda_trx520.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); } public node honda_trx520 { alias output __out: engine; engine engine( name: "Honda TRX520 (ATV)", starter_torque: 50 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 5000 * units.rpm, fuel: fuel( max_burning_efficiency: 1.0 ), hf_gain: 0.00121, noise: 0.229, jitter: 0.42, simulation_frequency: 40000 ) wires wires() crankshaft c0( throw: 71.5 * units.mm / 2, flywheel_mass: 5 * units.lb, mass: 5 * units.lb, friction_torque: 5.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.2, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 2 ) rod_journal rj0(angle: 0.0) c0 .add_rod_journal(rj0) piston_parameters piston_params( mass: 100 * units.g, compression_height: 1.0 * units.inch, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 100.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: 6.0 * units.inch ) cylinder_bank_parameters bank_params( bore: 96 * units.mm, deck_height: 71.5 * units.mm / 2 + 6.0 * units.inch + 1.0 * units.inch ) intake intake( plenum_volume: 1.5 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(100.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.993, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(500.0), primary_tube_length: 20.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 0.5, volume: 5.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank b0(bank_params, angle: 0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 180 * units.deg, gamma: 1.0, lift: 200 * units.thou, steps: 100 ) vtwin90_camshaft_builder camshaft( lobe_profile: lobe, lobe_separation: 100 * units.deg, base_radius: 500 * units.thou ) b0.set_cylinder_head ( generic_small_engine_head( chamber_volume: 60 * units.cc, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flow_attenuation: 2.0, intake_runner_cross_section_area: 9.0 * units.cm2, exhaust_runner_cross_section_area: 9.0 * units.cm2 ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 35 * units.deg) .add_sample(4000 * units.rpm, 35 * units.deg) engine.add_ignition_module( single_cylinder_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 6000 * units.rpm )) } private node honda_trx520_transmission { alias output __out: transmission; transmission transmission( max_clutch_torque: 50 * units.lb_ft ) transmission.add_gear(4.0) transmission.add_gear(3.5) transmission.add_gear(3.0) transmission.add_gear(2.5) transmission.add_gear(2.0) } private node honda_trx520_vehicle { alias output __out: vehicle( mass: 500 * units.kg, drag_coefficient: 0.25, cross_sectional_area: (47 * units.inch) * (47 * units.inch), diff_ratio: 3.33, tire_radius: 11 * units.inch, rolling_resistance: 200 * units.N ); } public node main { set_engine(honda_trx520()) set_transmission(honda_trx520_transmission()) set_vehicle(honda_trx520_vehicle()) } ================================================ FILE: assets/engines/atg-video-1/02_kohler_ch750.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); } public node kohler_ch750 { alias output __out: engine; engine engine( name: "Kohler CH750", starter_torque: 50 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 3600 * units.rpm, throttle: governor( min_speed: 1600 * units.rpm, max_speed: 3500 * units.rpm, min_v: -5.0, max_v: 5.0, k_s: 0.0006, k_d: 200.0, gamma: 2.0 ), hf_gain: 0.01, noise: 1.0, jitter: 0.5, simulation_frequency: 30000 ) wires wires() crankshaft c0( throw: 69 * units.mm / 2, flywheel_mass: 5 * units.lb, mass: 5 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.5, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 4 ) rod_journal rj0(angle: 0.0) c0 .add_rod_journal(rj0) piston_parameters piston_params( mass: 400 * units.g, //blowby: k_28inH2O(0.1), compression_height: 1.0 * units.inch, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 300.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: 4.0 * units.inch ) cylinder_bank_parameters bank_params( bore: 83 * units.mm, deck_height: (4.0 + 1) * units.inch + 69 * units.mm / 2 ) intake intake( plenum_volume: 1.0 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(50.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.96 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(300.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 1.0, volume: 20.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0 ) cylinder_bank b0(bank_params, angle: -45 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) cylinder_bank b1(bank_params, angle: 45.0 * units.deg) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2 ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 160 * units.deg, gamma: 1.1, lift: 200 * units.thou, steps: 100 ) vtwin90_camshaft_builder camshaft( lobe_profile: lobe, lobe_separation: 114 * units.deg, base_radius: 500 * units.thou ) b0.set_cylinder_head ( generic_small_engine_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0 ) ) b1.set_cylinder_head ( generic_small_engine_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: true ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 18 * units.deg) .add_sample(1000 * units.rpm, 30 * units.deg) .add_sample(2000 * units.rpm, 50 * units.deg) .add_sample(3000 * units.rpm, 50 * units.deg) .add_sample(4000 * units.rpm, 50 * units.deg) engine.add_ignition_module( vtwin90_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 5000 * units.rpm )) } private node direct_drive { alias output __out: transmission; transmission transmission( max_clutch_torque: 35 * units.lb_ft ) transmission.add_gear(1.0) } private node static_load { alias output __out: vehicle( mass: 563 * units.lb, drag_coefficient: 0.1, cross_sectional_area: (20 * units.inch) * (47 * units.inch), diff_ratio: 2.353, tire_radius: 8.5 * units.inch, rolling_resistance: 10000 * units.N ); } public node main { set_engine(kohler_ch750()) set_transmission(direct_drive()) set_vehicle(static_load()) } ================================================ FILE: assets/engines/atg-video-1/03_harley_davidson_shovelhead.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); } public node harley_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, 0 * units.deg) .connect_wire(wires.wire2, 315 * units.deg); } public node harley_davidson_shovelhead { alias output __out: engine; engine engine( name: "Harley Davidson Shovelhead", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 5000 * units.rpm, hf_gain: 0.01, noise: 0.115, jitter: 0.136, simulation_frequency: 35000 ) wires wires() label stroke(4.25 * units.inch) label bore(3.5 * units.inch) label rod_length(8 * units.inch) label compression_height(1.0 * units.inch) label crank_mass(9.39 * units.kg) label flywheel_mass(15 * units.kg) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 3.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 5.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: (90 - 0.5 * 45) * units.deg ) rod_journal rj0(angle: 0.0) c0 .add_rod_journal(rj0) piston_parameters piston_params( mass: 500 * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 500.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.5 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(100.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.991, throttle_gamma: 1.0, velocity_decay: 1.0 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(100.0), primary_tube_length: 70.0 * units.inch, primary_flow_rate: k_carb(100.0), velocity_decay: 0.75, volume: 10.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0 * 0.1, impulse_response: ir_lib.minimal_muffling_01 ) exhaust_system exhaust1( es_params, audio_volume: 2.0 * 0.1, impulse_response: ir_lib.minimal_muffling_01 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: -0.5 * 45 * units.deg, display_depth: 0.55) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) cylinder_bank b1(bank_params, angle: 0.5 * 45 * units.deg, display_depth: 0.55) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2 ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 210 * units.deg, gamma: 0.9, lift: 400 * units.thou, steps: 100 ) vtwin_camshaft_builder camshaft( lobe_profile: lobe, lobe_separation: 110 * units.deg, base_radius: 500 * units.thou, angle: 315 * units.deg ) b0.set_cylinder_head ( generic_small_engine_head( chamber_volume: 100 * units.cc, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flow_attenuation: 2.0, intake_runner_cross_section_area: 20.0 * units.cm2, exhaust_runner_cross_section_area: 20.0 * units.cm2 ) ) b1.set_cylinder_head ( generic_small_engine_head( flip_display: true, chamber_volume: 100 * units.cc, intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flow_attenuation: 1.0, intake_runner_cross_section_area: 20.0 * units.cm2, exhaust_runner_cross_section_area: 20.0 * units.cm2 ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 18 * units.deg) .add_sample(1000 * units.rpm, 18 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) engine.add_ignition_module( harley_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 5500 * units.rpm )) } private node harley_davidson_motorcyle { alias output __out: vehicle( mass: 900 * units.lb, drag_coefficient: 0.1, cross_sectional_area: (15 * units.inch) * (47 * units.inch), diff_ratio: 2.0, tire_radius: 11 * units.inch, rolling_resistance: 200 * units.N ); } private node harley_davidson_transmission { alias output __out: transmission( max_clutch_torque: 200 * units.lb_ft ) .add_gear(3.34) .add_gear(2.30) .add_gear(1.71) .add_gear(1.41) .add_gear(1.18) .add_gear(1.00); } public node main { set_engine(harley_davidson_shovelhead()) set_vehicle(harley_davidson_motorcyle()) set_transmission(harley_davidson_transmission()) } ================================================ FILE: assets/engines/atg-video-1/04_hayabusa.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); } private node hayabusa_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 19.2 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 10.0 * units.cm2 * 2.0; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 15.0 * units.cm2 * 2.0; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 40 * flow_attenuation) .add_flow_sample(100 * lift_scale, 80 * flow_attenuation) .add_flow_sample(150 * lift_scale, 125 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 190 * flow_attenuation) .add_flow_sample(300 * lift_scale, 210 * flow_attenuation) .add_flow_sample(350 * lift_scale, 225 * flow_attenuation) .add_flow_sample(400 * lift_scale, 230 * flow_attenuation) .add_flow_sample(450 * lift_scale, 240 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 30 * flow_attenuation) .add_flow_sample(100 * lift_scale, 70 * flow_attenuation) .add_flow_sample(150 * lift_scale, 100 * flow_attenuation) .add_flow_sample(200 * lift_scale, 125 * flow_attenuation) .add_flow_sample(250 * lift_scale, 140 * flow_attenuation) .add_flow_sample(300 * lift_scale, 150 * flow_attenuation) .add_flow_sample(350 * lift_scale, 160 * flow_attenuation) .add_flow_sample(400 * lift_scale, 165 * flow_attenuation) .add_flow_sample(450 * lift_scale, 170 * flow_attenuation) cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } private node hayabusa_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle) } public node hayabusa_i4 { alias output __out: engine; engine engine( name: "Suzuki Hayabusa I4", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 11000 * units.rpm, fuel: fuel( max_turbulence_effect: 5.5, max_dilution_effect: 1000.0, max_burning_efficiency: 1.0, burning_efficiency_randomness: 0.0 ), throttle_gamma: 2.0, hf_gain: 0.00407, noise: 0.292, jitter: 0.062, simulation_frequency: 20000 ) wires wires() label stroke(65 * units.mm) label bore(81 * units.mm) label rod_length(4.705 * units.inch) label compression_height(1.0 * units.inch) crankshaft c0( throw: stroke / 2, flywheel_mass: 10 * units.lb, mass: 24.8 * units.lb, friction_torque: 1.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.2, position_x: 0.0, position_y: 0.0, tdc: 90 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: 180.0 * units.deg) rod_journal rj2(angle: 180.0 * units.deg) rod_journal rj3(angle: 0.0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: 303.5 * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 395.837 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 4.5 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(800.0), runner_flow_rate: k_carb(300.0), runner_length: 10.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.999, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 40.0 * units.inch, primary_flow_rate: k_carb(500.0), velocity_decay: 1.0, volume: 10.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0 * 0.25, impulse_response: ir_lib.minimal_muffling_03 ) exhaust_system exhaust1( es_params, audio_volume: 2.0 * 0.25, impulse_response: ir_lib.minimal_muffling_03 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: 0.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 0.8 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 1.1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 0.9 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 240 * units.deg, gamma: 1.2, lift: 345 * units.thou, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 220 * units.deg, gamma: 1.2, lift: 294 * units.thou, steps: 100 ) hayabusa_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 105 * units.deg, exhaust_lobe_center: 100 * units.deg, base_radius: 500 * units.thou ) b0.set_cylinder_head ( hayabusa_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0 ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 25 * units.deg) .add_sample(1000 * units.rpm, 25 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 11500 * units.rpm, limiter_duration: 0.05) ignition_module .connect_wire(wires.wire1, (0.0 / 4.0) * cycle) .connect_wire(wires.wire2, (1.0 / 4.0) * cycle) .connect_wire(wires.wire4, (2.0 / 4.0) * cycle) .connect_wire(wires.wire3, (3.0 / 4.0) * cycle) engine.add_ignition_module(ignition_module) } private node hayabusa_transmission { alias output __out: transmission; transmission transmission( max_clutch_torque: 200 * units.lb_ft ) transmission.add_gear(2.615) transmission.add_gear(1.937) transmission.add_gear(1.526) transmission.add_gear(1.285) transmission.add_gear(1.136) transmission.add_gear(1.043) } private node hayabusa { alias output __out: vehicle( mass: 563 * units.lb, drag_coefficient: 0.1, cross_sectional_area: (20 * units.inch) * (47 * units.inch), diff_ratio: 2.353, tire_radius: 8.5 * units.inch, rolling_resistance: 100 * units.N ); } public node main { set_engine(hayabusa_i4()) set_transmission(hayabusa_transmission()) set_vehicle(hayabusa()) } ================================================ FILE: assets/engines/atg-video-1/05_honda_vtec.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); } private node honda_vtec_head { input camshaft_set; input chamber_volume: 41.6 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 50 * flow_attenuation) .add_flow_sample(100 * lift_scale, 80 * flow_attenuation) .add_flow_sample(150 * lift_scale, 125 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 190 * flow_attenuation) .add_flow_sample(300 * lift_scale, 210 * flow_attenuation) .add_flow_sample(350 * lift_scale, 225 * flow_attenuation) .add_flow_sample(400 * lift_scale, 230 * flow_attenuation) .add_flow_sample(450 * lift_scale, 250 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 50 * flow_attenuation) .add_flow_sample(100 * lift_scale, 80 * flow_attenuation) .add_flow_sample(150 * lift_scale, 110 * flow_attenuation) .add_flow_sample(200 * lift_scale, 130 * flow_attenuation) .add_flow_sample(250 * lift_scale, 150 * flow_attenuation) .add_flow_sample(300 * lift_scale, 160 * flow_attenuation) .add_flow_sample(350 * lift_scale, 170 * flow_attenuation) .add_flow_sample(400 * lift_scale, 170 * flow_attenuation) .add_flow_sample(450 * lift_scale, 170 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: vtec_valvetrain( vtec_intake_camshaft: camshaft_set.vtec_intake_cam, vtec_exhaust_camshaft: camshaft_set.vtec_exhaust_cam, intake_camshaft: camshaft_set.intake_cam, exhaust_camshaft: camshaft_set.exhaust_cam ), flip_display: flip_display ) } private node honda_vtec_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input vtec_lobe_profile; input vtec_intake_lobe_profile: vtec_lobe_profile; input vtec_exhaust_lobe_profile: vtec_lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input vtec_lobe_separation: 110 * units.deg; input vtec_intake_lobe_center: vtec_lobe_separation; input vtec_exhaust_lobe_center: vtec_lobe_separation; input advance: 0 * units.deg; input base_radius: 1.0 * units.inch; output intake_cam: _intake_cam; output exhaust_cam: _exhaust_cam; output vtec_intake_cam: _vtec_intake_cam; output vtec_exhaust_cam: _vtec_exhaust_cam; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile) camshaft _vtec_intake_cam(params, lobe_profile: vtec_intake_lobe_profile) camshaft _vtec_exhaust_cam(params, lobe_profile: vtec_exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam .add_lobe(rot360 + exhaust_lobe_center - (0.0 / 4) * cycle) .add_lobe(rot360 + exhaust_lobe_center - (3.0 / 4) * cycle) .add_lobe(rot360 + exhaust_lobe_center - (1.0 / 4) * cycle) .add_lobe(rot360 + exhaust_lobe_center - (2.0 / 4) * cycle) _intake_cam .add_lobe(rot360 - intake_lobe_center - (0.0 / 4) * cycle) .add_lobe(rot360 - intake_lobe_center - (3.0 / 4) * cycle) .add_lobe(rot360 - intake_lobe_center - (1.0 / 4) * cycle) .add_lobe(rot360 - intake_lobe_center - (2.0 / 4) * cycle) _vtec_exhaust_cam .add_lobe(rot360 + vtec_exhaust_lobe_center - (0.0 / 4) * cycle) .add_lobe(rot360 + vtec_exhaust_lobe_center - (3.0 / 4) * cycle) .add_lobe(rot360 + vtec_exhaust_lobe_center - (1.0 / 4) * cycle) .add_lobe(rot360 + vtec_exhaust_lobe_center - (2.0 / 4) * cycle) _vtec_intake_cam .add_lobe(rot360 - vtec_intake_lobe_center - (0.0 / 4) * cycle) .add_lobe(rot360 - vtec_intake_lobe_center - (3.0 / 4) * cycle) .add_lobe(rot360 - vtec_intake_lobe_center - (1.0 / 4) * cycle) .add_lobe(rot360 - vtec_intake_lobe_center - (2.0 / 4) * cycle) } public node honda_vtec_i4 { alias output __out: engine; engine engine( name: "Honda B18C5 [VTEC, I4]", starter_torque: 70 * units.lb_ft, starter_speed: -500 * units.rpm, redline: 8400 * units.rpm, fuel: fuel( max_turbulence_effect: 2.5, max_burning_efficiency: 0.75 ), throttle_gamma: 2.0, hf_gain: 0.002, noise: 0.253, jitter: 0.195, simulation_frequency: 20000 ) wires wires() label stroke(87.2 * units.mm) label bore(81 * units.mm) label rod_length(5.430 * units.inch) label compression_height(1.0 * units.inch) crankshaft c0( throw: stroke / 2, flywheel_mass: 10 * units.lb, mass: 35.5 * units.lb, friction_torque: 1.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.5, position_x: 0.0, position_y: 0.0, tdc: 90 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: 180.0 * units.deg) rod_journal rj2(angle: 180.0 * units.deg) rod_journal rj3(angle: 0.0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: 303.5 * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 395.837 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(800.0), runner_flow_rate: k_carb(250.0), runner_length: 7.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.9989, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 1.0, volume: 100.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 8 * 0.75, impulse_response: ir_lib.mild_exhaust_0 ) exhaust_system exhaust1( es_params, audio_volume: 8 * 1.0, impulse_response: ir_lib.mild_exhaust_0 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: 0.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 1.1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 0.8 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 0.9 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 210 * units.deg, gamma: 1.0, lift: 6.9 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 190 * units.deg, gamma: 1.0, lift: 6.5 * units.mm, steps: 100 ) harmonic_cam_lobe vtec_intake_lobe( duration_at_50_thou: 240 * units.deg, gamma: 0.5, lift: 11.5 * units.mm, steps: 100 ) harmonic_cam_lobe vtec_exhaust_lobe( duration_at_50_thou: 232 * units.deg, gamma: 0.5, lift: 10.5 * units.mm, steps: 100 ) honda_vtec_camshaft camshaft( lobe_profile: "N/A", vtec_lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, vtec_intake_lobe_profile: vtec_intake_lobe, vtec_exhaust_lobe_profile: vtec_exhaust_lobe, intake_lobe_center: 116 * units.deg, exhaust_lobe_center: 116 * units.deg, vtec_intake_lobe_center: 100 * units.deg, vtec_exhaust_lobe_center: 100 * units.deg, base_radius: 500 * units.thou ) b0.set_cylinder_head ( honda_vtec_head( camshaft_set: camshaft ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, -25 * units.deg) .add_sample(1000 * units.rpm, -25 * units.deg) .add_sample(2000 * units.rpm, -30 * units.deg) .add_sample(3000 * units.rpm, -40 * units.deg) .add_sample(4000 * units.rpm, -40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 9400 * units.rpm, //43244374 limiter_duration: 0.05) ignition_module .connect_wire(wires.wire1, (0.0 / 4.0) * cycle) .connect_wire(wires.wire3, (3.0 / 4.0) * cycle) .connect_wire(wires.wire4, (2.0 / 4.0) * cycle) .connect_wire(wires.wire2, (1.0 / 4.0) * cycle) engine.add_ignition_module(ignition_module) } private node integra_type_r { alias output __out: vehicle( mass: 2400 * units.lb, drag_coefficient: 0.2, cross_sectional_area: (66 * units.inch) * (50 * units.inch), diff_ratio: 3.55, tire_radius: 10 * units.inch, rolling_resistance: 300 * units.N ); } private node integra_type_r_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.23) .add_gear(2.105) .add_gear(1.458) .add_gear(1.107) .add_gear(0.848); } public node main { set_engine(honda_vtec_i4()) set_vehicle(integra_type_r()) set_transmission(integra_type_r_transmission()) } ================================================ FILE: assets/engines/atg-video-1/06_subaru_ej25.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); } private node ej25_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 67 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node ej25_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 1.0 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle) } public node subaru_ej25 { alias output __out: engine; engine engine( name: "Subaru EJ25", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 6500 * units.rpm, fuel: fuel( max_turbulence_effect: 2.5, max_burning_efficiency: 0.75 ), throttle_gamma: 2.0, hf_gain: 0.01, noise: 1.0, jitter: 0.5, simulation_frequency: 20000 ) wires wires() label stroke(79 * units.mm) label bore(99.5 * units.mm) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(1.0 * units.inch) label crank_mass(9.39 * units.kg) label flywheel_mass(6.8 * units.kg) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 6.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 1.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 180 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: 180.0 * units.deg) rod_journal rj2(angle: 0.0 * units.deg) rod_journal rj3(angle: 180.0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(800.0), runner_flow_rate: k_carb(250.0), runner_length: 12.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.9985, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 1.0, volume: 100.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 0.5 * 8, impulse_response: ir_lib.mild_exhaust_0 ) exhaust_system exhaust1( es_params, audio_volume: 1.0 * 8, impulse_response: ir_lib.mild_exhaust_0 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: 90.0 * units.deg) cylinder_bank b1(bank_params, angle: -90.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3 ) .set_cylinder_head( ej25_head( flip_display: true, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4 ) .set_cylinder_head( ej25_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 232 * units.deg, gamma: 2.0, lift: 9.78 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 236 * units.deg, gamma: 2.0, lift: 9.60 * units.mm, steps: 100 ) ej25_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 117 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: (34.0 / 2) * units.mm ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 25 * units.deg) .add_sample(1000 * units.rpm, 25 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 6500 * units.rpm, limiter_duration: 0.08) ignition_module .connect_wire(wires.wire1, (0.0 / 4.0) * cycle) .connect_wire(wires.wire3, (1.0 / 4.0) * cycle) .connect_wire(wires.wire2, (2.0 / 4.0) * cycle) .connect_wire(wires.wire4, (3.0 / 4.0) * cycle) engine.add_ignition_module(ignition_module) } private node impreza { alias output __out: vehicle( mass: 2700 * units.lb, drag_coefficient: 0.2, cross_sectional_area: (66 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 300 * units.N ); } private node impreza_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.636) .add_gear(2.375) .add_gear(1.761) .add_gear(1.346) .add_gear(0.971) .add_gear(0.756); } public node main { set_engine(subaru_ej25()) set_vehicle(impreza()) set_transmission(impreza_transmission()) } ================================================ FILE: assets/engines/atg-video-1/07_audi_i5.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); } label cycle(2 * 360 * units.deg) public node wb_ignition { input wires; input timing_curve; input rev_limit: 7500 * units.rpm; alias output __out: ignition_module( timing_curve: timing_curve, rev_limit: rev_limit, limiter_duration: 0.1 ) .connect_wire(wires.wire1, (0.0 / 5.0) * cycle) .connect_wire(wires.wire2, (1.0 / 5.0) * cycle) .connect_wire(wires.wire4, (2.0 / 5.0) * cycle) .connect_wire(wires.wire5, (3.0 / 5.0) * cycle) .connect_wire(wires.wire3, (4.0 / 5.0) * cycle); } public node i5_camshaft_builder { input lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(); input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 110.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam: _intake_cam; output exhaust_cam: _exhaust_cam; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile) label rot(2 * (360 / 5.0) * units.deg) label rot360(360 * units.deg) _exhaust_cam .add_lobe(rot360 - exhaust_lobe_center) .add_lobe((rot360 - exhaust_lobe_center) + 1 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 4 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 2 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 3 * rot) _intake_cam .add_lobe(rot360 + intake_lobe_center) .add_lobe(rot360 + intake_lobe_center + 1 * rot) .add_lobe(rot360 + intake_lobe_center + 4 * rot) .add_lobe(rot360 + intake_lobe_center + 2 * rot) .add_lobe(rot360 + intake_lobe_center + 3 * rot) } private node audi_i5_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 50 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.9 * units.inch * 1.9 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } public node audi_i5_2_2L { alias output __out: engine; wires wires() engine engine( name: "Audi 2.3 inline 5", starter_torque: 200 * units.lb_ft, redline: 6000 * units.rpm, fuel: fuel( max_turbulence_effect: 2.5, max_burning_efficiency: 0.75), hf_gain: 0.01, noise: 1.0, jitter: 0.299, simulation_frequency: 17000 ) label stroke(79.5 * units.mm) label bore(86.4 * units.mm) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(32.8 * units.mm) label crank_mass(9.39 * units.kg) label flywheel_mass(6.8 * units.kg) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 20 * units.kg, radius: 8.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 5.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 2 ) rod_journal rj0(angle: (0.0 / 5.0) * 360 * units.deg) rod_journal rj1(angle: (2.0 / 5.0) * 360 * units.deg) rod_journal rj2(angle: (3.0 / 5.0) * 360 * units.deg) rod_journal rj3(angle: (4.0 / 5.0) * 360 * units.deg) rod_journal rj4(angle: (1.0 / 5.0) * 360 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight blowby: 0, compression_height: compression_height, wrist_pin_position: 0 * units.mm, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) intake intake( plenum_volume: 1.0 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(350.0), runner_flow_rate: k_carb(175.0), runner_length: 5.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.993 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(500.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(100.0), velocity_decay: 1.0, volume: 50.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0) exhaust_system exhaust1( es_params, audio_volume: 0.8, impulse_response: ir_lib.mild_exhaust_0) cylinder_bank b0(bank_params, angle: 0) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 0.8 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 1.0 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5, sound_attenuation: 1.1 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 210 * units.deg, gamma: 2.0, lift: 9.78 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 215 * units.deg, gamma: 2.0, lift: 9.60 * units.mm, steps: 100 ) i5_camshaft_builder camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 116 * units.deg, exhaust_lobe_center: 116 * units.deg, base_radius: (34.0 / 2) * units.mm ) b0.set_cylinder_head ( audi_i5_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam, exhaust_camshaft: camshaft.exhaust_cam, flow_attenuation: 0.9 ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 26 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) .add_sample(5000 * units.rpm, 34 * units.deg) .add_sample(6000 * units.rpm, 38 * units.deg) .add_sample(7000 * units.rpm, 38 * units.deg) engine.add_ignition_module( wb_ignition( wires: wires, timing_curve: timing_curve, rev_limit: 6500 * units.rpm ) ) } private node audi_vehicle { alias output __out: vehicle( mass: 2844 * units.lb, drag_coefficient: 0.3, cross_sectional_area: (66 * units.inch) * (50 * units.inch), diff_ratio: 3.55, tire_radius: 10 * units.inch, rolling_resistance: 500 * units.N ); } private node audi_transmission { alias output __out: transmission( max_clutch_torque: 200 * units.lb_ft ) .add_gear(3.417) .add_gear(2.105) .add_gear(1.429) .add_gear(1.088) .add_gear(0.970) .add_gear(0.912); } public node main { set_engine(audi_i5_2_2L()) set_vehicle(audi_vehicle()) set_transmission(audi_transmission()) } ================================================ FILE: assets/engines/atg-video-1/08_radial_5.mr ================================================ import "engine_sim.mr" import "radial.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); } label cycle(2 * 360 * units.deg) public node radial_5_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module( timing_curve: timing_curve, rev_limit: rev_limit, limiter_duration: 0.2 ) .connect_wire(wires.wire1, (0 / 5.0) * cycle) .connect_wire(wires.wire3, (1 / 5.0) * cycle) .connect_wire(wires.wire5, (2 / 5.0) * cycle) .connect_wire(wires.wire2, (3 / 5.0) * cycle) .connect_wire(wires.wire4, (4 / 5.0) * cycle); } public node radial_5 { alias output __out: engine; engine engine( name: "Radial 5", starter_torque: 150 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 3000 * units.rpm, throttle_gamma: 2.0, hf_gain: 0.00121, noise: 0.623, jitter: 0.042, simulation_frequency: 12000 ) wires wires() label slave_throw(2.9 * units.inch) label stroke(5.5 * units.inch) label bore(5 * units.inch) label rod_length(12 * units.inch) label compression_height(1.0 * units.inch) label rod_mass(535 * units.g) label crank_mass(20.39 * units.kg) label flywheel_mass(100 * units.kg) label flywheel_radius(5 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 2.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: 10 * units.lb, mass: 10 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: (90 - 0.5 * 45) * units.deg ) rod_journal rj0(angle: 0.0) c0 .add_rod_journal(rj0) piston_parameters piston_params( mass: 200 * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 100.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: rod_length - slave_throw ) intake intake( plenum_volume: 10.5 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(1000.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.995, velocity_decay: 1.0 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 70.0 * units.inch, primary_flow_rate: k_carb(300.0), velocity_decay: 0.75, volume: 10.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.minimal_muffling_01 ) exhaust_system exhaust1( es_params, audio_volume: 0.2, impulse_response: ir_lib.minimal_muffling_01 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) connecting_rod master( connecting_rod_parameters( cr_params, slave_throw: slave_throw, length: rod_length ) ) rod_journal sj0(angle: (0 / 5.0) * 360 * units.deg) rod_journal sj1(angle: (1 / 5.0) * 360 * units.deg) rod_journal sj2(angle: (2 / 5.0) * 360 * units.deg) rod_journal sj3(angle: (3 / 5.0) * 360 * units.deg) rod_journal sj4(angle: (4 / 5.0) * 360 * units.deg) master .add_slave_journal(sj0) .add_slave_journal(sj1) .add_slave_journal(sj2) .add_slave_journal(sj3) .add_slave_journal(sj4) cylinder_bank b0(bank_params, angle: (0 / 5.0) * 360 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: master, rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) cylinder_bank b1(bank_params, angle: (1 / 5.0) * 360 * units.deg) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.03)), connecting_rod: connecting_rod(cr_params), rod_journal: sj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5 ) cylinder_bank b2(bank_params, angle: (2 / 5.0) * 360 * units.deg) b2 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: sj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4 ) cylinder_bank b3(bank_params, angle: (3 / 5.0) * 360 * units.deg) b3 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: sj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire3 ) cylinder_bank b4(bank_params, angle: (4 / 5.0) * 360 * units.deg) b4 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: sj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2 ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) .add_cylinder_bank(b2) .add_cylinder_bank(b3) .add_cylinder_bank(b4) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 260 * units.deg, gamma: 0.9, lift: 800 * units.thou, steps: 100 ) b0.set_cylinder_head ( radial_head( offset: 0 / 5.0, lobe_profile: lobe ) ) b1.set_cylinder_head ( radial_head( offset: 2 / 5.0, lobe_profile: lobe ) ) b2.set_cylinder_head ( radial_head( offset: 4 / 5.0, lobe_profile: lobe ) ) b3.set_cylinder_head ( radial_head( offset: 1 / 5.0, lobe_profile: lobe ) ) b4.set_cylinder_head ( radial_head( offset: 3 / 5.0, lobe_profile: lobe ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 18 * units.deg) .add_sample(1000 * units.rpm, 18 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) engine.add_ignition_module( radial_5_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 3500 * units.rpm )) } private node propellor { alias output __out: vehicle( mass: 100 * units.lb, drag_coefficient: 0.5, cross_sectional_area: (15 * units.inch) * (47 * units.inch), diff_ratio: 1.0, tire_radius: 1.0, rolling_resistance: 300 * units.N ); } private node direct_drive { alias output __out: transmission( max_clutch_torque: 500 * units.lb_ft ) .add_gear(1.0); } public node main { set_engine(radial_5()) set_vehicle(propellor()) set_transmission(direct_drive()) } ================================================ FILE: assets/engines/atg-video-1/README.md ================================================ ## Engines from *Simulating VTEC, a radial-5 engine and more with my engine simulator (there is sound)* This folder contains all the engines from [*Simulating VTEC, a radial-5 engine and more with my engine simulator (there is sound)*](https://youtu.be/WPCMNnM6D0k). Each file contains a node called `main` and it can be used in `main.mr` as follows. ``` import "engines/atg-video-1/" main() ``` The following engines are included: 1. Honda 1-cylinder ATV engine 2. Kohler Command Pro CH750 3. Harley Davidson Shovelhead 4. Hayabusa Inline-4 5. Honda B18C5 6. Subaru EJ25 (version 1) 7. Audi Inline-5 8. Radial-5 ================================================ FILE: assets/engines/atg-video-1/radial.mr ================================================ import "engine_sim.mr" units units() constants constants() label cycle(2 * 360 * units.deg) public node radial_head { input offset; input lobe_profile; input chamber_volume: 290 * units.cc; alias output __head: generic_small_engine_head( chamber_volume: chamber_volume, intake_camshaft: camshaft.intake_cam, exhaust_camshaft: camshaft.exhaust_cam, flow_attenuation: 2.0, intake_runner_cross_section_area: 20.0 * units.cm2, exhaust_runner_cross_section_area: 20.0 * units.cm2 ); radial_camshaft camshaft( lobe_profile: lobe_profile, offset: offset ) } public node radial_camshaft { input lobe_profile; input offset; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 1.0 * units.inch; output intake_cam: _intake_cam; output exhaust_cam: _exhaust_cam; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam .add_lobe(rot360 - exhaust_lobe_center + offset * cycle) _intake_cam .add_lobe(rot360 + intake_lobe_center + offset * cycle) } ================================================ FILE: assets/engines/atg-video-2/01_subaru_ej25_eh.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node turbulence_to_flame_speed_ratio { alias output __out: function(5.0) .add_sample(0.0, 1.0 * 3.0) .add_sample(5.0, 1.0 * 1.5 * 5.0) .add_sample(10.0, 1.0 * 1.5 * 10.0) .add_sample(15.0, 1.1 * 1.5 * 15.0) .add_sample(20.0, 1.25 * 1.5 * 20.0) .add_sample(25.0, 1.25 * 1.5 * 25.0) .add_sample(30.0, 1.25 * 1.5 * 30.0) .add_sample(35.0, 1.25 * 1.5 * 35.0) .add_sample(40.0, 1.25 * 1.5 * 40.0) .add_sample(45.0, 1.25 * 1.5 * 45.0); } private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); } private node ej25_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 67 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node ej25_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 1.0 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle) } public node subaru_ej25 { alias output __out: engine; engine engine( name: "Subaru EJ25", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 6500 * units.rpm, fuel: fuel( max_burning_efficiency: 0.9, turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio() ), throttle_gamma: 2.0, hf_gain: 0.01, noise: 1.0, jitter: 0.5, simulation_frequency: 20000 ) wires wires() label stroke(79 * units.mm) label bore(99.5 * units.mm) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(1.0 * units.inch) label crank_mass(9.39 * units.kg) label flywheel_mass(6.8 * units.kg) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) * 2 ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 6.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 1.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 180 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: 180.0 * units.deg) rod_journal rj2(angle: 0.0 * units.deg) rod_journal rj3(angle: 180.0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(400.0), runner_flow_rate: k_carb(100.0), runner_length: 12.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.9978, velocity_decay: 1.0 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 40.0 * units.inch, primary_flow_rate: k_carb(400.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, length: 500 * units.mm, audio_volume: 0.5 * 0.02, impulse_response: ir_lib.minimal_muffling_02 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: 90.0 * units.deg) cylinder_bank b1(bank_params, angle: -90.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, primary_length: 2.0 * units.inch, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, primary_length: 3.0 * units.inch, sound_attenuation: 1.0 ) .set_cylinder_head( ej25_head( flip_display: true, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2, primary_length: 3.0 * units.inch, sound_attenuation: 1.1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire4, primary_length: 5.0 * units.inch, sound_attenuation: 0.9 ) .set_cylinder_head( ej25_head( flip_display: false, intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 232 * units.deg, gamma: 2.0, lift: 9.78 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 236 * units.deg, gamma: 2.0, lift: 9.60 * units.mm, steps: 100 ) ej25_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 117 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: (34.0 / 2) * units.mm ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 25 * units.deg) .add_sample(1000 * units.rpm, 25 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 6800 * units.rpm, limiter_duration: 0.16) ignition_module .connect_wire(wires.wire1, (0.0 / 4.0) * cycle) .connect_wire(wires.wire3, (1.0 / 4.0) * cycle) .connect_wire(wires.wire2, (2.0 / 4.0) * cycle) .connect_wire(wires.wire4, (3.0 / 4.0) * cycle) engine.add_ignition_module(ignition_module) } label car_mass(2700 * units.lb) private node impreza { alias output __out: vehicle( mass: 2700 * units.lb, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 0.015 * car_mass * 9.81 ); } private node impreza_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.636) .add_gear(2.375) .add_gear(1.761) .add_gear(1.346) .add_gear(0.971) .add_gear(0.756); } public node main { set_engine(subaru_ej25()) set_vehicle(impreza()) set_transmission(impreza_transmission()) } ================================================ FILE: assets/engines/atg-video-2/02_subaru_ej25_uh.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node turbulence_to_flame_speed_ratio { alias output __out: function(5.0) .add_sample(0.0, 1.0 * 3.0) .add_sample(5.0, 1.0 * 1.5 * 5.0) .add_sample(10.0, 1.0 * 1.5 * 10.0) .add_sample(15.0, 1.1 * 1.5 * 15.0) .add_sample(20.0, 1.25 * 1.5 * 20.0) .add_sample(25.0, 1.25 * 1.5 * 25.0) .add_sample(30.0, 1.25 * 1.5 * 30.0) .add_sample(35.0, 1.25 * 1.5 * 35.0) .add_sample(40.0, 1.25 * 1.5 * 40.0) .add_sample(45.0, 1.25 * 1.5 * 45.0); } private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); } private node ej25_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 67 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node ej25_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 1.0 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (1.0 / 4) * cycle) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + (0.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (1.0 / 4) * cycle) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + (2.0 / 4) * cycle) .add_lobe(rot360 - exhaust_lobe_center + (3.0 / 4) * cycle) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + (2.0 / 4) * cycle) .add_lobe(rot360 + intake_lobe_center + (3.0 / 4) * cycle) } public node subaru_ej25 { alias output __out: engine; engine engine( name: "Subaru EJ25", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 6500 * units.rpm, fuel: fuel( max_burning_efficiency: 0.9, turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio() ), throttle_gamma: 2.0, hf_gain: 0.01, noise: 1.0, jitter: 0.5, simulation_frequency: 20000 ) wires wires() label stroke(79 * units.mm) label bore(99.5 * units.mm) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(1.0 * units.inch) label crank_mass(9.39 * units.kg) label flywheel_mass(6.8 * units.kg) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) * 2 ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 6.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 1.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 180 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: 180.0 * units.deg) rod_journal rj2(angle: 0.0 * units.deg) rod_journal rj3(angle: 180.0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(400.0), runner_flow_rate: k_carb(100.0), runner_length: 12.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.9978, velocity_decay: 1.0 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 24.0 * units.inch, primary_flow_rate: k_carb(400.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, length: 500 * units.mm, audio_volume: 0.5 * 0.02, impulse_response: ir_lib.minimal_muffling_02 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: 90.0 * units.deg) cylinder_bank b1(bank_params, angle: -90.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, primary_length: 2.0 * units.inch, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, primary_length: 3.0 * units.inch, sound_attenuation: 1.0 ) .set_cylinder_head( ej25_head( flip_display: true, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.001)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2, primary_length: 500 * units.mm, sound_attenuation: 1.1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.002)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire4, primary_length: 550 * units.mm, sound_attenuation: 0.9 ) .set_cylinder_head( ej25_head( flip_display: false, intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 232 * units.deg, gamma: 2.0, lift: 9.78 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 236 * units.deg, gamma: 2.0, lift: 9.60 * units.mm, steps: 100 ) ej25_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 117 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: (34.0 / 2) * units.mm ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 25 * units.deg) .add_sample(1000 * units.rpm, 25 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 6800 * units.rpm, limiter_duration: 0.16) ignition_module .connect_wire(wires.wire1, (0.0 / 4.0) * cycle) .connect_wire(wires.wire3, (1.0 / 4.0) * cycle) .connect_wire(wires.wire2, (2.0 / 4.0) * cycle) .connect_wire(wires.wire4, (3.0 / 4.0) * cycle) engine.add_ignition_module(ignition_module) } label car_mass(2700 * units.lb) private node impreza { alias output __out: vehicle( mass: car_mass, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 0.015 * car_mass * 9.81 ); } private node impreza_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.636) .add_gear(2.375) .add_gear(1.761) .add_gear(1.346) .add_gear(0.971) .add_gear(0.756); } public node main { set_engine(subaru_ej25()) set_vehicle(impreza()) set_transmission(impreza_transmission()) } ================================================ FILE: assets/engines/atg-video-2/03_2jz.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); } label cycle(2 * 360 * units.deg) public node wb_ignition { input wires; input timing_curve; input rev_limit: 7500 * units.rpm; alias output __out: ignition_module( timing_curve: timing_curve, rev_limit: rev_limit, limiter_duration: 0.1 ) .connect_wire(wires.wire1, (0.0 / 6.0) * cycle) .connect_wire(wires.wire5, (1.0 / 6.0) * cycle) .connect_wire(wires.wire3, (2.0 / 6.0) * cycle) .connect_wire(wires.wire6, (3.0 / 6.0) * cycle) .connect_wire(wires.wire2, (4.0 / 6.0) * cycle) .connect_wire(wires.wire4, (5.0 / 6.0) * cycle); } public node t2jz_camshaft_builder { input lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(); input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 110.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam: _intake_cam; output exhaust_cam: _exhaust_cam; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile) label rot(2 * (360 / 6.0) * units.deg) label rot360(360 * units.deg) // 1 5 3 6 2 4 _exhaust_cam .add_lobe((rot360 - exhaust_lobe_center) + 0 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 4 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 2 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 5 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 1 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 3 * rot) _intake_cam .add_lobe(rot360 + intake_lobe_center + 0 * rot) .add_lobe(rot360 + intake_lobe_center + 4 * rot) .add_lobe(rot360 + intake_lobe_center + 2 * rot) .add_lobe(rot360 + intake_lobe_center + 5 * rot) .add_lobe(rot360 + intake_lobe_center + 1 * rot) .add_lobe(rot360 + intake_lobe_center + 3 * rot) } private node t2jz_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 50 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.9 * units.inch * 1.9 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.25 * units.inch * 1.25 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } public node t2jz { alias output __out: engine; wires wires() engine engine( name: "2JZ [I6]", starter_torque: 200 * units.lb_ft, redline: 6000 * units.rpm, fuel: fuel( max_burning_efficiency: 1.0 ), hf_gain: 0.01, noise: 1.0, jitter: 0.23, simulation_frequency: 10000 ) label stroke(86.0 * units.mm) label bore(86.0 * units.mm) label rod_length(142 * units.mm) label rod_mass(500 * units.g) label compression_height(32.8 * units.mm) label crank_mass(15 * units.kg) label flywheel_mass(10 * units.kg) label flywheel_radius(7 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 20 * units.kg, radius: 8.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 5.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 2 ) // 1 5 3 6 2 4 rod_journal rj0(angle: 0 * units.deg) rod_journal rj1(angle: 480 * units.deg) rod_journal rj2(angle: 240 * units.deg) rod_journal rj3(angle: 600 * units.deg) rod_journal rj4(angle: 120 * units.deg) rod_journal rj5(angle: 360 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) .add_rod_journal(rj5) piston_parameters piston_params( mass: (200 + 50) * units.g, blowby: 0, compression_height: compression_height, wrist_pin_position: 0 * units.mm, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) intake intake( plenum_volume: 1.0 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(500.0), runner_flow_rate: k_carb(200.0), runner_length: 40.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.9965 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 40.0 * units.inch, primary_flow_rate: k_carb(400.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, length: 100.0 * units.inch, audio_volume: 0.2, impulse_response: ir_lib.mild_exhaust_0_reverb) exhaust_system exhaust1( es_params, length: 100.0 * units.inch, audio_volume: 0.2, impulse_response: ir_lib.mild_exhaust_0_reverb) label spacing(0.5 * units.inch) cylinder_bank b0(bank_params, angle: 0) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, primary_length: spacing * 5, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.05)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2, primary_length: spacing * 4, sound_attenuation: 0.95 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, primary_length: spacing * 3, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.05)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, primary_length: spacing * 3, sound_attenuation: 0.97 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5, primary_length: spacing * 4, sound_attenuation: 0.98 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.05)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, primary_length: spacing * 5, sound_attenuation: 0.93 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 220 * units.deg, gamma: 1.1, lift: 9.78 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 220 * units.deg, gamma: 1.1, lift: 9.60 * units.mm, steps: 100 ) t2jz_camshaft_builder camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 116 * units.deg, exhaust_lobe_center: 116 * units.deg, base_radius: (34.0 / 2) * units.mm ) b0.set_cylinder_head ( t2jz_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam, exhaust_camshaft: camshaft.exhaust_cam, flow_attenuation: 0.9 ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 26 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) .add_sample(5000 * units.rpm, 34 * units.deg) .add_sample(6000 * units.rpm, 38 * units.deg) .add_sample(7000 * units.rpm, 38 * units.deg) engine.add_ignition_module( wb_ignition( wires: wires, timing_curve: timing_curve, rev_limit: 6500 * units.rpm ) ) } private node supra_vehicle { alias output __out: vehicle( mass: 3400 * units.lb, drag_coefficient: 0.4, cross_sectional_area: (66 * units.inch) * (50 * units.inch), diff_ratio: 3.15, tire_radius: 10 * units.inch, rolling_resistance: 500 * units.N ); } private node supra_transmission { alias output __out: transmission( max_clutch_torque: 500 * units.lb_ft ) .add_gear(5.25) .add_gear(3.36) .add_gear(2.17) .add_gear(1.72) .add_gear(1.32) .add_gear(1.0); } public node main { set_engine(t2jz()) set_vehicle(supra_vehicle()) set_transmission(supra_transmission()) } ================================================ FILE: assets/engines/atg-video-2/04_60_degree_v6.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); } private node v6_60_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 67 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node v6_60_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) // 1 2 3 4 5 6 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * units.deg) .add_lobe(rot360 + intake_lobe_center + 240 * units.deg) .add_lobe(rot360 + intake_lobe_center + 480 * units.deg) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 120 * units.deg) .add_lobe(rot360 + intake_lobe_center + 360 * units.deg) .add_lobe(rot360 + intake_lobe_center + 600 * units.deg) } public node v6_60 { alias output __out: engine; engine engine( name: "Generic 60 deg. V6", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 5500 * units.rpm, throttle_gamma: 2.0 ) wires wires() label stroke(3.48 * units.inch) label bore(3.5 * units.inch) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(1.0 * units.inch) label crank_mass(50 * units.lb) label flywheel_mass(30 * units.lb) label flywheel_radius(7 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 1.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 20.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: (90 + 30) * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: (0 + 60) * units.deg) rod_journal rj2(angle: 240.0 * units.deg) rod_journal rj3(angle: (240.0 + 60) * units.deg) rod_journal rj4(angle: 120.0 * units.deg) rod_journal rj5(angle: (120.0 + 60) * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) .add_rod_journal(rj5) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(400.0), runner_flow_rate: k_carb(250.0), runner_length: 4.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.994, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 20.0 * units.inch, primary_flow_rate: k_carb(500.0), velocity_decay: 1.0, length: 100.0 * units.inch ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) exhaust_system exhaust1( es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(5.0 * units.inch) cylinder_bank b0(bank_params, angle: 30.0 * units.deg) cylinder_bank b1(bank_params, angle: -30.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 0.9, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 0.95, primary_length: spacing * 1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5, primary_length: spacing * 0 ) .set_cylinder_head( v6_60_head( flip_display: true, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 0.95, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 0.9, primary_length: spacing * 1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, sound_attenuation: 1.0, primary_length: spacing * 0 ) .set_cylinder_head( v6_60_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: false) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 222 * units.deg, gamma: 1.0, lift: 400 * units.thou, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 226 * units.deg, gamma: 1.0, lift: 300 * units.thou, steps: 100 ) v6_60_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 117 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: 0.75 * units.inch ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 20 * units.deg) .add_sample(2000 * units.rpm, 25 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 5600 * units.rpm, limiter_duration: 0.2) ignition_module .connect_wire(wires.wire1, 0 * units.deg) .connect_wire(wires.wire2, 120 * units.deg) .connect_wire(wires.wire3, 240 * units.deg) .connect_wire(wires.wire4, 360 * units.deg) .connect_wire(wires.wire5, 480 * units.deg) .connect_wire(wires.wire6, 600 * units.deg) engine.add_ignition_module(ignition_module) } label car_mass(2700 * units.lb) private node random_car { alias output __out: vehicle( mass: car_mass, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 0.015 * car_mass * 9.81 ); } private node random_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.636) .add_gear(2.375) .add_gear(1.761) .add_gear(1.346) .add_gear(0.971) .add_gear(0.756); } public node main { set_engine(v6_60()) set_vehicle(random_car()) set_transmission(random_transmission()) } ================================================ FILE: assets/engines/atg-video-2/05_odd_fire_v6.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); } private node odd_fire_v6_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 67 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node odd_fire_v6_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) // 1 6 5 4 3 2 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * units.deg) .add_lobe(rot360 + intake_lobe_center + 480 * units.deg) .add_lobe(rot360 + intake_lobe_center + 240 * units.deg) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 630 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 390 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 150 * units.deg) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 630 * units.deg) .add_lobe(rot360 + intake_lobe_center + 390 * units.deg) .add_lobe(rot360 + intake_lobe_center + 150 * units.deg) } public node odd_fire_v6_90 { alias output __out: engine; engine engine( name: "Generic Odd-fire V6 (Common Rod Jnl.)", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 5500 * units.rpm, fuel: fuel( //max_turbulence_effect: 2.5, //burning_efficiency_randomness: 0.5, //max_burning_efficiency: 0.75 ), throttle_gamma: 2.0 ) wires wires() label stroke(3.48 * units.inch) label bore(3.5 * units.inch) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(1.0 * units.inch) label crank_mass(50 * units.lb) label flywheel_mass(30 * units.lb) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 1.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 5.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 45 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: 120.0 * units.deg) rod_journal rj2(angle: 240.0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(400.0), runner_flow_rate: k_carb(250.0), runner_length: 4.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.995, throttle_gamma: 2.0, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 20.0 * units.inch, primary_flow_rate: k_carb(600.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, length: 100 * units.inch, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) exhaust_system exhaust1( es_params, length: 172 * units.inch, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(5.0 * units.inch) cylinder_bank b0(bank_params, angle: -45.0 * units.deg) cylinder_bank b1(bank_params, angle: 45.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 1.0, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.05)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 1.0, primary_length: spacing * 1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5, primary_length: spacing * 0 ) .set_cylinder_head( odd_fire_v6_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 1.0, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 1.0, primary_length: spacing * 1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, sound_attenuation: 1.0, primary_length: spacing * 0 ) .set_cylinder_head( odd_fire_v6_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: true) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 222 * units.deg, gamma: 1.0, lift: 400 * units.thou, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 226 * units.deg, gamma: 1.0, lift: 300 * units.thou, steps: 100 ) odd_fire_v6_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 117 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: 0.75 * units.inch ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 20 * units.deg) .add_sample(2000 * units.rpm, 25 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 5600 * units.rpm, limiter_duration: 0.2) ignition_module .connect_wire(wires.wire1, 0 * units.deg) .connect_wire(wires.wire6, 150 * units.deg) .connect_wire(wires.wire5, 240 * units.deg) .connect_wire(wires.wire4, 390 * units.deg) .connect_wire(wires.wire3, 480 * units.deg) .connect_wire(wires.wire2, 630 * units.deg) engine.add_ignition_module(ignition_module) } label car_mass(2700 * units.lb) private node random_car { alias output __out: vehicle( mass: car_mass, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 0.015 * car_mass * 9.81 ); } private node random_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.636) .add_gear(2.375) .add_gear(1.761) .add_gear(1.346) .add_gear(0.971) .add_gear(0.756); } public node main { set_engine(odd_fire_v6_90()) set_vehicle(random_car()) set_transmission(random_transmission()) } ================================================ FILE: assets/engines/atg-video-2/06_even_fire_v6.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); } private node even_fire_v6_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 67 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.35 * units.inch * 1.35 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node even_fire_v6_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) // 1 6 5 4 3 2 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * units.deg) .add_lobe(rot360 + intake_lobe_center + 480 * units.deg) .add_lobe(rot360 + intake_lobe_center + 240 * units.deg) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 600 * units.deg) .add_lobe(rot360 + intake_lobe_center + 360 * units.deg) .add_lobe(rot360 + intake_lobe_center + 120 * units.deg) } public node even_fire_v6_90 { alias output __out: engine; engine engine( name: "Generic Even-fire V6 (Split Rod Jnl.)", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 5500 * units.rpm, fuel: fuel( //max_turbulence_effect: 2.5, //burning_efficiency_randomness: 0.5, //max_burning_efficiency: 0.75 ), throttle_gamma: 2.0 ) wires wires() label stroke(3.48 * units.inch) label bore(3.5 * units.inch) label rod_length(5.142 * units.inch) label rod_mass(535 * units.g) label compression_height(1.0 * units.inch) label crank_mass(50 * units.lb) label flywheel_mass(30 * units.lb) label flywheel_radius(6 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 1.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 5.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 45 * units.deg ) rod_journal rj0(angle: 0.0 * units.deg) rod_journal rj1(angle: (0.0 - 30) * units.deg) rod_journal rj2(angle: 120.0 * units.deg) rod_journal rj3(angle: (120.0 - 30) * units.deg) rod_journal rj4(angle: 240.0 * units.deg) rod_journal rj5(angle: (240.0 - 30) * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) .add_rod_journal(rj5) piston_parameters piston_params( mass: (414 + 152) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(400.0), runner_flow_rate: k_carb(250.0), runner_length: 4.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.995, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 20.0 * units.inch, primary_flow_rate: k_carb(300.0), velocity_decay: 1.0, length: 100.0 * units.inch ) exhaust_system exhaust0( es_params, length: 100.0 * units.inch, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) exhaust_system exhaust1( es_params, length: 172.0 * units.inch, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(5.0 * units.inch) cylinder_bank b0(bank_params, angle: -45.0 * units.deg) cylinder_bank b1(bank_params, angle: 45.0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 0.8, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 0.9, primary_length: spacing * 1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5, primary_length: spacing * 0 ) .set_cylinder_head( even_fire_v6_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 0.6, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 0.3, primary_length: spacing * 1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, sound_attenuation: 1.1, primary_length: spacing * 0 ) .set_cylinder_head( even_fire_v6_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: true) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 222 * units.deg, gamma: 1.0, lift: 400 * units.thou, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 226 * units.deg, gamma: 1.0, lift: 300 * units.thou, steps: 100 ) even_fire_v6_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 117 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: 0.75 * units.inch ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 20 * units.deg) .add_sample(2000 * units.rpm, 25 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 5600 * units.rpm, limiter_duration: 0.2) ignition_module .connect_wire(wires.wire1, 0 * units.deg) .connect_wire(wires.wire6, 120 * units.deg) .connect_wire(wires.wire5, 240 * units.deg) .connect_wire(wires.wire4, 360 * units.deg) .connect_wire(wires.wire3, 480 * units.deg) .connect_wire(wires.wire2, 600 * units.deg) engine.add_ignition_module(ignition_module) } label car_mass(2700 * units.lb) private node random_car { alias output __out: vehicle( mass: car_mass, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 0.015 * car_mass * 9.81 ); } private node random_transmission { alias output __out: transmission( max_clutch_torque: 300 * units.lb_ft ) .add_gear(3.636) .add_gear(2.375) .add_gear(1.761) .add_gear(1.346) .add_gear(0.971) .add_gear(0.756); } public node main { set_engine(even_fire_v6_90()) set_vehicle(random_car()) set_transmission(random_transmission()) } ================================================ FILE: assets/engines/atg-video-2/07_gm_ls.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); } private node ls_v8_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 90 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 2.2 * units.inch * 2.2 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 1 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 1 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node ls_v8_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot(90 * units.deg) label rot360(360 * units.deg) // 1 8 7 2 6 5 4 3 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * rot) // 1 .add_lobe(rot360 - exhaust_lobe_center + 7 * rot) // 3 .add_lobe(rot360 - exhaust_lobe_center + 5 * rot) // 5 .add_lobe(rot360 - exhaust_lobe_center + 2 * rot) // 7 _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * rot) // 1 .add_lobe(rot360 + intake_lobe_center + 7 * rot) // 3 .add_lobe(rot360 + intake_lobe_center + 5 * rot) // 5 .add_lobe(rot360 + intake_lobe_center + 2 * rot) // 7 _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 3 * rot) // 2 .add_lobe(rot360 - exhaust_lobe_center + 6 * rot) // 4 .add_lobe(rot360 - exhaust_lobe_center + 4 * rot) // 6 .add_lobe(rot360 - exhaust_lobe_center + 1 * rot) // 8 _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 3 * rot) // 2 .add_lobe(rot360 + intake_lobe_center + 6 * rot) // 4 .add_lobe(rot360 + intake_lobe_center + 4 * rot) // 6 .add_lobe(rot360 + intake_lobe_center + 1 * rot) // 8 } private node turbulence_to_flame_speed_ratio { alias output __out: function(5.0) .add_sample(0.0, 3.0) .add_sample(5.0, 1.5 * 5.0) .add_sample(10.0, 1.75 * 10.0) .add_sample(15.0, 2.0 * 15.0) .add_sample(20.0, 2.0 * 20.0) .add_sample(25.0, 2.0 * 25.0) .add_sample(30.0, 2.0 * 30.0) .add_sample(35.0, 2.0 * 35.0) .add_sample(40.0, 2.0 * 40.0) .add_sample(45.0, 2.0 * 45.0); } public node ls_v8 { alias output __out: engine; engine engine( name: "GM LS", starter_torque: 200 * units.lb_ft, starter_speed: 200 * units.rpm, redline: 6500 * units.rpm, throttle_gamma: 2.0, fuel: fuel( //max_turbulence_effect: 10.0, //max_dilution_effect: 20.0,R //burning_efficiency_randomness: 0.5, max_burning_efficiency: 1.0, turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio() ), hf_gain: 0.01, noise: 1.0, jitter: 0.6, simulation_frequency: 10000 ) wires wires() label stroke(3.622 * units.inch) label bore(3.78 * units.inch) label rod_length(160 * units.mm) label rod_mass(50 * units.g) label compression_height(1.0 * units.inch) label crank_mass(60 * units.lb) label flywheel_mass(30 * units.lb) label flywheel_radius(8 * units.inch) label crank_moment( 1.5 * disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm) ) label v_angle(90 * units.deg) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 20.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 90 * units.deg - (v_angle / 2.0) ) // 1 8 7 2 6 5 4 3 rod_journal rj0(angle: 0 * units.deg) rod_journal rj1(angle: 270 * units.deg) rod_journal rj2(angle: 90 * units.deg) rod_journal rj3(angle: 180 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( // 414 - piston mass, 152 - pin weight mass: (100) * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(700.0), runner_flow_rate: k_carb(100.0), runner_length: 12.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.996, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 29.0 * units.inch, primary_flow_rate: k_carb(500.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, audio_volume: 4.0, length: 100 * units.inch, impulse_response: ir_lib.default_0 ) exhaust_system exhaust1( es_params, audio_volume: 4.0, length: 172 * units.inch, impulse_response: ir_lib.default_0 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(2 * units.inch) cylinder_bank b0(bank_params, angle: -v_angle / 2.0) cylinder_bank b1(bank_params, angle: v_angle / 2.0) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 1.0, primary_length: 3 * spacing + 2 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 1.0, primary_length: 2 * spacing + 1 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5, sound_attenuation: 1.0, primary_length: 1 * spacing + 3 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire7, sound_attenuation: 1.0, primary_length: 0 * spacing + 5 * units.cm ) .set_cylinder_head( ls_v8_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flip_display: false, flow_attenuation: 1.0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 1.0, primary_length: 3 * spacing + 1 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 1.0, primary_length: 2 * spacing + 5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, sound_attenuation: 1.0, primary_length: 1 * spacing + 7 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire8, sound_attenuation: 1.0, primary_length: 0 * spacing + 0 * units.cm ) .set_cylinder_head( ls_v8_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flow_attenuation: 1.0, flip_display: true) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 234 * units.deg, gamma: 1.1, lift: 551 * units.thou, steps: 256 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 235 * units.deg, gamma: 1.1, lift: 551 * units.thou, steps: 256 ) ls_v8_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 116 * units.deg, exhaust_lobe_center: 116 * units.deg, base_radius: 1.0 * units.inch ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) .add_sample(5000 * units.rpm, 40 * units.deg) .add_sample(6000 * units.rpm, 40 * units.deg) .add_sample(7000 * units.rpm, 40 * units.deg) .add_sample(8000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 6800 * units.rpm, limiter_duration: 0.2) ignition_module .connect_wire(wires.wire1, 0 * 90 * units.deg) .connect_wire(wires.wire8, 1 * 90 * units.deg) .connect_wire(wires.wire7, 2 * 90 * units.deg) .connect_wire(wires.wire2, 3 * 90 * units.deg) .connect_wire(wires.wire6, 4 * 90 * units.deg) .connect_wire(wires.wire5, 5 * 90 * units.deg) .connect_wire(wires.wire4, 6 * 90 * units.deg) .connect_wire(wires.wire3, 7 * 90 * units.deg) engine.add_ignition_module(ignition_module) } private node corvette { alias output __out: vehicle( mass: 1614 * units.kg, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (50 * units.inch), diff_ratio: 3.42, tire_radius: 10 * units.inch, rolling_resistance: 200 * units.N ); } private node corvette_transmission { alias output __out: transmission( max_clutch_torque: 500 * units.lb_ft ) .add_gear(2.97) .add_gear(2.07) .add_gear(1.43) .add_gear(1.00) .add_gear(0.71) .add_gear(0.57); } public node main { set_engine(ls_v8()) set_vehicle(corvette()) set_transmission(corvette_transmission()) } ================================================ FILE: assets/engines/atg-video-2/08_ferrari_f136_v8.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); } private node f136_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 90 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 2.2 * units.inch * 2.2 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node f136_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot(90 * units.deg) label rot360(360 * units.deg) // 1 5 3 7 4 8 2 6 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * rot) // 1 .add_lobe(rot360 - exhaust_lobe_center + 6 * rot) // 2 .add_lobe(rot360 - exhaust_lobe_center + 2 * rot) // 3 .add_lobe(rot360 - exhaust_lobe_center + 4 * rot) // 4 _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * rot) // 1 .add_lobe(rot360 + intake_lobe_center + 6 * rot) // 2 .add_lobe(rot360 + intake_lobe_center + 2 * rot) // 3 .add_lobe(rot360 + intake_lobe_center + 4 * rot) // 4 _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 1 * rot) // 5 .add_lobe(rot360 - exhaust_lobe_center + 7 * rot) // 6 .add_lobe(rot360 - exhaust_lobe_center + 3 * rot) // 7 .add_lobe(rot360 - exhaust_lobe_center + 5 * rot) // 8 _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 1 * rot) // 5 .add_lobe(rot360 + intake_lobe_center + 7 * rot) // 6 .add_lobe(rot360 + intake_lobe_center + 3 * rot) // 7 .add_lobe(rot360 + intake_lobe_center + 5 * rot) // 8 } private node turbulence_to_flame_speed_ratio { alias output __out: function(5.0) .add_sample(0.0, 3.0) .add_sample(5.0, 1.5 * 5.0) .add_sample(10.0, 1.75 * 10.0) .add_sample(15.0, 2.0 * 15.0) .add_sample(20.0, 2.0 * 20.0) .add_sample(25.0, 2.0 * 25.0) .add_sample(30.0, 2.0 * 30.0) .add_sample(35.0, 2.0 * 35.0) .add_sample(40.0, 2.0 * 40.0) .add_sample(45.0, 2.0 * 45.0); } public node f136_v8 { alias output __out: engine; engine engine( name: "Ferrari F136", starter_torque: 200 * units.lb_ft, starter_speed: 200 * units.rpm, redline: 9000 * units.rpm, throttle_gamma: 2.0, fuel: fuel( max_burning_efficiency: 1.0, turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio() ), hf_gain: 0.01, noise: 1.0, jitter: 0.15, simulation_frequency: 10000 ) wires wires() label stroke(81 * units.mm) label bore(94 * units.mm) label rod_length(160 * units.mm) label rod_mass(50 * units.g) label compression_height(1.0 * units.inch) label crank_mass(60 * units.lb) label flywheel_mass(30 * units.lb) label flywheel_radius(8 * units.inch) label crank_moment( 1.5 * disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm) ) label v_angle(90 * units.deg) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 20.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 90 * units.deg + (v_angle / 2.0) ) // 1 5 3 7 4 8 2 6 rod_journal rj0(angle: 0 * units.deg) rod_journal rj1(angle: 180 * units.deg) rod_journal rj2(angle: 180 * units.deg) rod_journal rj3(angle: 0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( // 414 - piston mass, 152 - pin weight mass: (100) * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(700.0), runner_flow_rate: k_carb(100.0), runner_length: 12.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.995, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 29.0 * units.inch, primary_flow_rate: k_carb(600.0), velocity_decay: 0.5 ) exhaust_system exhaust0( es_params, audio_volume: 2.0 * 0.1, length: 100 * units.inch, impulse_response: ir_lib.mild_exhaust_0_reverb ) exhaust_system exhaust1( es_params, audio_volume: 2.0 * 0.09, length: 100 * units.inch, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(5 * units.inch) cylinder_bank b0(bank_params, angle: v_angle / 2.0) cylinder_bank b1(bank_params, angle: -v_angle / 2.0) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, sound_attenuation: 0.9, primary_length: 2 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2, sound_attenuation: 0.8, primary_length: 1 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, sound_attenuation: 1.1, primary_length: 3 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire4, sound_attenuation: 1.0, primary_length: 5 * units.cm ) .set_cylinder_head( f136_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flip_display: true, flow_attenuation: 1.0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5, sound_attenuation: 1.0, primary_length: 1 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, sound_attenuation: 0.8, primary_length: 5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire7, sound_attenuation: 0.9, primary_length: 7 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire8, sound_attenuation: 0.7, primary_length: 0 * units.cm ) .set_cylinder_head( f136_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flow_attenuation: 1.0) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 230 * units.deg, gamma: 0.9, lift: 551 * units.thou, steps: 256 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 230 * units.deg, gamma: 0.9, lift: 551 * units.thou, steps: 256 ) f136_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 116 * units.deg, exhaust_lobe_center: 116 * units.deg, base_radius: 1.0 * units.inch ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) .add_sample(5000 * units.rpm, 40 * units.deg) .add_sample(6000 * units.rpm, 40 * units.deg) .add_sample(7000 * units.rpm, 40 * units.deg) .add_sample(8000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 9300 * units.rpm, limiter_duration: 0.1) ignition_module .connect_wire(wires.wire1, 0 * 90 * units.deg) .connect_wire(wires.wire5, 1 * 90 * units.deg) .connect_wire(wires.wire3, 2 * 90 * units.deg) .connect_wire(wires.wire7, 3 * 90 * units.deg) .connect_wire(wires.wire4, 4 * 90 * units.deg) .connect_wire(wires.wire8, 5 * 90 * units.deg) .connect_wire(wires.wire2, 6 * 90 * units.deg) .connect_wire(wires.wire6, 7 * 90 * units.deg) engine.add_ignition_module(ignition_module) } private node mustang_vehicle { alias output __out: vehicle( mass: 1614 * units.kg, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (50 * units.inch), diff_ratio: 3.42, tire_radius: 10 * units.inch, rolling_resistance: 200 * units.N ); } private node mustang_transmission { alias output __out: transmission( max_clutch_torque: 500 * units.lb_ft ) .add_gear(3.23) .add_gear(2.19) .add_gear(1.61) .add_gear(1.23) .add_gear(0.97) .add_gear(0.8); } public node main { set_engine(f136_v8()) set_vehicle(mustang_vehicle()) set_transmission(mustang_transmission()) } ================================================ FILE: assets/engines/atg-video-2/09_radial_9.mr ================================================ import "engine_sim.mr" import "radial.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); output wire9: ignition_wire(); } label cycle(2 * 360 * units.deg) public node radial_9_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module( timing_curve: timing_curve, rev_limit: rev_limit, limiter_duration: 0.2 ) .connect_wire(wires.wire1, (0 / 9.0) * cycle) .connect_wire(wires.wire3, (1 / 9.0) * cycle) .connect_wire(wires.wire5, (2 / 9.0) * cycle) .connect_wire(wires.wire7, (3 / 9.0) * cycle) .connect_wire(wires.wire9, (4 / 9.0) * cycle) .connect_wire(wires.wire2, (5 / 9.0) * cycle) .connect_wire(wires.wire4, (6 / 9.0) * cycle) .connect_wire(wires.wire6, (7 / 9.0) * cycle) .connect_wire(wires.wire8, (8 / 9.0) * cycle); } public node radial_9 { alias output __out: engine; engine engine( name: "Radial 9", starter_torque: 80 * units.lb_ft, starter_speed: 400 * units.rpm, redline: 3000 * units.rpm, fuel: fuel( //max_turbulence_effect: 0.5, //max_burning_efficiency: 1.0 ), simulation_frequency: 7500 ) wires wires() label slave_throw(3.5 * units.inch) label stroke(5.5 * units.inch) label bore(5 * units.inch) label rod_length(16 * units.inch) label compression_height(1.0 * units.inch) label rod_mass(535 * units.g) label crank_mass(20.39 * units.kg) label flywheel_mass(50 * units.kg) label flywheel_radius(12 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke / 2) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 10 * units.kg, radius: 2.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: 10 * units.lb, mass: 10 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: (90 - 0.5 * 45) * units.deg ) rod_journal rj0(angle: 0.0) c0 .add_rod_journal(rj0) piston_parameters piston_params( mass: 10 * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 100.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: rod_length - slave_throw ) intake intake( plenum_volume: 10.5 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(1000.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.993, throttle_gamma: 1.0, velocity_decay: 1.0 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(2000.0), primary_tube_length: 70.0 * units.inch, primary_flow_rate: k_carb(1000.0), velocity_decay: 0.75, length: 100 * units.inch ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) exhaust_system exhaust1( es_params, audio_volume: 1.0, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) connecting_rod master( connecting_rod_parameters( cr_params, slave_throw: slave_throw, length: rod_length ) ) rod_journal sj0(angle: (0 / 9.0) * 360 * units.deg) rod_journal sj1(angle: (1 / 9.0) * 360 * units.deg) rod_journal sj2(angle: (2 / 9.0) * 360 * units.deg) rod_journal sj3(angle: (3 / 9.0) * 360 * units.deg) rod_journal sj4(angle: (4 / 9.0) * 360 * units.deg) rod_journal sj5(angle: (5 / 9.0) * 360 * units.deg) rod_journal sj6(angle: (6 / 9.0) * 360 * units.deg) rod_journal sj7(angle: (7 / 9.0) * 360 * units.deg) rod_journal sj8(angle: (8 / 9.0) * 360 * units.deg) master .add_slave_journal(sj0) .add_slave_journal(sj1) .add_slave_journal(sj2) .add_slave_journal(sj3) .add_slave_journal(sj4) .add_slave_journal(sj5) .add_slave_journal(sj6) .add_slave_journal(sj7) .add_slave_journal(sj8) label spacing(10 * units.inch) cylinder_bank b0(bank_params, angle: (0 / 9.0) * 360 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: master, rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, primary_length: 6.11 * units.foot ) cylinder_bank b1(bank_params, angle: (1 / 9.0) * 360 * units.deg) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.03)), connecting_rod: connecting_rod(cr_params), rod_journal: sj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire9, primary_length: 7.46 * units.foot ) cylinder_bank b2(bank_params, angle: (2 / 9.0) * 360 * units.deg) b2 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: sj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire8, primary_length: 8.31 * units.foot ) cylinder_bank b3(bank_params, angle: (3 / 9.0) * 360 * units.deg) b3 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: sj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire7, primary_length: 8.45 * units.foot ) cylinder_bank b4(bank_params, angle: (4 / 9.0) * 360 * units.deg) b4 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: sj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, primary_length: 7.84 * units.foot ) cylinder_bank b5(bank_params, angle: (5 / 9.0) * 360 * units.deg) b5 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: sj5, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5, primary_length: 6.63 * units.foot ) cylinder_bank b6(bank_params, angle: (6 / 9.0) * 360 * units.deg) b6 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: sj6, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, primary_length: 5.2 * units.foot ) cylinder_bank b7(bank_params, angle: (7 / 9.0) * 360 * units.deg) b7 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: sj7, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire3, primary_length: 4.33 * units.foot ) cylinder_bank b8(bank_params, angle: (8 / 9.0) * 360 * units.deg) b8 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: sj8, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, primary_length: 4.77 * units.foot ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) .add_cylinder_bank(b2) .add_cylinder_bank(b3) .add_cylinder_bank(b4) .add_cylinder_bank(b5) .add_cylinder_bank(b6) .add_cylinder_bank(b7) .add_cylinder_bank(b8) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 260 * units.deg, gamma: 0.9, lift: 800 * units.thou, steps: 100 ) b0.set_cylinder_head ( radial_head( offset: 0 / 9.0, lobe_profile: lobe ) ) b1.set_cylinder_head ( radial_head( offset: 4 / 9.0, lobe_profile: lobe ) ) b2.set_cylinder_head ( radial_head( offset: 8 / 9.0, lobe_profile: lobe ) ) b3.set_cylinder_head ( radial_head( offset: 3 / 9.0, lobe_profile: lobe ) ) b4.set_cylinder_head ( radial_head( offset: 7 / 9.0, lobe_profile: lobe ) ) b5.set_cylinder_head ( radial_head( offset: 2 / 9.0, lobe_profile: lobe ) ) b6.set_cylinder_head ( radial_head( offset: 6 / 9.0, lobe_profile: lobe ) ) b7.set_cylinder_head ( radial_head( offset: 1 / 9.0, lobe_profile: lobe ) ) b8.set_cylinder_head ( radial_head( offset: 5 / 9.0, lobe_profile: lobe ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 18 * units.deg) .add_sample(1000 * units.rpm, 18 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 40 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) engine.add_ignition_module( radial_9_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 3500 * units.rpm )) } label car_mass(2700 * units.lb) private node random_car { alias output __out: vehicle( mass: car_mass, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 10000 ); } private node random_transmission { alias output __out: transmission( max_clutch_torque: 2000 * units.lb_ft ) .add_gear(0.01); } public node main { set_engine(radial_9()) set_vehicle(random_car()) set_transmission(random_transmission()) } ================================================ FILE: assets/engines/atg-video-2/10_lfa_v10.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); output wire9: ignition_wire(); output wire10: ignition_wire(); } private node v10_72_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 1.5 * 25 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 2.5 * units.inch * 2.5 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node v10_72_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot(72 * units.deg) label rot360(360 * units.deg) // 1 2 3 4 7 8 9 10 5 6 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * rot) // 1 .add_lobe(rot360 - exhaust_lobe_center + 2 * rot) // 3 .add_lobe(rot360 - exhaust_lobe_center + 8 * rot) // 5 .add_lobe(rot360 - exhaust_lobe_center + 4 * rot) // 7 .add_lobe(rot360 - exhaust_lobe_center + 6 * rot) // 9 _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * rot) // 1 .add_lobe(rot360 + intake_lobe_center + 2 * rot) // 3 .add_lobe(rot360 + intake_lobe_center + 8 * rot) // 5 .add_lobe(rot360 + intake_lobe_center + 4 * rot) // 7 .add_lobe(rot360 + intake_lobe_center + 6 * rot) // 9 _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 1 * rot) // 2 .add_lobe(rot360 - exhaust_lobe_center + 3 * rot) // 4 .add_lobe(rot360 - exhaust_lobe_center + 9 * rot) // 6 .add_lobe(rot360 - exhaust_lobe_center + 5 * rot) // 8 .add_lobe(rot360 - exhaust_lobe_center + 7 * rot) // 10 _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 1 * rot) // 2 .add_lobe(rot360 + intake_lobe_center + 3 * rot) // 4 .add_lobe(rot360 + intake_lobe_center + 9 * rot) // 6 .add_lobe(rot360 + intake_lobe_center + 5 * rot) // 8 .add_lobe(rot360 + intake_lobe_center + 7 * rot) // 10 } public node lr_gue_v10 { alias output __out: engine; engine engine( name: "1LR-GUE [V10]", starter_torque: 100 * units.lb_ft, starter_speed: 200 * units.rpm, redline: 9000 * units.rpm, throttle_gamma: 2.0, fuel: fuel( max_turbulence_effect: 10.0, max_dilution_effect: 20.0, burning_efficiency_randomness: 0.25, max_burning_efficiency: 1.0 ), hf_gain: 0.01, noise: 1.0, jitter: 0.1, simulation_frequency: 6500 ) wires wires() label stroke(79 * units.mm) label bore(88 * units.mm) label rod_length(130 * units.mm) label rod_mass(50 * units.g) label compression_height(1.0 * units.inch) label crank_mass(40 * units.lb) label flywheel_mass(30 * units.lb) label flywheel_radius(6.5 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm) ) label v_angle(72 * units.deg) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 0.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: 90 * units.deg + (v_angle / 2.0) ) // 72 degrees rod_journal rj0(angle: 0 * v_angle) rod_journal rj1(angle: 2 * v_angle) rod_journal rj2(angle: 3 * v_angle) rod_journal rj3(angle: 4 * v_angle) rod_journal rj4(angle: 1 * v_angle) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) piston_parameters piston_params( mass: (100) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(1000.0), runner_flow_rate: k_carb(200.0), runner_length: 4.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.998, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(2000.0), primary_tube_length: 50.0 * units.inch, primary_flow_rate: k_carb(1000.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, audio_volume: 2.0, length: 100 * units.inch, impulse_response: ir_lib.mild_exhaust_0_reverb ) exhaust_system exhaust1( es_params, audio_volume: 2.0, length: 100.5 * units.inch, impulse_response: ir_lib.mild_exhaust_0_reverb ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) cylinder_bank b0(bank_params, angle: v_angle / 2.0) cylinder_bank b1(bank_params, angle: -v_angle / 2.0) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire1, sound_attenuation: 0.8, primary_length: 5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire3, sound_attenuation: 1.0, primary_length: 4 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5, sound_attenuation: 1.1, primary_length: 3 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire7, sound_attenuation: 0.9, primary_length: 2 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire9, sound_attenuation: 0.7, primary_length: 1 * units.cm ) .set_cylinder_head( v10_72_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flip_display: true, flow_attenuation: 1.5) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2, sound_attenuation: 0.7, primary_length: 5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire4, sound_attenuation: 0.8, primary_length: 4 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire6, primary_length: 3 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire8, sound_attenuation: 1.1, primary_length: 2 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire10, sound_attenuation: 0.7, primary_length: 1 * units.cm ) .set_cylinder_head( v10_72_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flow_attenuation: 1.5) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 230 * units.deg, gamma: 1.1, lift: 15.95 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 230 * units.deg, gamma: 1.1, lift: 15.95 * units.mm, steps: 100 ) v10_72_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 90 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: 0.9 * units.inch ) function timing_curve(4000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) .add_sample(8000 * units.rpm, 40 * units.deg) .add_sample(12000 * units.rpm, 40 * units.deg) .add_sample(14000 * units.rpm, 40 * units.deg) .add_sample(18000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 9500 * units.rpm, limiter_duration: 0.1) ignition_module .connect_wire(wires.wire1, 0 * 72 * units.deg) .connect_wire(wires.wire2, 1 * 72 * units.deg) .connect_wire(wires.wire3, 2 * 72 * units.deg) .connect_wire(wires.wire4, 3 * 72 * units.deg) .connect_wire(wires.wire7, 4 * 72 * units.deg) .connect_wire(wires.wire8, 5 * 72 * units.deg) .connect_wire(wires.wire9, 6 * 72 * units.deg) .connect_wire(wires.wire10, 7 * 72 * units.deg) .connect_wire(wires.wire5, 8 * 72 * units.deg) .connect_wire(wires.wire6, 9 * 72 * units.deg) engine.add_ignition_module(ignition_module) } private node lfa_vehicle { alias output __out: vehicle( mass: 1614 * units.kg, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (50 * units.inch), diff_ratio: 3.42, tire_radius: 10 * units.inch, rolling_resistance: 200 * units.N ); } private node lfa_transmission { alias output __out: transmission( max_clutch_torque: 500 * units.lb_ft ) .add_gear(3.23) .add_gear(2.19) .add_gear(1.61) .add_gear(1.23) .add_gear(0.97) .add_gear(0.8); } public node main { set_engine(lr_gue_v10()) set_vehicle(lfa_vehicle()) set_transmission(lfa_transmission()) } ================================================ FILE: assets/engines/atg-video-2/11_merlin_v12.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1a: ignition_wire(); output wire2a: ignition_wire(); output wire3a: ignition_wire(); output wire4a: ignition_wire(); output wire5a: ignition_wire(); output wire6a: ignition_wire(); output wire1b: ignition_wire(); output wire2b: ignition_wire(); output wire3b: ignition_wire(); output wire4b: ignition_wire(); output wire5b: ignition_wire(); output wire6b: ignition_wire(); } private node v12_60_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 450 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 2.0 * units.inch * 2.0 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 5.0 * units.inch * 3.0 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node v12_60_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) // 1a 6b 4a 3b 2a 5b 6a 1b 3a 4b 5a 2b _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * units.deg) .add_lobe(rot360 + intake_lobe_center + 240 * units.deg) .add_lobe(rot360 + intake_lobe_center + 480 * units.deg) .add_lobe(rot360 + intake_lobe_center + 120 * units.deg) .add_lobe(rot360 + intake_lobe_center + 600 * units.deg) .add_lobe(rot360 + intake_lobe_center + 360 * units.deg) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + (360 + 60) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (600 + 60) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (120 + 60) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (480 + 60) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (240 + 60) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (0 + 60) * units.deg) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + (360 + 60) * units.deg) .add_lobe(rot360 + intake_lobe_center + (600 + 60) * units.deg) .add_lobe(rot360 + intake_lobe_center + (120 + 60) * units.deg) .add_lobe(rot360 + intake_lobe_center + (480 + 60) * units.deg) .add_lobe(rot360 + intake_lobe_center + (240 + 60) * units.deg) .add_lobe(rot360 + intake_lobe_center + (0 + 60) * units.deg) } public node merlin_v12 { alias output __out: engine; engine engine( name: "Merlin V-1650-9 [V12] (NA)", starter_torque: 190 * units.lb_ft, starter_speed: 200 * units.rpm, redline: 3000 * units.rpm, fuel: fuel( max_turbulence_effect: 10.0, max_dilution_effect: 5.0, burning_efficiency_randomness: 0.1, max_burning_efficiency: 1.0 ), throttle_gamma: 2.0, simulation_frequency: 7000, hf_gain: 0.004, noise: 0.35, jitter: 0.229 ) wires wires() label stroke(6 * units.inch) label bore(5.4 * units.inch) label rod_length(14 * units.inch) label rod_mass(2000 * units.g) label compression_height(1.0 * units.inch) label crank_mass(400 * units.lb) label flywheel_mass(200 * units.lb) label flywheel_radius(12 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 50.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: (90 + 30) * units.deg ) rod_journal rj0(angle: 0 * units.deg) rod_journal rj1(angle: 240 * units.deg) rod_journal rj2(angle: 120 * units.deg) rod_journal rj3(angle: 120 * units.deg) rod_journal rj4(angle: 240 * units.deg) rod_journal rj5(angle: 0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) .add_rod_journal(rj5) piston_parameters piston_params( mass: (1000) * units.g, compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(1400.0), runner_flow_rate: k_carb(200.0), runner_length: 16.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.99, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(2000.0), primary_tube_length: 50.0 * units.inch, primary_flow_rate: k_carb(400.0), velocity_decay: 1.0 ) exhaust_system exhaust0( es_params, length: 30 * units.inch, audio_volume: 1.0 * 0.5, impulse_response: ir_lib.minimal_muffling_01 ) exhaust_system exhaust1( es_params, length: 70 * units.inch, audio_volume: 1.0 * 0.5, impulse_response: ir_lib.minimal_muffling_01 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(6 * units.inch) cylinder_bank b0(bank_params, angle: (60 / 2.0) * units.deg) cylinder_bank b1(bank_params, angle: -(60 / 2.0) * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.7)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire1a, sound_attenuation: 0.9, primary_length: spacing * 6 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2a, sound_attenuation: 1.0, primary_length: spacing * 5 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire3a, sound_attenuation: 1.5, primary_length: spacing * 4 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.3)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4a, sound_attenuation: 0.9, primary_length: spacing * 3 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5a, sound_attenuation: 0.8, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6a, sound_attenuation: 1.0, primary_length: spacing * 1 ) .set_cylinder_head( v12_60_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flip_display: true, flow_attenuation: 1.0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.5)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1b, sound_attenuation: 0.9, primary_length: spacing * 6 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2b, sound_attenuation: 1.1, primary_length: spacing * 5 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3b, primary_length: spacing * 4 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.3)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire4b, sound_attenuation: 1.1, primary_length: spacing * 3 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5b, sound_attenuation: 0.7, primary_length: spacing * 2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire6b, primary_length: spacing * 1 ) .set_cylinder_head( v12_60_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flow_attenuation: 1.0) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 242 * units.deg, gamma: 0.8, lift: 15.95 * units.mm, steps: 100 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 246 * units.deg, gamma: 0.8, lift: 590 * units.thou, steps: 100 ) v12_60_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 100.5 * units.deg, exhaust_lobe_center: 120 * units.deg, base_radius: 1.0 * units.inch ) function timing_curve(4000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(4000 * units.rpm, 50 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 3500 * units.rpm, limiter_duration: 0.05) ignition_module .connect_wire(wires.wire1a, (0) * units.deg) .connect_wire(wires.wire6b, (0 + 60) * units.deg) .connect_wire(wires.wire4a, (120) * units.deg) .connect_wire(wires.wire3b, (120 + 60) * units.deg) .connect_wire(wires.wire2a, (240) * units.deg) .connect_wire(wires.wire5b, (240 + 60) * units.deg) .connect_wire(wires.wire6a, (360) * units.deg) .connect_wire(wires.wire1b, (360 + 60) * units.deg) .connect_wire(wires.wire3a, (480) * units.deg) .connect_wire(wires.wire4b, (480 + 60) * units.deg) .connect_wire(wires.wire5a, (600) * units.deg) .connect_wire(wires.wire2b, (600 + 60) * units.deg) engine.add_ignition_module(ignition_module) } label car_mass(2700 * units.lb) private node random_car { alias output __out: vehicle( mass: car_mass, drag_coefficient: 0.3, cross_sectional_area: (72 * units.inch) * (56 * units.inch), diff_ratio: 3.9, tire_radius: 10 * units.inch, rolling_resistance: 10000 ); } private node random_transmission { alias output __out: transmission( max_clutch_torque: 2000 * units.lb_ft ) .add_gear(0.01); } public node main { set_engine(merlin_v12()) set_vehicle(random_car()) set_transmission(random_transmission()) } ================================================ FILE: assets/engines/atg-video-2/12_ferrari_412_t2.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); output wire9: ignition_wire(); output wire10: ignition_wire(); output wire11: ignition_wire(); output wire12: ignition_wire(); } private node v12_75_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 1.5 * 25 * units.cc; input intake_runner_volume: 149.6 * units.cc; input intake_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input exhaust_runner_volume: 50.0 * units.cc; input exhaust_runner_cross_section_area: 1.75 * units.inch * 1.75 * units.inch; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 58 * flow_attenuation) .add_flow_sample(100 * lift_scale, 103 * flow_attenuation) .add_flow_sample(150 * lift_scale, 156 * flow_attenuation) .add_flow_sample(200 * lift_scale, 214 * flow_attenuation) .add_flow_sample(250 * lift_scale, 249 * flow_attenuation) .add_flow_sample(300 * lift_scale, 268 * flow_attenuation) .add_flow_sample(350 * lift_scale, 280 * flow_attenuation) .add_flow_sample(400 * lift_scale, 280 * flow_attenuation) .add_flow_sample(450 * lift_scale, 281 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 37 * flow_attenuation) .add_flow_sample(100 * lift_scale, 72 * flow_attenuation) .add_flow_sample(150 * lift_scale, 113 * flow_attenuation) .add_flow_sample(200 * lift_scale, 160 * flow_attenuation) .add_flow_sample(250 * lift_scale, 196 * flow_attenuation) .add_flow_sample(300 * lift_scale, 222 * flow_attenuation) .add_flow_sample(350 * lift_scale, 235 * flow_attenuation) .add_flow_sample(400 * lift_scale, 245 * flow_attenuation) .add_flow_sample(450 * lift_scale, 246 * flow_attenuation) generic_cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ), flip_display: flip_display ) } private node v12_75_camshaft { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 0.5 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) // 1 12 5 8 3 10 6 7 2 11 4 9 _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center + 0 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 480 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 240 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 600 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 120 * units.deg) .add_lobe(rot360 - exhaust_lobe_center + 360 * units.deg) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center + 0 * units.deg) .add_lobe(rot360 + intake_lobe_center + 480 * units.deg) .add_lobe(rot360 + intake_lobe_center + 240 * units.deg) .add_lobe(rot360 + intake_lobe_center + 600 * units.deg) .add_lobe(rot360 + intake_lobe_center + 120 * units.deg) .add_lobe(rot360 + intake_lobe_center + 360 * units.deg) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + (0 + 75) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (480 + 75) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (240 + 75) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (600 + 75) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (120 + 75) * units.deg) .add_lobe(rot360 - exhaust_lobe_center + (360 + 75) * units.deg) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + (0 + 75) * units.deg) .add_lobe(rot360 + intake_lobe_center + (480 + 75) * units.deg) .add_lobe(rot360 + intake_lobe_center + (240 + 75) * units.deg) .add_lobe(rot360 + intake_lobe_center + (600 + 75) * units.deg) .add_lobe(rot360 + intake_lobe_center + (120 + 75) * units.deg) .add_lobe(rot360 + intake_lobe_center + (360 + 75) * units.deg) } private node turbulence_to_flame_speed_ratio { alias output __out: function(5.0) .add_sample(0.0, 2.0 * 3.0) .add_sample(5.0, 2.0 * 1.5 * 5.0) .add_sample(10.0, 2.5 * 1.5 * 10.0) .add_sample(15.0, 3.0 * 1.5 * 15.0) .add_sample(20.0, 3.0 * 1.5 * 20.0) .add_sample(25.0, 3.0 * 1.5 * 25.0) .add_sample(30.0, 3.0 * 1.5 * 30.0) .add_sample(35.0, 3.0 * 1.5 * 35.0) .add_sample(40.0, 3.0 * 1.5 * 40.0) .add_sample(45.0, 3.0 * 1.5 * 45.0); } public node ferrari_412_t2_v12 { alias output __out: engine; engine engine( name: "Ferrari 412 T2 [V12]", starter_torque: 70 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 18000 * units.rpm, throttle_gamma: 2.0, fuel: fuel( max_turbulence_effect: 10.0, max_dilution_effect: 5.0, burning_efficiency_randomness: 1.0, max_burning_efficiency: 1.0, turbulence_to_flame_speed_ratio: turbulence_to_flame_speed_ratio() ), hf_gain: 0.01, noise: 1.0, jitter: 0.1, simulation_frequency: 5000 ) wires wires() label stroke(43 * units.mm) label bore(86 * units.mm) label rod_length(120 * units.mm) label rod_mass(50 * units.g) label compression_height(1.0 * units.inch) label crank_mass(20 * units.lb) label flywheel_mass(10 * units.lb) label flywheel_radius(5 * units.inch) label crank_moment( disk_moment_of_inertia(mass: crank_mass, radius: stroke) ) label flywheel_moment( disk_moment_of_inertia(mass: flywheel_mass, radius: flywheel_radius) ) label other_moment( // Moment from cams, pulleys, etc [estimated] disk_moment_of_inertia(mass: 1 * units.kg, radius: 1.0 * units.cm) ) crankshaft c0( throw: stroke / 2, flywheel_mass: flywheel_mass, mass: crank_mass, friction_torque: 1.0 * units.lb_ft, moment_of_inertia: crank_moment + flywheel_moment + other_moment, position_x: 0.0, position_y: 0.0, tdc: (90 + (75 / 2.0)) * units.deg ) // 1 12 5 8 3 10 6 7 2 11 4 9 rod_journal rj0(angle: 0 * units.deg) rod_journal rj1(angle: 120 * units.deg) rod_journal rj2(angle: 240 * units.deg) rod_journal rj3(angle: 240 * units.deg) rod_journal rj4(angle: 120 * units.deg) rod_journal rj5(angle: 0 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) .add_rod_journal(rj5) piston_parameters piston_params( mass: (50) * units.g, // 414 - piston mass, 152 - pin weight compression_height: compression_height, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: rod_mass, moment_of_inertia: rod_moment_of_inertia( mass: rod_mass, length: rod_length ), center_of_mass: 0.0, length: rod_length ) intake intake( plenum_volume: 1.325 * units.L, plenum_cross_section_area: 20.0 * units.cm2, intake_flow_rate: k_carb(1400.0), runner_flow_rate: k_carb(200.0), runner_length: 4.0 * units.inch, idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.992, velocity_decay: 0.5 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(2000.0), primary_tube_length: 20.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 0.5 ) exhaust_system exhaust0( es_params, audio_volume: 1.0 * 0.004, length: 20 * units.inch, impulse_response: ir_lib.minimal_muffling_01 ) exhaust_system exhaust1( es_params, audio_volume: 1.0 * 0.004, length: 56 * units.inch, impulse_response: ir_lib.minimal_muffling_01 ) cylinder_bank_parameters bank_params( bore: bore, deck_height: stroke / 2 + rod_length + compression_height ) label spacing(0.1) cylinder_bank b0(bank_params, angle: (75 / 2.0) * units.deg) cylinder_bank b1(bank_params, angle: -(75 / 2.0) * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire1, sound_attenuation: 0.5, primary_length: spacing * 0.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, sound_attenuation: 1.0, primary_length: spacing * 0.0 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire3, sound_attenuation: 0.75, primary_length: spacing * 0.2 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, sound_attenuation: 0.9, primary_length: spacing * 1.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5, sound_attenuation: 0.7, primary_length: spacing * 2.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, sound_attenuation: 1.0, primary_length: spacing * 0.5 * units.cm ) .set_cylinder_head( v12_75_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0, flip_display: true, flow_attenuation: 1.0) ) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire12, sound_attenuation: 0.5, primary_length: spacing * 0.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire11, sound_attenuation: 0.3, primary_length: spacing * 0.25 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire10, primary_length: spacing * 3.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire9, sound_attenuation: 1.2, primary_length: spacing * 1.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire8, sound_attenuation: 0.7, primary_length: spacing * 0.5 * units.cm ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.0)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire7, sound_attenuation: 1.2, primary_length: spacing * 1.5 * units.cm ) .set_cylinder_head( v12_75_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flow_attenuation: 1.0) ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe intake_lobe( duration_at_50_thou: 242 * units.deg, gamma: 0.8, lift: 15.95 * units.mm, steps: 512 ) harmonic_cam_lobe exhaust_lobe( duration_at_50_thou: 246 * units.deg, gamma: 0.8, lift: 15.95 * units.mm, steps: 512 ) v12_75_camshaft camshaft( lobe_profile: "N/A", intake_lobe_profile: intake_lobe, exhaust_lobe_profile: exhaust_lobe, intake_lobe_center: 90 * units.deg, exhaust_lobe_center: 112 * units.deg, base_radius: 1.0 * units.inch ) function timing_curve(4000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(4000 * units.rpm, 40 * units.deg) .add_sample(8000 * units.rpm, 40 * units.deg) .add_sample(12000 * units.rpm, 40 * units.deg) .add_sample(14000 * units.rpm, 40 * units.deg) .add_sample(18000 * units.rpm, 40 * units.deg) ignition_module ignition_module( timing_curve: timing_curve, rev_limit: 18500 * units.rpm, limiter_duration: 0.1) ignition_module .connect_wire(wires.wire1, (0) * units.deg) .connect_wire(wires.wire12, (0 + 75) * units.deg) .connect_wire(wires.wire5, (120) * units.deg) .connect_wire(wires.wire8, (120 + 75) * units.deg) .connect_wire(wires.wire3, (240) * units.deg) .connect_wire(wires.wire10, (240 + 75) * units.deg) .connect_wire(wires.wire6, (360) * units.deg) .connect_wire(wires.wire7, (360 + 75) * units.deg) .connect_wire(wires.wire2, (480) * units.deg) .connect_wire(wires.wire11, (480 + 75) * units.deg) .connect_wire(wires.wire4, (600) * units.deg) .connect_wire(wires.wire9, (600 + 75) * units.deg) engine.add_ignition_module(ignition_module) } private node f1_vehicle { alias output __out: vehicle( mass: 798 * units.kg, drag_coefficient: 0.9, cross_sectional_area: (72 * units.inch) * (36 * units.inch), diff_ratio: 4.10, tire_radius: 9 * units.inch, rolling_resistance: 200 * units.N ); } private node f1_transmission { alias output __out: transmission( max_clutch_torque: 1000 * units.lb_ft ) .add_gear(2.8) .add_gear(2.29) .add_gear(1.93) .add_gear(1.583) .add_gear(1.375) .add_gear(1.19); } public node main { set_engine(ferrari_412_t2_v12()) set_vehicle(f1_vehicle()) set_transmission(f1_transmission()) } ================================================ FILE: assets/engines/atg-video-2/README.md ================================================ ## Engines from *Simulating an F1 V12, cross-plane and flat-plane V8s, unequal length headers and more* This folder contains all the engines from [*Simulating an F1 V12, cross-plane and flat-plane V8s, unequal length headers and more*](https://youtu.be/NBBwva3NZ6o). Each file contains a node called `main` and it can be used in `main.mr` as follows. ``` import "engines/atg-video-2/" main() ``` The following engines are included: 1. Subaru EJ25 with equal length headers 2. Subaru EJ25 with unequal length headers 3. Toyota 2JZ 4. 60 degree V6 5. Odd-fire 90 degree V6 6. Even-fire 90 degree V6 7. GM LS V8 8. Ferrari F136 V8 9. Radial-9 10. Toyota 1LR-GUE (Lexus LFA) 11. Merlin V12 12. Ferrari Tipo 044/1 (Formula 1 V12) ================================================ FILE: assets/engines/atg-video-2/radial.mr ================================================ import "engine_sim.mr" units units() constants constants() label cycle(2 * 360 * units.deg) public node radial_head { input offset; input lobe_profile; input chamber_volume: 290 * units.cc; alias output __head: generic_small_engine_head( chamber_volume: chamber_volume, intake_camshaft: camshaft.intake_cam, exhaust_camshaft: camshaft.exhaust_cam, flow_attenuation: 2.0, intake_runner_cross_section_area: 20.0 * units.cm2, exhaust_runner_cross_section_area: 20.0 * units.cm2 ); radial_camshaft camshaft( lobe_profile: lobe_profile, offset: offset ) } public node radial_camshaft { input lobe_profile; input offset; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0 * units.deg; input base_radius: 1.0 * units.inch; output intake_cam: _intake_cam; output exhaust_cam: _exhaust_cam; camshaft_parameters params ( advance: advance, base_radius: base_radius ) camshaft _intake_cam(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile) label rot180(180 * units.deg) label rot360(360 * units.deg) _exhaust_cam .add_lobe(rot360 - exhaust_lobe_center + offset * cycle) _intake_cam .add_lobe(rot360 + intake_lobe_center + offset * cycle) } ================================================ FILE: assets/engines/audi/i5.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); } label cycle(2 * 360 * units.deg) public node vemsign { input wires; input timing_curve; input rev_limit: 7500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0 / 5.0) * cycle) .connect_wire(wires.wire2, (1.0 / 5.0) * cycle) .connect_wire(wires.wire4, (2.0 / 5.0) * cycle) .connect_wire(wires.wire5, (3.0 / 5.0) * cycle) .connect_wire(wires.wire3, (4.0 / 5.0) * cycle); } public node i5_camshaft_builder { input lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(); input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 110.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam: _intake_cam; output exhaust_cam: _exhaust_cam; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam(params, lobe_profile: exhaust_lobe_profile) label rot(2 * (360 / 5.0) * units.deg) label rot360(360 * units.deg) _exhaust_cam .add_lobe(rot360 - exhaust_lobe_center) .add_lobe((rot360 - exhaust_lobe_center) + 1 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 4 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 2 * rot) .add_lobe((rot360 - exhaust_lobe_center) + 3 * rot) _intake_cam .add_lobe(rot360 + intake_lobe_center) .add_lobe(rot360 + intake_lobe_center + 1 * rot) .add_lobe(rot360 + intake_lobe_center + 4 * rot) .add_lobe(rot360 + intake_lobe_center + 2 * rot) .add_lobe(rot360 + intake_lobe_center + 3 * rot) } public node audi_i5_2_2L { alias output __out: engine; wires wires() engine engine( name: "Audi 2.2 inline 5", starter_torque: 200 * units.lb_ft, redline: 7500 * units.rpm, fuel: fuel( max_turbulence_effect: 4.0, burning_efficiency_randomness: 0.2, max_burning_efficiency: 0.85) ) crankshaft c0( throw: 0.5 * 86.4 * units.mm, flywheel_mass: 20 * units.lb, mass: 40 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.9, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 2 ) rod_journal rj0(angle: (0.0 / 5.0) * 360 * units.deg) rod_journal rj1(angle: (2.0 / 5.0) * 360 * units.deg) rod_journal rj2(angle: (3.0 / 5.0) * 360 * units.deg) rod_journal rj3(angle: (4.0 / 5.0) * 360 * units.deg) rod_journal rj4(angle: (1.0 / 5.0) * 360 * units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) piston_parameters piston_params( mass: 400 * units.g, blowby: 0, compression_height: 32.8 * units.mm, wrist_pin_position: 0 * units.mm, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 300.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: (220 - 0.5 * 86.4 - 32.8) * units.mm ) cylinder_bank_parameters bank_params( bore: 81 * units.mm, deck_height: 220 * units.mm ) intake intake( plenum_volume: 5.0 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(500.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.995 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(200.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(100.0), velocity_decay: 0.5, //0.5 volume: 50.0 * units.L ) exhaust_system exhaust0(es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0) exhaust_system exhaust1(es_params, audio_volume: 0.8, impulse_response: ir_lib.default_0) cylinder_bank b0(bank_params, angle: 0) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) i5_camshaft_builder camshaft() b0.set_cylinder_head ( generic_small_engine_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam, exhaust_camshaft: camshaft.exhaust_cam ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 26 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) .add_sample(5000 * units.rpm, 34 * units.deg) .add_sample(6000 * units.rpm, 38 * units.deg) .add_sample(7000 * units.rpm, 38 * units.deg) engine.add_ignition_module( vemsign( wires: wires, timing_curve: timing_curve, rev_limit: 7500 * units.rpm ) ) } ================================================ FILE: assets/engines/bmw/M52B28.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() label cycle(2 * 360 * units.deg) public node bmw_distributor { input wires; input timing_curve; input rev_limit: 8500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0/6.0) * cycle) .connect_wire(wires.wire5, (1.0/6.0) * cycle) .connect_wire(wires.wire3, (2.0/6.0) * cycle) .connect_wire(wires.wire6, (3.0/6.0) * cycle) .connect_wire(wires.wire2, (4.0/6.0) * cycle) .connect_wire(wires.wire4, (5.0/6.0) * cycle); } private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); } private node add_sym_sample { input angle; input lift; input this; alias output __out: this; this.add_sample(angle * units.deg, lift * units.thou) this.add_sample(-angle * units.deg, lift * units.thou) } public node m52b28_lobe_profile_int { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 210 * units.deg, gamma: 0.8, lift: 9.0 * units.mm, steps: 100 ); } public node m52b28_lobe_profile_exh { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 210 * units.deg, gamma: 0.8, lift: 9.0 * units.mm, steps: 100 ); } public node bmw_camshaft_builder { input lobe_profile: m52b28_lobe_profile_int(); input ex_lobe_profile: m52b28_lobe_profile_exh(); input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: ex_lobe_profile; input lobe_separation: 110.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: 105.0 * units.deg; input advance: 0.0 * units.deg; input base_radius: 0.6 * units.inch; output intake_cam_0: _intake_cam_0; output exhaust_cam_0: _exhaust_cam_0; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) label rot60(60 * units.deg) label rot90(90 * units.deg) label rot120(120 * units.deg) label rot180(180 * units.deg) label rot360(360 * units.deg) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center) .add_lobe(rot360 + intake_lobe_center + 4 * rot120) .add_lobe(rot360 + intake_lobe_center + 2 * rot120) .add_lobe(rot360 + intake_lobe_center + 5 * rot120) .add_lobe(rot360 + intake_lobe_center + 1 * rot120) .add_lobe(rot360 + intake_lobe_center + 3 * rot120) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center) .add_lobe(rot360 - exhaust_lobe_center + 4 * rot120) .add_lobe(rot360 - exhaust_lobe_center + 2 * rot120) .add_lobe(rot360 - exhaust_lobe_center + 5 * rot120) .add_lobe(rot360 - exhaust_lobe_center + 1 * rot120) .add_lobe(rot360 - exhaust_lobe_center + 3 * rot120) } private node add_flow_sample { input lift; input flow; input this; alias output __out: this; this.add_sample(lift * units.mm, k_28inH2O(flow)) } public node bmw_m52b28_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 34.0 * units.cc; input flip_display: false; input flow_attenuation: 1.0; input lift_scale: 1.0; alias output __out: head; function intake_flow(1 * units.mm) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(1 * lift_scale, 35 * flow_attenuation) .add_flow_sample(2 * lift_scale, 60 * flow_attenuation) .add_flow_sample(3 * lift_scale, 90 * flow_attenuation) .add_flow_sample(4 * lift_scale, 125 * flow_attenuation) .add_flow_sample(5 * lift_scale, 150 * flow_attenuation) .add_flow_sample(6 * lift_scale, 175 * flow_attenuation) .add_flow_sample(7 * lift_scale, 200 * flow_attenuation) .add_flow_sample(8 * lift_scale, 215 * flow_attenuation) .add_flow_sample(9 * lift_scale, 230 * flow_attenuation) .add_flow_sample(10 * lift_scale, 235 * flow_attenuation) .add_flow_sample(11 * lift_scale, 235 * flow_attenuation) .add_flow_sample(12 * lift_scale, 238 * flow_attenuation) function exhaust_flow(1 * units.mm) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(1 * lift_scale, 35 * flow_attenuation) .add_flow_sample(2 * lift_scale, 55 * flow_attenuation) .add_flow_sample(3 * lift_scale, 85 * flow_attenuation) .add_flow_sample(4 * lift_scale, 105 * flow_attenuation) .add_flow_sample(5 * lift_scale, 120 * flow_attenuation) .add_flow_sample(6 * lift_scale, 140 * flow_attenuation) .add_flow_sample(7 * lift_scale, 150 * flow_attenuation) .add_flow_sample(8 * lift_scale, 155 * flow_attenuation) .add_flow_sample(9 * lift_scale, 160 * flow_attenuation) .add_flow_sample(10 * lift_scale, 165 * flow_attenuation) .add_flow_sample(11 * lift_scale, 165 * flow_attenuation) .add_flow_sample(12 * lift_scale, 165 * flow_attenuation) cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: 100.0 * units.cc, intake_runner_cross_section_area: 2 * 12.4087 * units.cm2, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } public node M52B28 { alias output __out: engine; engine engine( name: "BMW M52B28", starter_torque: 150 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 7000 * units.rpm, fuel: fuel( max_turbulence_effect: 4.0 ), throttle_gamma: 2.0 ) wires wires() crankshaft c0( throw: 84 * units.mm / 2, flywheel_mass: 5.9 * units.kg, mass: 5 * units.kg, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.9, position_x: 0.0, position_y: 0.0, tdc: 120.0 * units.deg ) rod_journal rj0(angle: 0.0) rod_journal rj1(angle: 120*units.deg) rod_journal rj2(angle: 240*units.deg) rod_journal rj3(angle: 240*units.deg) rod_journal rj4(angle: 120*units.deg) rod_journal rj5(angle: 0*units.deg) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) .add_rod_journal(rj4) .add_rod_journal(rj5) piston_parameters piston_params( mass: 280 * units.g, //blowby: k_28inH2O(0.1), compression_height: 31.82 * units.mm, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 300.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: 135.0 * units.mm ) cylinder_bank_parameters bank_params( bore: 84 * units.mm, deck_height: (210.0 + 1) * units.mm ) performer_rpm_intake intake( carburetor_cfm: 500.0, idle_flow_rate_cfm: 0.1, idle_throttle_plate_position: 0.994 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(1000.0), primary_tube_length: 20.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 1.0, //0.5 volume: 50.0 * units.L ) exhaust_system exhaust0(es_params, audio_volume: 0.5, impulse_response: ir_lib.default_0) exhaust_system exhaust1(es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0) cylinder_bank b0(bank_params, angle: 0 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire3 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire4 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj4, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire5 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj5, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire6 ) engine .add_cylinder_bank(b0) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 256 * units.deg, gamma: 1.1, lift: 10.2 * units.mm, steps: 100 ) bmw_camshaft_builder camshaft( lobe_profile: m52b28_lobe_profile_int(), ex_lobe_profile: m52b28_lobe_profile_exh() ) b0.set_cylinder_head ( bmw_m52b28_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0 ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 10 * units.deg) .add_sample(1000 * units.rpm, 10 * units.deg) .add_sample(2000 * units.rpm, 30 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 30 * units.deg) .add_sample(5000 * units.rpm, 30 * units.deg) .add_sample(6000 * units.rpm, 30 * units.deg) .add_sample(7000 * units.rpm, 30 * units.deg) engine.add_ignition_module( bmw_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 8000 * units.rpm )) } ================================================ FILE: assets/engines/chevrolet/chev_truck_454.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); } public node chev_truck_454 { alias output __out: engine; wires wires() engine engine( name: "Chev. 454 V8", starter_torque: 200 * units.lb_ft, redline: 5500 * units.rpm, fuel: fuel( max_turbulence_effect: 3.0, burning_efficiency_randomness: 0.5, max_burning_efficiency: 0.85), throttle_gamma: 1.5 ) crankshaft c0( throw: 2.0 * units.inch, flywheel_mass: 29 * 2 * units.lb, mass: 75 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 2, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 4 ) rod_journal rj0(angle: 0.0) rod_journal rj1(angle: -constants.pi / 2) rod_journal rj2(angle: -3.0 * constants.pi / 2) rod_journal rj3(angle: constants.pi) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: 880 * units.g, blowby: 0, compression_height: 1.640 * units.inch, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 785.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: 6.135 * units.inch ) cylinder_bank_parameters bank_params( bore: 4.25 * units.inch, deck_height: 9.8 * units.inch ) chevy_bbc_stock_intake intake( carburetor_cfm: 650.0, idle_flow_rate_cfm: 0.007, idle_throttle_plate_position: 0.991 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(550.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(100.0), velocity_decay: 1.0, //0.5 length: 100.0 * units.inch ) label distance(5.0 * units.inch) exhaust_system exhaust0( es_params, length: (180 + 72.0) * units.inch, audio_volume: 5.5, impulse_response: ir_lib.default_0) exhaust_system exhaust1( es_params, length: 180.0 * units.inch, audio_volume: 5.5, impulse_response: ir_lib.default_0) cylinder_bank b0(bank_params, angle: -45 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1, primary_length: distance * 4, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3, primary_length: distance * 3, sound_attenuation: 1.1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5, primary_length: distance * 2, sound_attenuation: 0.8 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire7, primary_length: distance * 1, sound_attenuation: 0.85 ) cylinder_bank b1(bank_params, angle: 45.0 * units.deg) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2, primary_length: distance * 4, sound_attenuation: 0.9 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4, primary_length: distance * 3, sound_attenuation: 1.1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6, primary_length: distance * 2, sound_attenuation: 0.8 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire8, primary_length: distance * 1, sound_attenuation: 0.75 ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) chevy_454_stock_camshaft camshaft() b0.set_cylinder_head ( chevy_bbc_peanut_port_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0 ) ) b1.set_cylinder_head ( chevy_bbc_peanut_port_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: true ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 38 * units.deg) .add_sample(5000 * units.rpm, 38 * units.deg) .add_sample(6000 * units.rpm, 38 * units.deg) engine.add_ignition_module( chevy_bbc_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 7000 * units.rpm ) ) } ================================================ FILE: assets/engines/chevrolet/engine_03_for_e1.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); output wire3: ignition_wire(); output wire4: ignition_wire(); output wire5: ignition_wire(); output wire6: ignition_wire(); output wire7: ignition_wire(); output wire8: ignition_wire(); } public node engine_03_for_e1 { alias output __out: engine; wires wires() engine engine( name: "Chev. 454 V8", starter_torque: 200 * units.lb_ft, redline: 5500 * units.rpm, fuel: fuel( max_turbulence_effect: 3.0, burning_efficiency_randomness: 0.5, max_burning_efficiency: 0.85), throttle_gamma: 1.5 ) crankshaft c0( throw: 2.0 * units.inch, flywheel_mass: 29 * 2 * units.lb, mass: 75 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 2, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 4 ) rod_journal rj0(angle: 0.0) rod_journal rj1(angle: -constants.pi / 2) rod_journal rj2(angle: -3.0 * constants.pi / 2) rod_journal rj3(angle: constants.pi) c0 .add_rod_journal(rj0) .add_rod_journal(rj1) .add_rod_journal(rj2) .add_rod_journal(rj3) piston_parameters piston_params( mass: 880 * units.g, blowby: 0, compression_height: 1.640 * units.inch, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 785.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: 6.135 * units.inch ) cylinder_bank_parameters bank_params( bore: 4.25 * units.inch, deck_height: 9.8 * units.inch ) chevy_bbc_stock_intake intake( carburetor_cfm: 950.0, idle_flow_rate_cfm: 0.007, idle_throttle_plate_position: 0.995 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(550.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(90.0), velocity_decay: 1.0, //0.5 volume: 50.0 * units.L ) exhaust_system exhaust0(es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0) exhaust_system exhaust1(es_params, audio_volume: 0.1, impulse_response: ir_lib.default_0) cylinder_bank b0(bank_params, angle: -45 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire3 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire5 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.4)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire7 ) cylinder_bank b1(bank_params, angle: 45.0 * units.deg) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire2 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.2)), connecting_rod: connecting_rod(cr_params), rod_journal: rj1, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire4 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj2, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire6 ) .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.6)), connecting_rod: connecting_rod(cr_params), rod_journal: rj3, intake: intake, exhaust_system: exhaust1, ignition_wire: wires.wire8 ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) comp_cams_magnum_11_450_8 camshaft() b0.set_cylinder_head ( chevy_bbc_peanut_port_head( intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0 ) ) b1.set_cylinder_head ( chevy_bbc_peanut_port_head( intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: true ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 12 * units.deg) .add_sample(1000 * units.rpm, 12 * units.deg) .add_sample(2000 * units.rpm, 20 * units.deg) .add_sample(3000 * units.rpm, 30 * units.deg) .add_sample(4000 * units.rpm, 38 * units.deg) .add_sample(5000 * units.rpm, 38 * units.deg) .add_sample(6000 * units.rpm, 38 * units.deg) engine.add_ignition_module( chevy_bbc_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 7000 * units.rpm ) ) } ================================================ FILE: assets/engines/kohler/kohler_ch750.mr ================================================ import "engine_sim.mr" units units() constants constants() impulse_response_library ir_lib() private node wires { output wire1: ignition_wire(); output wire2: ignition_wire(); } public node kohler_ch750 { alias output __out: engine; engine engine( name: "Kohler CH750", starter_torque: 50 * units.lb_ft, starter_speed: 500 * units.rpm, redline: 3600 * units.rpm ) wires wires() crankshaft c0( throw: 69 * units.mm / 2, flywheel_mass: 5 * units.lb, mass: 5 * units.lb, friction_torque: 10.0 * units.lb_ft, moment_of_inertia: 0.22986844776863666 * 0.5, position_x: 0.0, position_y: 0.0, tdc: constants.pi / 4 ) rod_journal rj0(angle: 0.0) c0 .add_rod_journal(rj0) piston_parameters piston_params( mass: 400 * units.g, //blowby: k_28inH2O(0.1), compression_height: 1.0 * units.inch, wrist_pin_position: 0.0, displacement: 0.0 ) connecting_rod_parameters cr_params( mass: 300.0 * units.g, moment_of_inertia: 0.0015884918028487504, center_of_mass: 0.0, length: 4.0 * units.inch ) cylinder_bank_parameters bank_params( bore: 83 * units.mm, deck_height: (4.0 + 1) * units.inch + 69 * units.mm / 2 ) intake intake( plenum_volume: 1.0 * units.L, plenum_cross_section_area: 10.0 * units.cm2, intake_flow_rate: k_carb(50.0), idle_flow_rate: k_carb(0.0), idle_throttle_plate_position: 0.96, throttle_gamma: 1.0 ) exhaust_system_parameters es_params( outlet_flow_rate: k_carb(300.0), primary_tube_length: 10.0 * units.inch, primary_flow_rate: k_carb(200.0), velocity_decay: 1.0, volume: 20.0 * units.L ) exhaust_system exhaust0( es_params, audio_volume: 1.0, impulse_response: ir_lib.default_0 ) cylinder_bank b0(bank_params, angle: -45 * units.deg) b0 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire1 ) cylinder_bank b1(bank_params, angle: 45.0 * units.deg) b1 .add_cylinder( piston: piston(piston_params, blowby: k_28inH2O(0.1)), connecting_rod: connecting_rod(cr_params), rod_journal: rj0, intake: intake, exhaust_system: exhaust0, ignition_wire: wires.wire2 ) engine .add_cylinder_bank(b0) .add_cylinder_bank(b1) engine.add_crankshaft(c0) harmonic_cam_lobe lobe( duration_at_50_thou: 160 * units.deg, gamma: 1.1, lift: 200 * units.thou, steps: 100 ) vtwin90_camshaft_builder camshaft( lobe_profile: lobe, lobe_separation: 114 * units.deg, base_radius: 500 * units.thou ) b0.set_cylinder_head ( generic_small_engine_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam_0, exhaust_camshaft: camshaft.exhaust_cam_0 ) ) b1.set_cylinder_head ( generic_small_engine_head( chamber_volume: 50 * units.cc, intake_camshaft: camshaft.intake_cam_1, exhaust_camshaft: camshaft.exhaust_cam_1, flip_display: true ) ) function timing_curve(1000 * units.rpm) timing_curve .add_sample(0000 * units.rpm, 50 * units.deg) .add_sample(1000 * units.rpm, 50 * units.deg) .add_sample(2000 * units.rpm, 50 * units.deg) .add_sample(3000 * units.rpm, 50 * units.deg) .add_sample(4000 * units.rpm, 50 * units.deg) engine.add_ignition_module( vtwin90_distributor( wires: wires, timing_curve: timing_curve, rev_limit: 5000 * units.rpm )) } ================================================ FILE: assets/main.mr ================================================ import "engine_sim.mr" import "themes/default.mr" import "engines/atg-video-2/01_subaru_ej25_eh.mr" use_default_theme() main() ================================================ FILE: assets/part-library/part_library.mr ================================================ public import "parts/cam_lobes.mr" public import "parts/camshafts.mr" public import "parts/heads.mr" public import "parts/intakes.mr" public import "parts/ignition_modules.mr" ================================================ FILE: assets/part-library/parts/cam_lobes.mr ================================================ private import "engine_sim.mr" units units() private node add_sym_sample { input angle; input lift; input this; alias output __out: this; this.add_sample(angle * units.deg, lift * units.thou) this.add_sample(-angle * units.deg, lift * units.thou) } public node stock_454_intake_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 194 * units.deg, gamma: 0.8, lift: 390 * units.thou, steps: 100 ); } public node stock_454_exhaust_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 202 * units.deg, gamma: 0.8, lift: 409 * units.thou, steps: 100 ); } public node comp_cams_magnum_11_450_8_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 232 * units.deg, gamma: 0.75, lift: 578 * units.thou, steps: 100 ); } public node comp_cams_magnum_11_470_8_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 252 * units.deg, gamma: 0.8, lift: 612 * units.thou, steps: 100 ); } ================================================ FILE: assets/part-library/parts/camshafts.mr ================================================ private import "cam_lobes.mr" private import "engine_sim.mr" units units() public node chevy_bbc_camshaft_builder { input lobe_profile: stock_454_intake_lobe_profile(); input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam_0: _intake_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_0: _exhaust_cam_0; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot90(90 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center) .add_lobe(rot360 - exhaust_lobe_center + 3 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 5 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 6 * rot90) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 7 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 2 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 4 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 1 * rot90) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center) .add_lobe(rot360 + intake_lobe_center + 3 * rot90) .add_lobe(rot360 + intake_lobe_center + 5 * rot90) .add_lobe(rot360 + intake_lobe_center + 6 * rot90) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 7 * rot90) .add_lobe(rot360 + intake_lobe_center + 2 * rot90) .add_lobe(rot360 + intake_lobe_center + 4 * rot90) .add_lobe(rot360 + intake_lobe_center + 1 * rot90) } public node vtwin90_camshaft_builder { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam_0: _intake_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_0: _exhaust_cam_0; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot90(90 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 3 * rot90) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 3 * rot90) } public node chevy_454_stock_camshaft { alias output __out: chevy_bbc_camshaft_builder( advance: 0 * units.deg, intake_lobe_profile: stock_454_intake_lobe_profile(), exhaust_lobe_profile: stock_454_exhaust_lobe_profile(), intake_lobe_center: 108 * units.deg, exhaust_lobe_center: 113 * units.deg); } public node comp_cams_magnum_11_450_8 { alias output __out: chevy_bbc_camshaft_builder( lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(), lobe_separation: 110 * units.deg, advance: 4.0 * units.deg, base_radius: 1000.0 * units.thou); } public node comp_cams_magnum_11_470_8 { alias output __out: chevy_bbc_camshaft_builder( lobe_profile: comp_cams_magnum_11_470_8_lobe_profile(), lobe_separation: 110 * units.deg, advance: 4.0 * units.deg, base_radius: 1000.0 * units.thou); } ================================================ FILE: assets/part-library/parts/heads.mr ================================================ private import "engine_sim.mr" units units() private node add_flow_sample { input lift; input flow; input this; alias output __out: this; this.add_sample(lift * units.thou, k_28inH2O(flow)) } public node chevy_bbc_peanut_port_head { input intake_camshaft; input exhaust_camshaft; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 75) .add_flow_sample(150, 100) .add_flow_sample(200, 130) .add_flow_sample(250, 180) .add_flow_sample(300, 190) .add_flow_sample(350, 220) .add_flow_sample(400, 240) .add_flow_sample(450, 250) .add_flow_sample(500, 260) .add_flow_sample(550, 260) .add_flow_sample(600, 260) .add_flow_sample(650, 255) .add_flow_sample(700, 250) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 50) .add_flow_sample(150, 75) .add_flow_sample(200, 100) .add_flow_sample(250, 125) .add_flow_sample(300, 160) .add_flow_sample(350, 175) .add_flow_sample(400, 180) .add_flow_sample(450, 190) .add_flow_sample(500, 200) .add_flow_sample(550, 205) .add_flow_sample(600, 210) .add_flow_sample(650, 210) .add_flow_sample(700, 210) cylinder_head head( chamber_volume: 118.0 * units.cc, intake_runner_volume: 189.0 * units.cc, intake_runner_cross_section_area: 37.8 * units.cm2, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } public node edelbrock_6055_rectangle_port { input intake_camshaft; input exhaust_camshaft; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 76) .add_flow_sample(150, 100) .add_flow_sample(200, 146) .add_flow_sample(250, 175) .add_flow_sample(300, 212) .add_flow_sample(350, 230) .add_flow_sample(400, 255) .add_flow_sample(450, 275) .add_flow_sample(500, 294) .add_flow_sample(550, 300) .add_flow_sample(600, 314) .add_flow_sample(650, 314) .add_flow_sample(700, 314) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 70) .add_flow_sample(150, 100) .add_flow_sample(200, 132) .add_flow_sample(250, 140) .add_flow_sample(300, 156) .add_flow_sample(350, 170) .add_flow_sample(400, 181) .add_flow_sample(450, 191) .add_flow_sample(500, 207) .add_flow_sample(550, 214) .add_flow_sample(600, 228) .add_flow_sample(650, 228) .add_flow_sample(700, 228) cylinder_head head( chamber_volume: 118.0 * units.cc, intake_runner_volume: 315.0 * units.cc, intake_runner_cross_section_area: 78.75 * units.cm2, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } public node generic_small_engine_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 100.0 * units.cc; input intake_runner_volume: 100.0 * units.cc; input intake_runner_cross_section_area: 30.0 * units.cm2; input exhaust_runner_volume: 100.0 * units.cc; input exhaust_runner_cross_section_area: 30.0 * units.cm2; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 25 * flow_attenuation) .add_flow_sample(100 * lift_scale, 75 * flow_attenuation) .add_flow_sample(150 * lift_scale, 100 * flow_attenuation) .add_flow_sample(200 * lift_scale, 130 * flow_attenuation) .add_flow_sample(250 * lift_scale, 180 * flow_attenuation) .add_flow_sample(300 * lift_scale, 190 * flow_attenuation) .add_flow_sample(350 * lift_scale, 220 * flow_attenuation) .add_flow_sample(400 * lift_scale, 240 * flow_attenuation) .add_flow_sample(450 * lift_scale, 250 * flow_attenuation) .add_flow_sample(500 * lift_scale, 260 * flow_attenuation) .add_flow_sample(550 * lift_scale, 260 * flow_attenuation) .add_flow_sample(600 * lift_scale, 260 * flow_attenuation) .add_flow_sample(650 * lift_scale, 255 * flow_attenuation) .add_flow_sample(700 * lift_scale, 250 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 25 * flow_attenuation) .add_flow_sample(100 * lift_scale, 50 * flow_attenuation) .add_flow_sample(150 * lift_scale, 75 * flow_attenuation) .add_flow_sample(200 * lift_scale, 100 * flow_attenuation) .add_flow_sample(250 * lift_scale, 125 * flow_attenuation) .add_flow_sample(300 * lift_scale, 160 * flow_attenuation) .add_flow_sample(350 * lift_scale, 175 * flow_attenuation) .add_flow_sample(400 * lift_scale, 180 * flow_attenuation) .add_flow_sample(450 * lift_scale, 190 * flow_attenuation) .add_flow_sample(500 * lift_scale, 200 * flow_attenuation) .add_flow_sample(550 * lift_scale, 205 * flow_attenuation) .add_flow_sample(600 * lift_scale, 210 * flow_attenuation) .add_flow_sample(650 * lift_scale, 210 * flow_attenuation) .add_flow_sample(700 * lift_scale, 210 * flow_attenuation) cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } ================================================ FILE: assets/part-library/parts/ignition_modules.mr ================================================ private import "engine_sim.mr" units units() label cycle(2 * 360 * units.deg) public node chevy_bbc_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0 / 8.0) * cycle) .connect_wire(wires.wire8, (1.0 / 8.0) * cycle) .connect_wire(wires.wire4, (2.0 / 8.0) * cycle) .connect_wire(wires.wire3, (3.0 / 8.0) * cycle) .connect_wire(wires.wire6, (4.0 / 8.0) * cycle) .connect_wire(wires.wire5, (5.0 / 8.0) * cycle) .connect_wire(wires.wire7, (6.0 / 8.0) * cycle) .connect_wire(wires.wire2, (7.0 / 8.0) * cycle); } public node chevy_sbc_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: chevy_bbc_distributor( wires: wires, timing_curve: timing_curve, rev_limit: rev_limit ); } public node vtwin90_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0 / 8.0) * cycle) .connect_wire(wires.wire2, (3.0 / 8.0) * cycle); } ================================================ FILE: assets/part-library/parts/intakes.mr ================================================ private import "engine_sim.mr" units units() public node chevy_bbc_stock_intake { input carburetor_cfm: 650.0; input idle_flow_rate_cfm: 1.0; input idle_throttle_plate_position: 0.975; input throttle_gamma: 2.0; alias output __out: intake; intake intake( plenum_volume: 2.0 * units.L, plenum_cross_section_area: 100.0 * units.cm2, intake_flow_rate: k_carb(carburetor_cfm), idle_flow_rate: k_carb(idle_flow_rate_cfm), idle_throttle_plate_position: idle_throttle_plate_position, throttle_gamma: throttle_gamma, runner_flow_rate: k_carb(300.0), runner_length: 6.0 * units.inch, velocity_decay: 1.0 ) } public node performer_rpm_intake { input carburetor_cfm: 650.0; input idle_flow_rate_cfm: 1.0; input idle_throttle_plate_position: 0.975; input throttle_gamma: 2.0; alias output __out: intake; intake intake( plenum_volume: 2.0 * units.L, plenum_cross_section_area: 100.0 * units.cm2, intake_flow_rate: k_carb(carburetor_cfm), idle_flow_rate: k_carb(idle_flow_rate_cfm), idle_throttle_plate_position: idle_throttle_plate_position, throttle_gamma: throttle_gamma, runner_flow_rate: k_carb(500.0), runner_length: 6.0 * units.inch, velocity_decay: 0.1 ) } ================================================ FILE: assets/themes/amateur.mr ================================================ import "engine_sim.mr" unit_names units() public node use_amateur_theme { input start_fullscreen: false; input speed_units: units.mph; input pressure_units: units.inHg; input torque_units: units.lb_ft; input power_units: units.hp; set_application_settings( start_fullscreen: start_fullscreen, speed_units: speed_units, pressure_units: pressure_units, torque_units:torque_units, power_units: power_units, // Default Color Settings color_background: 0x000000, color_foreground: 0xFFFFFF, color_highlight1: 0xFF0000, color_highlight2: 0xFFFFFF, color_shadow: 0x000000, color_pink: 0xFF00FF, color_red: 0xFF0000, color_orange: 0xFF8000, color_yellow: 0xFFFF00, color_blue: 0x0000FF, color_green: 0x00FF00 ) } ================================================ FILE: assets/themes/bubble_gum.mr ================================================ import "engine_sim.mr" unit_names units() public node use_bubble_gum_theme { input start_fullscreen: false; input speed_units: units.mph; input pressure_units: units.inHg; input torque_units: units.lb_ft; input power_units: units.hp; set_application_settings( start_fullscreen: start_fullscreen, speed_units: speed_units, pressure_units: pressure_units, torque_units:torque_units, power_units: power_units, // Default Color Settings color_background: 0xF394BE, color_foreground: 0xFFFFFF, color_highlight1: 0xfdeaf2, color_highlight2: 0xFFFFFF, color_shadow: 0xF394BE, color_pink: 0xfad4e5, color_red: 0xf5a9cb, color_orange: 0xffd086, color_yellow: 0xffd0c3, color_blue: 0xcfd2dc, color_green: 0xd7f7d2 ) } ================================================ FILE: assets/themes/default.mr ================================================ import "engine_sim.mr" unit_names units() public node use_default_theme { input start_fullscreen: false; input speed_units: units.mph; input pressure_units: units.inHg; input torque_units: units.lb_ft; input power_units: units.hp; set_application_settings( start_fullscreen: start_fullscreen, speed_units: speed_units, pressure_units: pressure_units, torque_units: torque_units, power_units: power_units, // Default Color Settings color_background: 0x0E1012, color_foreground: 0xFFFFFF, color_shadow: 0x0E1012, color_highlight1: 0xEF4545, color_highlight2: 0xFFFFFF, color_pink: 0xF394BE, color_red: 0xEE4445, color_orange: 0xF4802A, color_yellow: 0xFDBD2E, color_blue: 0x77CEE0, color_green: 0xBDD869 ) } ================================================ FILE: assets/themes/minimalistic.mr ================================================ import "engine_sim.mr" unit_names units() public node use_minimalistic_theme { input start_fullscreen: false; input speed_units: units.mph; input pressure_units: units.inHg; input torque_units: units.lb_ft; input power_units: units.hp; set_application_settings( start_fullscreen: start_fullscreen, speed_units: speed_units, pressure_units: pressure_units, torque_units:torque_units, power_units: power_units, // Default Color Settings color_background: 0x000000, color_foreground: 0xFFFFFF, color_highlight1: 0xFFFFFF, color_highlight2: 0xFFFFFF, color_shadow: 0x000000, color_pink: 0xFFFFFF, color_red: 0xFFFFFF, color_orange: 0xFFFFFF, color_yellow: 0xFFFFFF, color_blue: 0xFFFFFF, color_green: 0xFFFFFF ) } ================================================ FILE: assets/themes/night_vision.mr ================================================ import "engine_sim.mr" unit_names units() public node use_night_vision_theme { input start_fullscreen: false; input speed_units: units.mph; input pressure_units: units.inHg; input torque_units: units.lb_ft; input power_units: units.hp; input color: 0x4dff68; set_application_settings( start_fullscreen: start_fullscreen, speed_units: speed_units, pressure_units: pressure_units, torque_units:torque_units, power_units: power_units, // Default Color Settings color_background: 0x000000, color_foreground: color, color_highlight1: color, color_highlight2: color, color_shadow: 0x000000, color_pink: color, color_red: color, color_orange: color, color_yellow: color, color_blue: color, color_green: color ) } ================================================ FILE: assets/themes/paper.mr ================================================ import "engine_sim.mr" unit_names units() public node use_paper_theme { input start_fullscreen: false; input speed_units: units.mph; input pressure_units: units.inHg; input torque_units: units.lb_ft; input power_units: units.hp; input color: 0x63aaff; set_application_settings( start_fullscreen: start_fullscreen, speed_units: speed_units, pressure_units: pressure_units, torque_units:torque_units, power_units: power_units, // Default Color Settings color_background: 0xf8f2f0, color_foreground: color, color_highlight1: color, color_highlight2: color, color_shadow: 0xf8f2f0, color_pink: color, color_red: color, color_orange: color, color_yellow: color, color_blue: color, color_green: color ) } ================================================ FILE: configuration/delta.conf ================================================ /delta /assets ================================================ FILE: dependencies/CMakeLists.txt ================================================ add_subdirectory(submodules) if (DISCORD_ENABLED) add_subdirectory(discord) endif () ================================================ FILE: dependencies/discord/CMakeLists.txt ================================================ set_property(TARGET discord PROPERTY FOLDER "discord") ================================================ FILE: dependencies/discord/Discord.cpp ================================================ #include "Discord.h" #include #include const char* DISCORD_APPLICATION_ID = "1008609936256811038"; // https://discordapp.com/developers/applications CDiscord* CDiscord::m_pInstance = NULL; static int64_t StartTime; CDiscord::CDiscord() { m_state = "Doing Things"; m_bEnableDiscord = true; m_bConnected = false; } CDiscord::~CDiscord() { } bool CDiscord::CreateInstance() { if (m_pInstance) return false; m_pInstance = new CDiscord; return true; } void CDiscord::DestroyInstance() { if (!m_pInstance) return; Discord_Shutdown(); // shut down discord delete m_pInstance; } void CDiscord::handleDiscordReady(const DiscordUser * connectedUser) { GetDiscordManager()->SetConnected(true); } void CDiscord::handleDiscordDisconnected(int errcode, const char* message) { GetDiscordManager()->SetConnected(false); } void CDiscord::handleDiscordError(int errcode, const char* message) { } void CDiscord::InitDiscord() { DiscordEventHandlers handlers; memset(&handlers, 0, sizeof(handlers)); handlers.ready = CDiscord::handleDiscordReady; handlers.disconnected = CDiscord::handleDiscordDisconnected; handlers.errored = CDiscord::handleDiscordError; handlers.joinGame = NULL; handlers.spectateGame = NULL; handlers.joinRequest = NULL; Discord_Initialize(DISCORD_APPLICATION_ID, &handlers, 1, NULL); } void CDiscord::UpdatePresence() { if (m_bEnableDiscord == false) return; if (m_bConnected == false) return; DiscordRichPresence discordPresence; memset(&discordPresence, 0, sizeof(discordPresence)); discordPresence.details = m_state.c_str(); discordPresence.largeImageKey = "atgenginesim"; discordPresence.largeImageText = "Engine Simulator"; discordPresence.startTimestamp = StartTime; discordPresence.instance = 0; Discord_UpdatePresence(&discordPresence); } void CDiscord::ClearPresence() { Discord_ClearPresence(); } void CDiscord::UpdateDiscordConnection() { if (m_bEnableDiscord == false) return; #ifdef DISCORD_DISABLE_IO_THREAD Discord_UpdateConnection(); #endif Discord_RunCallbacks(); } void CDiscord::SetUseDiscord(bool bFlag) { m_bEnableDiscord = bFlag; if (bFlag) { InitDiscord(); } } void CDiscord::SetConnected(bool bFlag) { m_bConnected = bFlag; } void CDiscord::SetStatus(DiscordRichPresence presence, std::string engineName = "", std::string buildVersion = "") { std::string engineString = "Engine: " + engineName; std::string stateString = "Build: " + buildVersion; presence.state = stateString.c_str(); presence.details = engineString.c_str(); presence.largeImageKey = "atgenginesim"; presence.largeImageText = "Engine Simulator"; StartTime = time(NULL); presence.instance = 1; presence.startTimestamp = StartTime; Discord_UpdatePresence(&presence); } ================================================ FILE: dependencies/discord/Discord.h ================================================ /***************************************************************************** * * File : Discord.h * Author : SanGawku * Copyright : SanGawku * Date : 7/29/2019 * Abstract : Discord ***************************************************************************** * * Desc : Discord rich presence integration * *****************************************************************************/ #ifndef __DISCORD_H__ #define __DISCORD_H__ #include #include "discord_rpc.h" #pragma comment(lib, "discord-rpc.lib") class CDiscord { public: CDiscord(); virtual ~CDiscord(); static bool CreateInstance(); static void DestroyInstance(); static CDiscord* GetInstance() { return m_pInstance; } public: static void handleDiscordReady(const DiscordUser* connectedUser); static void handleDiscordDisconnected(int errcode, const char* message); static void handleDiscordError(int errcode, const char* message); private: void InitDiscord(); void UpdatePresence(); void ClearPresence(); public: void UpdateDiscordConnection(); void SetUseDiscord(bool bFlag); // if true, then we will use discord and connect to discord void SetConnected(bool bFlag); //set connected to discord or not void SetStatus(DiscordRichPresence presence, std::string engineName, std::string buildVersion); private: std::string m_state; std::string m_engineName; bool m_bEnableDiscord; bool m_bConnected; protected: static CDiscord* m_pInstance; }; static CDiscord* GetDiscordManager() { return CDiscord::GetInstance(); }; #endif ================================================ FILE: dependencies/discord/LICENSE ================================================ Copyright 2017 Discord, Inc. 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. ================================================ FILE: dependencies/discord/discord_register.h ================================================ #pragma once #if defined(DISCORD_DYNAMIC_LIB) # if defined(_WIN32) # if defined(DISCORD_BUILDING_SDK) # define DISCORD_EXPORT __declspec(dllexport) # else # define DISCORD_EXPORT __declspec(dllimport) # endif # else # define DISCORD_EXPORT __attribute__((visibility("default"))) # endif #else # define DISCORD_EXPORT #endif #ifdef __cplusplus extern "C" { #endif DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); #ifdef __cplusplus } #endif ================================================ FILE: dependencies/discord/discord_rpc.h ================================================ #pragma once // clang-format off #if defined(DISCORD_DYNAMIC_LIB) # if defined(_WIN32) # if defined(DISCORD_BUILDING_SDK) # define DISCORD_EXPORT __declspec(dllexport) # else # define DISCORD_EXPORT __declspec(dllimport) # endif # else # define DISCORD_EXPORT __attribute__((visibility("default"))) # endif #else # define DISCORD_EXPORT #endif // clang-format on #ifdef __cplusplus extern "C" { #endif typedef struct DiscordRichPresence { const char* state; /* max 128 bytes */ const char* details; /* max 128 bytes */ long long startTimestamp; long long endTimestamp; const char* largeImageKey; /* max 32 bytes */ const char* largeImageText; /* max 128 bytes */ const char* smallImageKey; /* max 32 bytes */ const char* smallImageText; /* max 128 bytes */ const char* partyId; /* max 128 bytes */ int partySize; int partyMax; const char* matchSecret; /* max 128 bytes */ const char* joinSecret; /* max 128 bytes */ const char* spectateSecret; /* max 128 bytes */ unsigned char instance; } DiscordRichPresence; typedef struct DiscordUser { const char* userId; const char* username; const char* discriminator; const char* avatar; } DiscordUser; typedef struct DiscordEventHandlers { void (*ready)(const DiscordUser* request); void (*disconnected)(int errorCode, const char* message); void (*errored)(int errorCode, const char* message); void (*joinGame)(const char* joinSecret); void (*spectateGame)(const char* spectateSecret); void (*joinRequest)(const DiscordUser* request); } DiscordEventHandlers; #define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_IGNORE 2 DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers, int autoRegister, const char* optionalSteamId); DISCORD_EXPORT void Discord_Shutdown(void); /* checks for incoming messages, dispatches callbacks */ DISCORD_EXPORT void Discord_RunCallbacks(void); /* If you disable the lib starting its own io thread, you'll need to call this from your own */ #ifdef DISCORD_DISABLE_IO_THREAD DISCORD_EXPORT void Discord_UpdateConnection(void); #endif DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); DISCORD_EXPORT void Discord_ClearPresence(void); DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); #ifdef __cplusplus } /* extern "C" */ #endif ================================================ FILE: dependencies/submodules/CMakeLists.txt ================================================ add_subdirectory(delta-studio) set_property(TARGET delta-basic PROPERTY FOLDER "delta") set_property(TARGET delta-basic-demo PROPERTY FOLDER "delta") set_property(TARGET delta-core PROPERTY FOLDER "delta") set_property(TARGET delta-physics PROPERTY FOLDER "delta") add_subdirectory(simple-2d-constraint-solver) set_property(TARGET simple-2d-constraint-solver PROPERTY FOLDER "scs") set_property(TARGET simple-2d-constraint-solver-test PROPERTY FOLDER "scs") add_subdirectory(csv-io) set_property(TARGET csv-io PROPERTY FOLDER "csv-io") set_property(TARGET csv-io-main PROPERTY FOLDER "csv-io") set_property(TARGET csv-io-test PROPERTY FOLDER "csv-io") if (DTV) add_subdirectory(direct-to-video) set_property(TARGET direct-to-video PROPERTY FOLDER "dtv") set_property(TARGET direct-to-video PROPERTY FOLDER "dtv") endif (DTV) add_subdirectory(piranha) set_property(TARGET piranha PROPERTY FOLDER "piranha") set_property(TARGET piranha_test PROPERTY FOLDER "piranha") set_property(TARGET piranha_reference_compiler PROPERTY FOLDER "piranha") ================================================ FILE: es/actions/actions.mr ================================================ module { @name: "Actions" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } private import "../types/atomic_types.mr" private import "../types/conversions.mr" private import "../types/operations.mr" private import "../constants/constants.mr" private import "../constants/units.mr" private import "../objects/objects.mr" constants constants() units units() public node set_engine => __engine_sim__set_engine { input engine [engine]; } private node _add_rod_journal => __engine_sim__add_rod_journal { input rod_journal [rod_journal]; input crankshaft [crankshaft]; } public node add_rod_journal { input rod_journal; input this; alias output out: this; _add_rod_journal(rod_journal, this) } private node _add_slave_journal => __engine_sim__add_slave_journal { input rod_journal [rod_journal]; input rod [connecting_rod]; } public node add_slave_journal { input rod_journal; input this; alias output out: this; _add_slave_journal(rod_journal, this) } public node _add_crankshaft => __engine_sim__add_crankshaft { input crankshaft [crankshaft]; input engine [engine]; } public node add_crankshaft { input crankshaft; input this; alias output out: this; _add_crankshaft(crankshaft, engine: this) } private node _add_cylinder_bank => __engine_sim__add_cylinder_bank { input engine [engine]; input cylinder_bank [cylinder_bank]; } public node add_cylinder_bank { input cylinder_bank; input this; alias output __out: this; _add_cylinder_bank(engine: this, cylinder_bank) } private node _add_cylinder => __engine_sim__add_cylinder { input piston [piston]; input connecting_rod [connecting_rod]; input rod_journal [rod_journal]; input exhaust_system [exhaust_system]; input intake [intake]; input cylinder_bank [cylinder_bank]; input ignition_wire [ignition_wire]; input sound_attenuation [float]; input primary_length [float]; } public node add_cylinder { input intake; input exhaust_system; input piston; input connecting_rod; input rod_journal; input ignition_wire; input sound_attenuation: 1.0; input primary_length: 0.0; input this; alias output __out: this; _add_cylinder( piston: piston, connecting_rod: connecting_rod, rod_journal: rod_journal, exhaust_system: exhaust_system, intake: intake, ignition_wire: ignition_wire, cylinder_bank: this, sound_attenuation: sound_attenuation, primary_length: primary_length ) } private node _add_sample => __engine_sim__add_sample { input x [float]; input y [float]; input function [function]; } public node add_sample { input x; input y; input this; alias output __out: this; _add_sample(x: x, y: y, function: this) } private node _add_lobe => __engine_sim__add_lobe { input centerline [float]; input camshaft [camshaft]; } public node add_lobe { input centerline; input this; alias output __out: this; _add_lobe(centerline: centerline, camshaft: this) } private node _set_cylinder_head => __engine_sim__set_cylinder_head { input head [cylinder_head]; input bank [cylinder_bank]; } public node set_cylinder_head { input head; input this; _set_cylinder_head(head: head, bank: this) } public node k_28inH2O => __engine_sim__k_28inH2O { input flow [float]: 1.0; alias output __out [float]; } public node k_carb => __engine_sim__k_carb { input flow [float]: 1.0; alias output __out [float]; } public node circle_area { input radius; alias output __out: constants.pi * radius * radius; } public node _connect_wire => __engine_sim__connect_ignition_wire { input wire [ignition_wire]; input ignition_module [ignition_module]; input angle [float]; } public node connect_wire { input wire; input angle; input this; alias output __out: this; _connect_wire(wire: wire, angle: angle, ignition_module: this) } private node _add_ignition_module => __engine_sim__add_ignition_module { input ignition_module [ignition_module]; input engine [engine]; } public node add_ignition_module { input ignition_module; input this; alias output __out: this; _add_ignition_module(ignition_module: ignition_module, engine: this) } public node _generate_harmonic_cam_lobe => __engine_sim__generate_harmonic_cam_lobe { input duration_at_50_thou [float]; input gamma [float]; input lift [float]; input steps [int]; input function [function]; } public node harmonic_cam_lobe { input duration_at_50_thou: 0.0; input gamma: 1.0; input lift: 300 * units.thou; input steps: 100; alias output __out: generated_function; function generated_function() _generate_harmonic_cam_lobe( duration_at_50_thou: duration_at_50_thou, gamma: gamma, lift: lift, steps: steps, function: generated_function ) } public node set_vehicle => __engine_sim__set_vehicle { input vehicle [vehicle]; } private node _add_gear => __engine_sim__add_gear { input ratio [float]; input transmission [transmission]; } public node add_gear { input ratio; input this; alias output __out: this; _add_gear(ratio, this) } public node set_transmission => __engine_sim__set_transmission { input transmission [transmission]; } ================================================ FILE: es/constants/constants.mr ================================================ module { @name: "Units" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } private import "../types/atomic_types.mr" private import "../types/conversions.mr" public node constants { output pi: 3.14159265359; } ================================================ FILE: es/constants/units.mr ================================================ module { @name: "Units" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } private import "constants.mr" private import "../types/atomic_types.mr" private import "../types/conversions.mr" private import "../types/operations.mr" constants constants() public node units { // Force output N: 1.0; output lbf: N * 4.44822; // Mass output kg: 1.0; output g: kg / 1000.0; output lb: 0.45359237 * kg; // Distance output m: 1.0; output cm: m / 100.0; output mm: m / 1000.0; output km: m * 1000.0; output inch: cm * 2.54; output foot: inch * 12.0; output thou: inch / 1000.0; output mile: m * 1609.344; // Time output sec: 1.0; output minute: 60.0 * sec; output hour: 60.0 * minute; // Torque output Nm: N * m; output lb_ft: lbf * foot; // Volume output m3: 1.0; output cc: cm * cm * cm; output mL: cc; output L: mL * 1000.0; output cubic_feet: foot * foot * foot; output cubic_inches: inch * inch * inch; output gal: 3.785411784 * L; // Molecular output mol: 1.0; output kmol: mol / 1000.0; output mmol: kmol / 1000.0; output lbmol: mol * 453.59237; // Flow-rate output mol_per_sec: mol / sec; output scfm: 0.002641 * lbmol / minute; // Area output m2: 1.0; output cm2: cm * cm; // Pressure output Pa: 1.0; output kPa: Pa * 1000.0; output MPa: kPa * 1000.0; output atm: 101.325 * kPa; output psi: lb / (inch * inch); output psig: psi; output inHg: Pa * 3386.3886666666713; output inH2O: inHg * 0.0734824; // Temperature output K: 1.0; output K0: 273.15; output C: K; output F: (5.0 / 9.0) * K; output F0: -459.67; // Energy output J: 1.0; output kJ: J * 1000.0; output MJ: kJ * 1000.0; // Angles output rad: 1.0; output deg: rad * (constants.pi / 180.0); // RPM output rpm: 0.104719755; // Speed output mph: mile / hour; } public node unit_names { // Pressure output inHg: "inHg"; output mbar: "mbar"; output millibar: mbar; output bar: "bar"; output kPa: "kPa"; output psi: "psi"; // Speed output mph: "mph"; output kph: "kph"; output american: mph; output murican: american; output british: mph; output european: kph; output euro: european; // Torque output lb_ft: "lb-ft"; output ft_lb: lb_ft; output Nm: "Nm"; // Power output hp: "hp"; output kW: "kW"; output horsepower: hp; output kilowatt: kW; } ================================================ FILE: es/engine_sim.mr ================================================ module { @name: "Engine Simulator Library" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } // Types public import "types/atomic_types.mr" public import "types/conversions.mr" public import "types/operations.mr" // Actions public import "actions/actions.mr" // Objects public import "objects/objects.mr" // Constants public import "constants/constants.mr" public import "constants/units.mr" // Infrastructure public import "infrastructure/infrastructure.mr" // Library public import "part-library/part_library.mr" public import "sound-library/impulse_responses.mr" // Utilities public import "utilities/utilities.mr" // Application settings public import "settings/application_settings.mr" ================================================ FILE: es/infrastructure/infrastructure.mr ================================================ module { @name: "Infrastructure" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } public node label { input in; alias output _out: in; } ================================================ FILE: es/objects/objects.mr ================================================ module { @name: "Objects" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } private import "../types/atomic_types.mr" private import "../types/conversions.mr" private import "../types/operations.mr" private import "../constants/units.mr" private import "../actions/actions.mr" units units() // Channels public node engine_channel => __engine_sim__engine_channel { /* void */ } public node crankshaft_channel => __engine_sim__crankshaft_channel { /* void */ } public node rod_journal_channel => __engine_sim__rod_journal { /* void */ } public node connecting_rod_channel => __engine_sim__connecting_rod_channel { /* void */ } public node piston_channel => __engine_sim__piston_channel { /* void */ } public node cylinder_bank_channel => __engine_sim__cylinder_bank_channel { /* void */ } public node function_channel => __engine_sim__function_channel { /* void */ } public node cylinder_head_channel => __engine_sim__cylinder_head_channel { /* void */ } public node camshaft_channel => __engine_sim__camshaft_channel { /* void */ } public node intake_channel => __engine_sim__intake_channel { /* void */ } public node exhaust_system_channel => __engine_sim__exhaust_system_channel { /* void */ } public node ignition_module_channel => __engine_sim__ignition_module_channel { /* void */ } public node ignition_wire_channel => __engine_sim__ignition_wire_channel { /* void */ } public node fuel_channel => __engine_sim__fuel_channel { /* void */ } public node impulse_response_channel => __engine_sim__impulse_response_channel { /* void */ } public node valvetrain_channel => __engine_sim__valvetrain_channel { /* void */ } public node vehicle_channel => __engine_sim__vehicle_channel { /* void */ } public node transmission_channel => __engine_sim__transmission_channel { /* void */ } public node throttle_channel => __engine_sim__throttle_channel { /* void */ } private node turbulence_to_flame_speed_ratio_default { alias output __out: function(5.0) .add_sample(0.0, 3.0) .add_sample(5.0, 1.5 * 5.0) .add_sample(10.0, 1.5 * 10.0) .add_sample(15.0, 1.5 * 15.0) .add_sample(20.0, 1.5 * 20.0) .add_sample(25.0, 1.5 * 25.0) .add_sample(30.0, 1.5 * 30.0) .add_sample(35.0, 1.5 * 35.0) .add_sample(40.0, 1.5 * 40.0) .add_sample(45.0, 1.5 * 45.0); } public node fuel => __engine_sim__fuel { input name [string]: "Gasoline [Default]"; input molecular_mass [float]: 100 * units.g; input energy_density [float]: 48.1 * units.kJ / units.g; input density [float]: 0.755 * units.kg / units.L; input molecular_afr [float]: 25 / 2.0; input turbulence_to_flame_speed_ratio [function]: turbulence_to_flame_speed_ratio_default(); input max_burning_efficiency [float]: 0.8; input burning_efficiency_randomness [float]: 0.5; input low_efficiency_attenuation [float]: 0.6; input max_turbulence_effect [float]: 2.0; input max_dilution_effect [float]: 10.0; alias output __out [fuel_channel]; } // Engine public node engine_parameters { input name: ""; input redline: 6000 * units.rpm; input starter_speed: 200 * units.rpm; input starter_torque: 200 * units.lb_ft; input fuel: fuel(); } private node _engine => __engine_sim__engine { input name [string]; input redline [float]; input starter_speed [float]; input starter_torque [float]; input dyno_min_speed [float]; input dyno_max_speed [float]; input dyno_hold_step [float]; input fuel [fuel]; input throttle [throttle_channel]; input simulation_frequency [float]; input hf_gain [float]; input jitter [float]; input noise [float]; alias output __out [engine_channel]; } public node engine { input params: engine_parameters(); input name: params.name; input redline: params.redline; input starter_speed: params.starter_speed; input starter_torque: params.starter_torque; input dyno_min_speed: 1000 * units.rpm; input dyno_max_speed: redline; input dyno_hold_step: 100 * units.rpm; input fuel: params.fuel; input throttle_gamma: 1.0; input throttle: direct_throttle_linkage(gamma: throttle_gamma); input simulation_frequency: 10000; input hf_gain: 0.01; input jitter: 0.5; input noise: 1.0; alias output __out [_engine]: _engine( name: name, redline: redline, starter_speed: starter_speed, starter_torque: starter_torque, dyno_min_speed: dyno_min_speed, dyno_max_speed: dyno_max_speed, dyno_hold_step: dyno_hold_step, fuel: fuel, throttle: throttle, simulation_frequency: simulation_frequency, hf_gain: hf_gain, jitter: jitter, noise: noise ); } public node direct_throttle_linkage => __engine_sim__direct_throttle_linkage { input gamma [float]: 1.0; alias output __out [throttle_channel]; } public node governor => __engine_sim__governor { input min_speed [float]: 1.0; input max_speed [float]: 1.0; input min_v [float]: -2.0; input max_v [float]: 2.0; input k_s [float]: 1.0; input k_d [float]: 300.0; input gamma [float]: 0.1; alias output __out [throttle_channel]; } // Crankshaft public node crankshaft_parameter_defaults { input throw: 0.0; input flywheel_mass: 0.0; input mass: 0.0; input friction_torque: 0.0; input moment_of_inertia: 0.0; input position_x: 0.0; input position_y: 0.0; input tdc: 0.0; } private node _crankshaft => __engine_sim__crankshaft { input throw [float]: 0.0; input flywheel_mass [float]: 0.0; input mass [float]: 0.0; input friction_torque [float]: 0.0; input moment_of_inertia [float]: 0.0; input position_x [float]: 0.0; input position_y [float]: 0.0; input tdc [float]: 0.0; alias output __out [crankshaft_channel]; } public node crankshaft { input params: crankshaft_parameter_defaults(); input throw: params.throw; input flywheel_mass: params.flywheel_mass; input mass: params.mass; input friction_torque: params.friction_torque; input moment_of_inertia: params.moment_of_inertia; input position_x: params.position_x; input position_y: params.position_y; input tdc: params.tdc; alias output __out [_crankshaft]: _crankshaft( throw: throw, flywheel_mass: flywheel_mass, mass: mass, friction_torque: friction_torque, moment_of_inertia: moment_of_inertia, position_x: position_x, position_y: position_y, tdc: tdc ); } // Rod Journal public node rod_journal => __engine_sim__rod_journal { input angle [float]: 0; alias output __out [rod_journal_channel]; } // Connecting Rod private node connecting_rod_parameter_defaults { input mass: 0.0; input moment_of_inertia: 0.0; input center_of_mass: 0.0; input length: 0.0; input slave_throw: 0.0; } public node connecting_rod_parameters { input copy: connecting_rod_parameter_defaults(); input mass: copy.mass; input moment_of_inertia: copy.moment_of_inertia; input center_of_mass: copy.center_of_mass; input length: copy.length; input slave_throw: copy.slave_throw; } private node _connecting_rod => __engine_sim__connecting_rod { input mass [float]; input moment_of_inertia [float]; input center_of_mass [float]; input length [float]; input slave_throw [float]; alias output __out [connecting_rod_channel]; } public node connecting_rod { input params: connecting_rod_parameters(); alias output __out [_connecting_rod]: _connecting_rod( mass: params.mass, moment_of_inertia: params.moment_of_inertia, center_of_mass: params.center_of_mass, length: params.length, slave_throw: params.slave_throw ); } // Piston public node piston_parameters { input blowby: 0.0; input compression_height: 0.0; input wrist_pin_position: 0.0; input wrist_pin_location: 0.0; input displacement: 0.0; input mass: 0.0; } private node _piston => __engine_sim__piston { input mass [float]: 0.0; input blowby [float]: 0.0; input compression_height [float]: 0.0; input wrist_pin_position [float]: 0.0; input displacement [float]: 0.0; alias output __out [piston_channel]; } public node piston { input params: piston_parameters(); input blowby: params.blowby; alias output __out [_piston]: _piston( mass: params.mass, blowby: blowby, compression_height: params.compression_height, wrist_pin_position: params.wrist_pin_position, displacement: params.displacement ); } // Cylinder Bank public node cylinder_bank_parameters { input angle: 0.0; input bore: 0.0; input deck_height: 0.0; input position_x: 0.0; input position_y: 0.0; input display_depth: 0.5; } private node _cylinder_bank => __engine_sim__cylinder_bank { input angle [float]: 0.0; input bore [float]: 0.0; input deck_height [float]: 0.0; input position_x [float]: 0.0; input position_y [float]: 0.0; input display_depth [float]: 0.6; alias output __out [cylinder_bank_channel]; } public node cylinder_bank { input parameters: cylinder_bank_parameters(); input angle: parameters.angle; input bore: parameters.bore; input deck_height: parameters.deck_height; input position_x: parameters.position_x; input position_y: parameters.position_y; input display_depth: parameters.display_depth; alias output __out [_cylinder_bank]: _cylinder_bank( angle: angle, bore: bore, deck_height: deck_height, position_x: position_x, position_y: position_y, display_depth: display_depth ); } // Function public node function => __engine_sim__function { input filter_radius [float]: 1.0; alias output __out [function_channel]; } // Cylinder public node cylinder_friction_parameter_defaults { output friction_k: 0.06; output breakaway_friction: 0.0; output breakaway_friction_velocity: 0.0; output viscous_friction_coefficient: 0.0; } public node cylinder_friction_parameters { input copy: cylinder_friction_parameter_defaults(); input friction_k: copy.friction_k; input breakaway_friction: copy.breakaway_friction; input breakaway_friction_velocity: copy.breakaway_friction_velocity; input viscous_friction_coefficient: copy.viscous_friction_coefficient; } // Valvetrain public node standard_valvetrain => __engine_sim__standard_valvetrain { input intake_camshaft [camshaft]; input exhaust_camshaft [camshaft]; alias output __out [valvetrain_channel]; } public node vtec_valvetrain => __engine_sim__vtec_valvetrain { input vtec_intake_camshaft [camshaft]; input vtec_exhaust_camshaft [camshaft]; input intake_camshaft [camshaft]; input exhaust_camshaft [camshaft]; input min_rpm [float]: 5800 * units.rpm; input min_speed [float]: 10 * units.mph; input manifold_vacuum [float]: 1.0 * units.atm - 5.0 * units.inHg; input min_throttle_position [float]: 0.3; alias output __out [valvetrain_channel]; } // Cylinder Head public node cylinder_head_parameters { input intake_port_flow: function(); input exhaust_port_flow: function(); input chamber_volume: 118.0 * units.cc; input intake_runner_volume: 300.0 * units.cc; input intake_runner_cross_section_area: circle_area(0.75 * units.inch); input exhaust_runner_volume: 300.0 * units.cc; input exhaust_runner_cross_section_area: circle_area(0.85 * units.inch); input flip_display: false; } private node _cylinder_head => __engine_sim__cylinder_head { input intake_port_flow [function]; input exhaust_port_flow [function]; input valvetrain [valvetrain_channel]; input chamber_volume [float]; input intake_runner_volume [float]; input intake_runner_cross_section_area [float]; input exhaust_runner_volume [float]; input exhaust_runner_cross_section_area [float]; input flip_display [bool]; alias output __out [cylinder_head_channel]; } public node generic_cylinder_head { input parameters: cylinder_head_parameters(); input valvetrain; input intake_port_flow: parameters.intake_port_flow; input exhaust_port_flow: parameters.exhaust_port_flow; input chamber_volume: parameters.chamber_volume; input intake_runner_volume: parameters.intake_runner_volume; input intake_runner_cross_section_area: parameters.intake_runner_cross_section_area; input exhaust_runner_volume: parameters.exhaust_runner_volume; input exhaust_runner_cross_section_area: parameters.exhaust_runner_cross_section_area; input flip_display: parameters.flip_display; alias output __out [_cylinder_head]: _cylinder_head( intake_port_flow: intake_port_flow, exhaust_port_flow: exhaust_port_flow, chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, flip_display: flip_display, valvetrain: valvetrain ); } public node cylinder_head { input parameters: cylinder_head_parameters(); input intake_camshaft; input exhaust_camshaft; input intake_port_flow: parameters.intake_port_flow; input exhaust_port_flow: parameters.exhaust_port_flow; input chamber_volume: parameters.chamber_volume; input intake_runner_volume: parameters.intake_runner_volume; input intake_runner_cross_section_area: parameters.intake_runner_cross_section_area; input exhaust_runner_volume: parameters.exhaust_runner_volume; input exhaust_runner_cross_section_area: parameters.exhaust_runner_cross_section_area; input flip_display: parameters.flip_display; alias output __out [_cylinder_head]: generic_cylinder_head( intake_port_flow: intake_port_flow, exhaust_port_flow: exhaust_port_flow, chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, flip_display: flip_display, valvetrain: standard_valvetrain( intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft ) ); } // Camshaft public node camshaft_parameters { input advance: 0.0; input base_radius: 0.0; input lobe_profile: function(); } private node _camshaft => __engine_sim__camshaft { input advance [float]; input base_radius [float]; input lobe_profile [function]; alias output __out [camshaft_channel]; } public node camshaft { input parameters: camshaft_parameters(); input advance: parameters.advance; input base_radius: parameters.base_radius; input lobe_profile: parameters.lobe_profile; alias output __out [_camshaft]: _camshaft( advance: advance, base_radius: base_radius, lobe_profile: lobe_profile ); } // Intake public node intake_parameters { input plenum_volume: 2.0 * units.L; input plenum_cross_section_area: 100.0 * units.cm2; input intake_flow_rate: 0.0; input idle_flow_rate: 0.0; input molecular_afr: (25.0 / 2.0); input idle_throttle_plate_position: 0.975; input throttle_gamma: 2.0; input runner_length: 4.0 * units.inch; input runner_flow_rate: k_carb(200.0); input velocity_decay: 0.25; } private node _intake => __engine_sim__intake { input plenum_volume [float]; input plenum_cross_section_area [float]; input intake_flow_rate [float]; input idle_flow_rate [float]; input runner_flow_rate [float]; input molecular_afr [float]; input idle_throttle_plate_position [float]; input throttle_gamma [float]; input runner_length [float]; input velocity_decay [float]; alias output __out [intake_channel]; } public node intake { input parameters: intake_parameters(); input plenum_volume: parameters.plenum_volume; input plenum_cross_section_area: parameters.plenum_cross_section_area; input intake_flow_rate: parameters.intake_flow_rate; input idle_flow_rate: parameters.idle_flow_rate; input runner_flow_rate: parameters.runner_flow_rate; input molecular_afr: parameters.molecular_afr; input idle_throttle_plate_position: parameters.idle_throttle_plate_position; input throttle_gamma: parameters.throttle_gamma; input runner_length: parameters.runner_length; input velocity_decay: parameters.velocity_decay; alias output __out [_intake]: _intake( plenum_volume: plenum_volume, plenum_cross_section_area: plenum_cross_section_area, intake_flow_rate: intake_flow_rate, idle_flow_rate: idle_flow_rate, runner_flow_rate: runner_flow_rate, molecular_afr: molecular_afr, idle_throttle_plate_position: idle_throttle_plate_position, throttle_gamma: throttle_gamma, runner_length: runner_length, velocity_decay: velocity_decay ); } // Exhaust System public node impulse_response => __engine_sim__impulse_response { input filename [string]; input volume [float]: 1.0; alias output __out [impulse_response_channel]; } public node exhaust_system_parameters { input volume: 100.0 * units.L; input length: volume / collector_cross_section_area; input collector_cross_section_area: circle_area(2.0 * units.inch); input outlet_flow_rate: k_carb(1000.0); input primary_tube_length: 10.0 * units.inch; input primary_flow_rate: k_carb(100.0); input audio_volume: 1.0; input velocity_decay: 1.0; } private node _exhaust_system => __engine_sim__exhaust_system { input length [float]; input collector_cross_section_area [float]; input outlet_flow_rate [float]; input primary_tube_length [float]; input primary_flow_rate [float]; input audio_volume [float]; input velocity_decay [float]; input impulse_response [impulse_response]; alias output __out [exhaust_system_channel]; } public node exhaust_system { input parameters: exhaust_system_parameters(); input length: parameters.length; input collector_cross_section_area: parameters.collector_cross_section_area; input outlet_flow_rate: parameters.outlet_flow_rate; input primary_tube_length: parameters.primary_tube_length; input primary_flow_rate: parameters.primary_flow_rate; input audio_volume: parameters.audio_volume; input velocity_decay: parameters.velocity_decay; input impulse_response; alias output __out [_exhaust_system]: _exhaust_system( length: length, collector_cross_section_area: collector_cross_section_area, outlet_flow_rate: outlet_flow_rate, primary_tube_length: primary_tube_length, primary_flow_rate: primary_flow_rate, audio_volume: audio_volume, velocity_decay: velocity_decay, impulse_response: impulse_response ); } // Ignition Module public node ignition_module => __engine_sim__ignition_module { input timing_curve [function]; input rev_limit [float]: 7000.0 * units.rpm; input limiter_duration [float]: 0.5 * units.sec; alias output __out [ignition_module_channel]; } public node ignition_wire => __engine_sim__ignition_wire { alias output __out [ignition_wire_channel]; } public node vehicle => __engine_sim__vehicle { input mass [float]: 1000 * units.kg; input drag_coefficient [float]: 0.25; input cross_sectional_area [float]: (72 * units.inch) * (72 * units.inch); input diff_ratio [float]: 3.42; input tire_radius [float]: 10 * units.inch; input rolling_resistance [float]: 2000; alias output __out [vehicle_channel]; } public node transmission => __engine_sim__transmission { input max_clutch_torque [float]: 1000 * units.lb_ft; alias output __out [transmission_channel]; } ================================================ FILE: es/part-library/part_library.mr ================================================ public import "parts/cam_lobes.mr" public import "parts/camshafts.mr" public import "parts/heads.mr" public import "parts/intakes.mr" public import "parts/ignition_modules.mr" ================================================ FILE: es/part-library/parts/cam_lobes.mr ================================================ private import "engine_sim.mr" units units() private node add_sym_sample { input angle; input lift; input this; alias output __out: this; this.add_sample(angle * units.deg, lift * units.thou) this.add_sample(-angle * units.deg, lift * units.thou) } public node stock_454_intake_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 194 * units.deg, gamma: 0.8, lift: 390 * units.thou, steps: 100 ); } public node stock_454_exhaust_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 202 * units.deg, gamma: 0.8, lift: 409 * units.thou, steps: 100 ); } public node comp_cams_magnum_11_450_8_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 232 * units.deg, gamma: 0.75, lift: 578 * units.thou, steps: 100 ); } public node comp_cams_magnum_11_470_8_lobe_profile { alias output __out: harmonic_cam_lobe( duration_at_50_thou: 252 * units.deg, gamma: 0.8, lift: 612 * units.thou, steps: 100 ); } ================================================ FILE: es/part-library/parts/camshafts.mr ================================================ private import "cam_lobes.mr" private import "engine_sim.mr" units units() public node chevy_bbc_camshaft_builder { input lobe_profile: stock_454_intake_lobe_profile(); input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam_0: _intake_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_0: _exhaust_cam_0; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot90(90 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center) .add_lobe(rot360 - exhaust_lobe_center + 3 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 5 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 6 * rot90) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + 7 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 2 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 4 * rot90) .add_lobe(rot360 - exhaust_lobe_center + 1 * rot90) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center) .add_lobe(rot360 + intake_lobe_center + 3 * rot90) .add_lobe(rot360 + intake_lobe_center + 5 * rot90) .add_lobe(rot360 + intake_lobe_center + 6 * rot90) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + 7 * rot90) .add_lobe(rot360 + intake_lobe_center + 2 * rot90) .add_lobe(rot360 + intake_lobe_center + 4 * rot90) .add_lobe(rot360 + intake_lobe_center + 1 * rot90) } public node vtwin_camshaft_builder { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; input angle: 90 * 3 * units.deg; output intake_cam_0: _intake_cam_0; output intake_cam_1: _intake_cam_1; output exhaust_cam_0: _exhaust_cam_0; output exhaust_cam_1: _exhaust_cam_1; camshaft_parameters params( advance: advance, base_radius: base_radius ) camshaft _intake_cam_0(params, lobe_profile: intake_lobe_profile) camshaft _intake_cam_1(params, lobe_profile: intake_lobe_profile) camshaft _exhaust_cam_0(params, lobe_profile: exhaust_lobe_profile) camshaft _exhaust_cam_1(params, lobe_profile: exhaust_lobe_profile) label rot90(90 * units.deg) label rot360(360 * units.deg) _exhaust_cam_0 .add_lobe(rot360 - exhaust_lobe_center) _exhaust_cam_1 .add_lobe(rot360 - exhaust_lobe_center + angle) _intake_cam_0 .add_lobe(rot360 + intake_lobe_center) _intake_cam_1 .add_lobe(rot360 + intake_lobe_center + angle) } public node vtwin90_camshaft_builder { input lobe_profile; input intake_lobe_profile: lobe_profile; input exhaust_lobe_profile: lobe_profile; input lobe_separation: 114.0 * units.deg; input intake_lobe_center: lobe_separation; input exhaust_lobe_center: lobe_separation; input advance: 0.0 * units.deg; input base_radius: 0.75 * units.inch; output intake_cam_0: camshaft.intake_cam_0; output intake_cam_1: camshaft.intake_cam_1; output exhaust_cam_0: camshaft.exhaust_cam_0; output exhaust_cam_1: camshaft.exhaust_cam_1; vtwin_camshaft_builder camshaft( lobe_profile: lobe_profile, intake_lobe_profile: intake_lobe_profile, exhaust_lobe_profile: exhaust_lobe_profile, lobe_separation: lobe_separation, intake_lobe_center: intake_lobe_center, exhaust_lobe_center: exhaust_lobe_center, advance: advance, base_radius: base_radius, angle: 90 * 3 * units.deg ) } public node chevy_454_stock_camshaft { alias output __out: chevy_bbc_camshaft_builder( advance: 0 * units.deg, intake_lobe_profile: stock_454_intake_lobe_profile(), exhaust_lobe_profile: stock_454_exhaust_lobe_profile(), intake_lobe_center: 108 * units.deg, exhaust_lobe_center: 113 * units.deg); } public node comp_cams_magnum_11_450_8 { alias output __out: chevy_bbc_camshaft_builder( lobe_profile: comp_cams_magnum_11_450_8_lobe_profile(), lobe_separation: 110 * units.deg, advance: 4.0 * units.deg, base_radius: 1000.0 * units.thou); } public node comp_cams_magnum_11_470_8 { alias output __out: chevy_bbc_camshaft_builder( lobe_profile: comp_cams_magnum_11_470_8_lobe_profile(), lobe_separation: 110 * units.deg, advance: 4.0 * units.deg, base_radius: 1000.0 * units.thou); } ================================================ FILE: es/part-library/parts/heads.mr ================================================ private import "engine_sim.mr" units units() public node add_flow_sample { input lift; input flow; input this; alias output __out: this; this.add_sample(lift * units.thou, k_28inH2O(flow)) } public node chevy_bbc_peanut_port_head { input intake_camshaft; input exhaust_camshaft; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 75) .add_flow_sample(150, 100) .add_flow_sample(200, 130) .add_flow_sample(250, 180) .add_flow_sample(300, 190) .add_flow_sample(350, 220) .add_flow_sample(400, 240) .add_flow_sample(450, 250) .add_flow_sample(500, 260) .add_flow_sample(550, 260) .add_flow_sample(600, 260) .add_flow_sample(650, 255) .add_flow_sample(700, 250) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 50) .add_flow_sample(150, 75) .add_flow_sample(200, 100) .add_flow_sample(250, 125) .add_flow_sample(300, 160) .add_flow_sample(350, 175) .add_flow_sample(400, 180) .add_flow_sample(450, 190) .add_flow_sample(500, 200) .add_flow_sample(550, 205) .add_flow_sample(600, 210) .add_flow_sample(650, 210) .add_flow_sample(700, 210) cylinder_head head( chamber_volume: 118.0 * units.cc, intake_runner_volume: 189.0 * units.cc, intake_runner_cross_section_area: 37.8 * units.cm2, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } public node edelbrock_6055_rectangle_port { input intake_camshaft; input exhaust_camshaft; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 76) .add_flow_sample(150, 100) .add_flow_sample(200, 146) .add_flow_sample(250, 175) .add_flow_sample(300, 212) .add_flow_sample(350, 230) .add_flow_sample(400, 255) .add_flow_sample(450, 275) .add_flow_sample(500, 294) .add_flow_sample(550, 300) .add_flow_sample(600, 314) .add_flow_sample(650, 314) .add_flow_sample(700, 314) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0, 0) .add_flow_sample(50, 25) .add_flow_sample(100, 70) .add_flow_sample(150, 100) .add_flow_sample(200, 132) .add_flow_sample(250, 140) .add_flow_sample(300, 156) .add_flow_sample(350, 170) .add_flow_sample(400, 181) .add_flow_sample(450, 191) .add_flow_sample(500, 207) .add_flow_sample(550, 214) .add_flow_sample(600, 228) .add_flow_sample(650, 228) .add_flow_sample(700, 228) cylinder_head head( chamber_volume: 118.0 * units.cc, intake_runner_volume: 315.0 * units.cc, intake_runner_cross_section_area: 78.75 * units.cm2, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } public node generic_small_engine_head { input intake_camshaft; input exhaust_camshaft; input chamber_volume: 100.0 * units.cc; input intake_runner_volume: 100.0 * units.cc; input intake_runner_cross_section_area: 30.0 * units.cm2; input exhaust_runner_volume: 100.0 * units.cc; input exhaust_runner_cross_section_area: 30.0 * units.cm2; input flow_attenuation: 1.0; input lift_scale: 1.0; input flip_display: false; alias output __out: head; function intake_flow(50 * units.thou) intake_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 25 * flow_attenuation) .add_flow_sample(100 * lift_scale, 75 * flow_attenuation) .add_flow_sample(150 * lift_scale, 100 * flow_attenuation) .add_flow_sample(200 * lift_scale, 130 * flow_attenuation) .add_flow_sample(250 * lift_scale, 180 * flow_attenuation) .add_flow_sample(300 * lift_scale, 190 * flow_attenuation) .add_flow_sample(350 * lift_scale, 220 * flow_attenuation) .add_flow_sample(400 * lift_scale, 240 * flow_attenuation) .add_flow_sample(450 * lift_scale, 250 * flow_attenuation) .add_flow_sample(500 * lift_scale, 260 * flow_attenuation) .add_flow_sample(550 * lift_scale, 260 * flow_attenuation) .add_flow_sample(600 * lift_scale, 260 * flow_attenuation) .add_flow_sample(650 * lift_scale, 255 * flow_attenuation) .add_flow_sample(700 * lift_scale, 250 * flow_attenuation) function exhaust_flow(50 * units.thou) exhaust_flow .add_flow_sample(0 * lift_scale, 0 * flow_attenuation) .add_flow_sample(50 * lift_scale, 25 * flow_attenuation) .add_flow_sample(100 * lift_scale, 50 * flow_attenuation) .add_flow_sample(150 * lift_scale, 75 * flow_attenuation) .add_flow_sample(200 * lift_scale, 100 * flow_attenuation) .add_flow_sample(250 * lift_scale, 125 * flow_attenuation) .add_flow_sample(300 * lift_scale, 160 * flow_attenuation) .add_flow_sample(350 * lift_scale, 175 * flow_attenuation) .add_flow_sample(400 * lift_scale, 180 * flow_attenuation) .add_flow_sample(450 * lift_scale, 190 * flow_attenuation) .add_flow_sample(500 * lift_scale, 200 * flow_attenuation) .add_flow_sample(550 * lift_scale, 205 * flow_attenuation) .add_flow_sample(600 * lift_scale, 210 * flow_attenuation) .add_flow_sample(650 * lift_scale, 210 * flow_attenuation) .add_flow_sample(700 * lift_scale, 210 * flow_attenuation) cylinder_head head( chamber_volume: chamber_volume, intake_runner_volume: intake_runner_volume, intake_runner_cross_section_area: intake_runner_cross_section_area, exhaust_runner_volume: exhaust_runner_volume, exhaust_runner_cross_section_area: exhaust_runner_cross_section_area, intake_port_flow: intake_flow, exhaust_port_flow: exhaust_flow, intake_camshaft: intake_camshaft, exhaust_camshaft: exhaust_camshaft, flip_display: flip_display ) } ================================================ FILE: es/part-library/parts/ignition_modules.mr ================================================ private import "engine_sim.mr" units units() label cycle(2 * 360 * units.deg) public node chevy_bbc_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0 / 8.0) * cycle) .connect_wire(wires.wire8, (1.0 / 8.0) * cycle) .connect_wire(wires.wire4, (2.0 / 8.0) * cycle) .connect_wire(wires.wire3, (3.0 / 8.0) * cycle) .connect_wire(wires.wire6, (4.0 / 8.0) * cycle) .connect_wire(wires.wire5, (5.0 / 8.0) * cycle) .connect_wire(wires.wire7, (6.0 / 8.0) * cycle) .connect_wire(wires.wire2, (7.0 / 8.0) * cycle); } public node chevy_sbc_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: chevy_bbc_distributor( wires: wires, timing_curve: timing_curve, rev_limit: rev_limit ); } public node vtwin90_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0 / 8.0) * cycle) .connect_wire(wires.wire2, (3.0 / 8.0) * cycle); } public node single_cylinder_distributor { input wires; input timing_curve; input rev_limit: 5500 * units.rpm; alias output __out: ignition_module(timing_curve: timing_curve, rev_limit: rev_limit) .connect_wire(wires.wire1, (0.0 / 8.0) * cycle); } ================================================ FILE: es/part-library/parts/intakes.mr ================================================ private import "engine_sim.mr" units units() public node chevy_bbc_stock_intake { input carburetor_cfm: 650.0; input idle_flow_rate_cfm: 1.0; input idle_throttle_plate_position: 0.975; input throttle_gamma: 2.0; alias output __out: intake; intake intake( plenum_volume: 2.0 * units.L, plenum_cross_section_area: 100.0 * units.cm2, intake_flow_rate: k_carb(carburetor_cfm), idle_flow_rate: k_carb(idle_flow_rate_cfm), idle_throttle_plate_position: idle_throttle_plate_position, throttle_gamma: throttle_gamma, runner_flow_rate: k_carb(300.0), runner_length: 6.0 * units.inch, velocity_decay: 1.0 ) } public node performer_rpm_intake { input carburetor_cfm: 650.0; input idle_flow_rate_cfm: 1.0; input idle_throttle_plate_position: 0.975; input throttle_gamma: 2.0; alias output __out: intake; intake intake( plenum_volume: 2.0 * units.L, plenum_cross_section_area: 100.0 * units.cm2, intake_flow_rate: k_carb(carburetor_cfm), idle_flow_rate: k_carb(idle_flow_rate_cfm), idle_throttle_plate_position: idle_throttle_plate_position, throttle_gamma: throttle_gamma, runner_flow_rate: k_carb(500.0), runner_length: 6.0 * units.inch, velocity_decay: 0.1 ) } ================================================ FILE: es/settings/application_settings.mr ================================================ private import "engine_sim.mr" units units() public node set_application_settings => __engine_sim__set_application_settings { input start_fullscreen [bool]: false; input power_units [string]: "HP"; input torque_units [string]: "FTLBS"; input speed_units [string]: "MPH"; input pressure_units [string]: "INHG"; input boost_units [string]: "PSI"; input color_background [int]: 0x0E1012; input color_foreground [int]: 0xFFFFFF; input color_shadow [int]: 0x0E1012; input color_highlight1 [int]: 0xEF4545; input color_highlight2 [int]: 0xFFFFFF; input color_pink [int]: 0xF394BE; input color_red [int]: 0xEE4445; input color_orange [int]: 0xF4802A; input color_yellow [int]: 0xFDBD2E; input color_blue [int]: 0x77CEE0; input color_green [int]: 0xBDD869; } ================================================ FILE: es/sound-library/impulse_responses.mr ================================================ private import "engine_sim.mr" public node impulse_response_library { output default_0: impulse_response(filename: "smooth/smooth_39.wav", volume: 0.001); output real_engine_0: impulse_response(filename: "archive/test_engine_14_eq_adjusted_16.wav", volume: 0.001); output real_engine_1: impulse_response(filename: "archive/test_engine_15_eq_adjusted_16.wav", volume: 0.001); output real_engine_2: impulse_response(filename: "archive/test_engine_16_eq_adjusted_16.wav", volume: 0.001); output sharp_0: impulse_response(filename: "sharp/sharp_01.wav", volume: 0.001); output mild_exhaust_0: impulse_response(filename: "new/mild_exhaust.wav", volume: 0.01); output mild_exhaust_0_reverb: impulse_response(filename: "new/mild_exhaust_reverb.wav", volume: 0.01); output minimal_muffling_01: impulse_response(filename: "new/minimal_muffling_01.wav", volume: 0.01); output minimal_muffling_02: impulse_response(filename: "new/minimal_muffling_02.wav", volume: 0.01); output minimal_muffling_03: impulse_response(filename: "new/minimal_muffling_03.wav", volume: 0.01); } ================================================ FILE: es/types/atomic_types.mr ================================================ module { @name: "Atomic Types" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } // ======================================================== // Channels // ======================================================== @doc: "Floating-point channel type" private node float_channel => __engine_sim__float { /* void */ } @doc: "String channel type" private node string_channel => __engine_sim__string { /* void */ } @doc: "Integer channel type" private node int_channel => __engine_sim__int { /* void */ } @doc: "Bool channel type" private node bool_channel => __engine_sim__bool { /* void */ } // ======================================================== // Types // ======================================================== @doc: "Float cast type" @detail: "Converts anything connected to __in to " "a float type" public inline node float { input __in [::float_channel]: 0.0; alias output __out [::float_channel]: __in; } @doc: "Integer cast type" @detail: "Converts anything connected to __in to " "an integer type" public inline node int { input __in [::int_channel]: 0; alias output __out [::int_channel]: __in; } @doc: "Boolean cast type" @detail: "Converts anything connected to __in to " "a boolean type" public inline node bool { input __in [::bool_channel]: false; alias output __out [::bool_channel]: __in; } @doc: "String type" public inline node string { input s [::string_channel]: ""; alias output __out [::string_channel]: s; } // ======================================================== // Literals // ======================================================== public node literal_string => __engine_sim__literal_string { alias output __out [::string]; } public node literal_float => __engine_sim__literal_float { alias output __out [::float]; } public node literal_int => __engine_sim__literal_int { alias output __out [::int]; } public node literal_bool => __engine_sim__literal_bool { alias output __out [::bool]; } ================================================ FILE: es/types/conversions.mr ================================================ module { @name: "Conversions" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } private import "atomic_types.mr" // Float conversions public node int_to_float => __engine_sim__int_to_float { input __in [int]; alias output __out [float]; } // String conversions public node int_to_string => __engine_sim__int_to_string { input __in [int]; alias output __out [string]; } // Integer conversions public node string_to_int => __engine_sim__string_to_int { input __in [string]; alias output __out [int]; } ================================================ FILE: es/types/operations.mr ================================================ module { @name: "Operations" @author: "ATG (Ange Yaghi)" @copyright: "Copyright 2022, Ange Yaghi" } private import "atomic_types.mr" private import "conversions.mr" // Float operations public node float_negate => __engine_sim__float_negate { input __in [float]; alias output __out [float]; } public node float_divide => __engine_sim__float_divide { input __in0 [float]; input __in1 [float]; alias output __out [float]; } public node float_multiply => __engine_sim__float_multiply { input __in0 [float]; input __in1 [float]; alias output __out [float]; } public node float_add => __engine_sim__float_add { input __in0 [float]; input __in1 [float]; alias output __out [float]; } public node float_subtract => __engine_sim__float_subtract { input __in0 [float]; input __in1 [float]; alias output __out [float]; } // String operations public node string_add => __engine_sim__string_add { input __in0 [string]; input __in1 [string]; alias output __out [string]; } // Int operations public node int_add => __engine_sim__int_add { input __in0 [int]; input __in1 [int]; alias output __out [int]; } public node int_mul => __engine_sim__int_multiply { input __in0 [int]; input __in1 [int]; alias output __out [int]; } public node int_sub => __engine_sim__int_subtract { input __in0 [int]; input __in1 [int]; alias output __out [int]; } public node int_div => __engine_sim__int_divide { input __in0 [int]; input __in1 [int]; alias output __out [int]; } public node int_negate => __engine_sim__int_negate { input __in [int]; alias output __out [int]; } ================================================ FILE: es/utilities/utilities.mr ================================================ private import "engine_sim.mr" node rod_moment_of_inertia { input mass; input length; alias output __moment: (1 / 12.0) * mass * length * length; } node disk_moment_of_inertia { input mass; input radius; alias output __moment: (1 / 2.0) * mass * radius * radius; } ================================================ FILE: include/adaptive_volume_filter.h ================================================ #ifndef ATG_ENGINE_SIM_ADAPTIVE_VOLUME_FILTER_H #define ATG_ENGINE_SIM_ADAPTIVE_VOLUME_FILTER_H #include "filter.h" class AdaptiveVolumeFilter : public Filter { public: AdaptiveVolumeFilter(); virtual ~AdaptiveVolumeFilter(); void initialize(int lookahead); virtual double f(double sample); virtual void destroy(); protected: double *m_samples; int m_sampleCount; int m_lookahead; }; #endif /* ATG_ENGINE_SIM_CONVOLUTION_ADAPTIVE_VOLUME */ ================================================ FILE: include/afr_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_AFR_CLUSTER_H #define ATG_ENGINE_SIM_AFR_CLUSTER_H #include "ui_element.h" #include "engine.h" #include "gauge.h" #include "cylinder_temperature_gauge.h" #include "cylinder_pressure_gauge.h" #include "labeled_gauge.h" #include "throttle_display.h" class AfrCluster : public UiElement { public: AfrCluster(); virtual ~AfrCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Engine *m_engine; protected: LabeledGauge *m_intakeAfrGauge; LabeledGauge *m_exhaustAfrGauge; }; #endif /* ATG_ENGINE_SIM_AFR_CLUSTER_H */ ================================================ FILE: include/application_settings.h ================================================ #ifndef ATG_ENGINE_SIM_APPLICATION_SETTINGS_H #define ATG_ENGINE_SIM_APPLICATION_SETTINGS_H #include struct ApplicationSettings { bool startFullscreen = false; std::string powerUnits = "hp"; std::string torqueUnits = "lb-ft"; std::string speedUnits = "mph"; std::string pressureUnits = "inHg"; std::string boostUnits = "psi"; int colorBackground = 0x0E1012; int colorForeground = 0xFFFFFF; int colorShadow = 0x0E1012; int colorHighlight1 = 0xEF4545; int colorHighlight2 = 0xFFFFFF; int colorPink = 0xF394BE; int colorRed = 0xEE4445; int colorOrange = 0xF4802A; int colorYellow = 0xFDBD2E; int colorBlue = 0x77CEE0; int colorGreen = 0xBDD869; }; #endif /* ATG_ENGINE_SIM_APPLICATION_SETTINGS_H */ ================================================ FILE: include/audio_buffer.h ================================================ #ifndef ATG_ENGINE_SIM_AUDIO_BUFFER_H #define ATG_ENGINE_SIM_AUDIO_BUFFER_H #include "scs.h" #include class AudioBuffer { public: AudioBuffer(); ~AudioBuffer(); void initialize(int sampleRate, int bufferSize); void destroy(); inline double offsetToTime(int offset) const { return offset * m_offsetToSeconds; } inline double timeDelta(int offset0, int offset1) const { if (offset1 == offset0) return 0; else if (offset1 < offset0) { return offsetToTime((m_bufferSize - offset0) + offset1); } else { return offsetToTime(offset1 - offset0); } } inline int offsetDelta(int offset0, int offset1) const { if (offset1 == offset0) return 0; else if (offset1 < offset0) { return (m_bufferSize - offset0) + offset1; } else { return offset1 - offset0; } } inline void writeSample(int16_t sample, int offset, int index = 0) { m_samples[getBufferIndex(offset, index)] = sample; } inline int16_t readSample(int offset, int index) const { return m_samples[getBufferIndex(offset, index)]; } inline void commitBlock(int length) { m_writePointer = getBufferIndex(m_writePointer, length); } inline int getBufferIndex(int offset, int index = 0) const { return (((offset + index) % m_bufferSize) + m_bufferSize) % m_bufferSize; } inline void copyBuffer(int16_t *dest, int offset, int length) { const int start = getBufferIndex(offset, 0); const int end = getBufferIndex(offset, length); if (start == end) return; else if (start < end) { memcpy(dest, m_samples + start, length * sizeof(int16_t)); } else { memcpy(dest, m_samples + start, ((size_t)m_bufferSize - start) * sizeof(int16_t)); memcpy(dest + m_bufferSize - start, m_samples, end * sizeof(int16_t)); } } bool checkForDiscontinuitiy(int threshold) const; int m_writePointer; protected: int m_sampleRate; int16_t *m_samples; int m_bufferSize; double m_offsetToSeconds; }; #endif /* ATG_ENGINE_SIM_AUDIO_BUFFER_H */ ================================================ FILE: include/butterworth_low_pass_filter.h ================================================ #ifndef ATG_ENGINE_SIM_BUTTERWORTH_LOW_PASS_FILTER_H #define ATG_ENGINE_SIM_BUTTERWORTH_LOW_PASS_FILTER_H #include "filter.h" #include "constants.h" #include "ring_buffer.h" #include template class ButterworthLowPassFilter : public Filter { public: ButterworthLowPassFilter() { m_y.initialize(4); m_x.initialize(4); for (int i = 0; i < 4; ++i) { m_y.write(0); m_x.write(0); } } virtual ~ButterworthLowPassFilter() { /* void */ } __forceinline T_Real fast_f(T_Real sample) { const T_Real y_prev[4] = { m_y.read(3), m_y.read(2), m_y.read(1), m_y.read(0) }; const T_Real x_prev[4] = { m_x.read(3), m_x.read(2), m_x.read(1), m_x.read(0) }; T_Real const n = m_f_4 / m_a[0] * (sample + 4 * x_prev[0] + 6 * x_prev[1] + 4 * x_prev[2] + x_prev[3]); T_Real const d = -m_a[1] * y_prev[0] - m_a[2] * y_prev[1] - m_a[3] * y_prev[2] - m_a[4] * y_prev[3]; T_Real const y = n + d; m_x.removeBeginning(1); m_x.write(sample); m_y.removeBeginning(1); m_y.write(y); return y; } inline void setCutoffFrequency(T_Real f_c, T_Real sampleRate) { const T_Real f = std::tan(static_cast(constants::pi) * f_c / sampleRate); const T_Real f_2 = f * f; const T_Real f_3 = f_2 * f; const T_Real f_4 = f_2 * f_2; const T_Real m = -2.0 * std::cos(5.0 * constants::pi / 8.0); const T_Real n = -2.0 * std::cos(7.0 * constants::pi / 8.0); m_a[0] = 1.0 + (m + n) * f + (2.0 + n * m) * f_2 + (m + n) * f_3 + f_4; m_a[1] = (-4.0 - 2.0 * (n + m) * f + 2.0 * (m + n) * f_3 + 4.0 * f_4) / m_a[0]; m_a[2] = (6.0 - 2.0 * (2.0 + m * n) * f_2 + 6.0 * f_4) / m_a[0]; m_a[3] = (-4.0 + 2.0 * (m + n) * f - 2.0 * (m + n) * f_3 + 4.0 * f_4) / m_a[0]; m_a[4] = (1.0 - (n + m) * f + (2.0 + m * n) * f_2 - (m + n) * f_3 + f_4) / m_a[0]; m_f_4 = f_4; } protected: RingBuffer m_y; RingBuffer m_x; T_Real m_a[5]; T_Real m_f_4; }; #endif /* ATG_ENGINE_SIM_BUTTERWORTH_LOW_PASS_FILTER_H */ ================================================ FILE: include/camshaft.h ================================================ #ifndef ATG_ENGINE_SIM_CAMSHAFT_H #define ATG_ENGINE_SIM_CAMSHAFT_H #include "part.h" #include "function.h" #include "units.h" class Crankshaft; class Camshaft : public Part { public: struct Parameters { // Number of lobes int lobes; // Camshaft advance in camshaft degrees double advance = 0; // Corresponding crankshaft Crankshaft *crankshaft; // Lobe profile Function *lobeProfile; // Base radius double baseRadius = units::distance(600, units::thou); }; public: Camshaft(); virtual ~Camshaft(); void initialize(const Parameters ¶ms); virtual void destroy(); double valveLift(int lobe) const; double sampleLobe(double theta) const; void setLobeCenterline(int lobe, double crankAngle) { m_lobeAngles[lobe] = crankAngle / 2; } double getLobeCenterline(int lobe) const { return m_lobeAngles[lobe]; } double getAngle() const; Function *getLobeProfile() const { return m_lobeProfile; } double getAdvance() const { return m_advance; } double getBaseRadius() const { return m_baseRadius; } private: Crankshaft *m_crankshaft; Function *m_lobeProfile; double *m_lobeAngles; double m_advance; double m_baseRadius; int m_lobes; }; #endif /* ATG_ENGINE_SIM_CAMSHAFT_H */ ================================================ FILE: include/combustion_chamber.h ================================================ #ifndef ATG_ENGINE_SIM_COMBUSTION_CHAMBER_H #define ATG_ENGINE_SIM_COMBUSTION_CHAMBER_H #include "scs.h" #include "piston.h" #include "gas_system.h" #include "cylinder_head.h" #include "units.h" #include "fuel.h" class Engine; class CombustionChamber : public atg_scs::ForceGenerator { public: struct Parameters { Piston *Piston; CylinderHead *Head; Fuel *Fuel; Function *MeanPistonSpeedToTurbulence; double StartingPressure; double StartingTemperature; double CrankcasePressure; }; struct FlameEvent { double lit_n = 0; double total_n = 0; double percentageLit = 0; double efficiency = 1.0; double flameSpeed = 0.0; double lastVolume = 0.0; double travel_x = 0.0; double travel_y = 0.0; GasSystem::Mix globalMix; }; struct FrictionModelParams { double frictionCoeff = 0.06; double breakawayFriction = units::force(50, units::N); double breakawayFrictionVelocity = units::distance(0.1, units::m); double viscousFrictionCoefficient = units::force(20, units::N); }; public: CombustionChamber(); virtual ~CombustionChamber(); void initialize(const Parameters ¶ms); void destroy(); void setEngine(Engine *engine) { m_engine = engine; } virtual void apply(atg_scs::SystemState *system); CylinderHead *getCylinderHead() const { return m_head; } Piston *getPiston() const { return m_piston; } double getFrictionForce() const; double getVolume() const; double pistonSpeed() const; double calculateMeanPistonSpeed() const; double calculateFiringPressure() const; bool isLit() const { return m_lit; } bool popLitLastFrame(); void ignite(); void update(double dt); void flow(double dt); double lastEventAfr() const; double getLastIterationExhaustFlow() const { return m_exhaustFlow; } void resetLastTimestepExhaustFlow() { m_lastTimestepTotalExhaustFlow = 0; } double getLastTimestepExhaustFlow() const { return m_lastTimestepTotalExhaustFlow; } void resetLastTimestepIntakeFlow() { m_lastTimestepTotalIntakeFlow = 0; } double getLastTimestepIntakeFlow() const { return m_lastTimestepTotalIntakeFlow; } Function *m_meanPistonSpeedToTurbulence; GasSystem m_system; GasSystem m_intakeRunnerAndManifold; GasSystem m_exhaustRunnerAndPrimary; FlameEvent m_flameEvent; bool m_lit; FrictionModelParams m_frictionModel; double m_peakTemperature; double m_nBurntFuel; protected: double calculateFrictionForce(double v) const; void updateCycleStates(); double m_intakeFlowRate; double m_exhaustFlowRate; double m_manifoldToRunnerFlowRate; double m_primaryToCollectorFlowRate; double m_cylinderCrossSectionSurfaceArea; double m_cylinderWidthApproximation; double m_lastTimestepTotalExhaustFlow; double m_lastTimestepTotalIntakeFlow; double m_exhaustFlow; double m_crankcasePressure; double *m_pressure; double *m_pistonSpeed; static constexpr int StateSamples = 256; bool m_litLastFrame; Piston *m_piston; CylinderHead *m_head; Engine *m_engine; Fuel *m_fuel; }; #endif /* ATG_ENGINE_SIM_COMBUSTION_CHAMBER_H */ ================================================ FILE: include/combustion_chamber_object.h ================================================ #ifndef ATG_ENGINE_SIM_COMBUSTION_CHAMBER_OBJECT_H #define ATG_ENGINE_SIM_COMBUSTION_CHAMBER_OBJECT_H #include "simulation_object.h" #include "combustion_chamber.h" #include "geometry_generator.h" class CombustionChamberObject : public SimulationObject { public: CombustionChamberObject(); virtual ~CombustionChamberObject(); virtual void generateGeometry(); virtual void render(const ViewParameters *view); virtual void process(float dt); virtual void destroy(); CombustionChamber *m_chamber; protected: GeometryGenerator::GeometryIndices m_indices; }; #endif /* ATG_ENGINE_SIM_COMBUSTION_CHAMBER_OBJECT_H */ ================================================ FILE: include/connecting_rod.h ================================================ #ifndef ATG_ENGINE_CONNECTING_ROD_H #define ATG_ENGINE_CONNECTING_ROD_H #include "part.h" #include "crankshaft.h" class Piston; class ConnectingRod : public Part { public: struct Parameters { double mass = 0.0; double momentOfInertia = 0.0; double centerOfMass = 0.0; double length = 0.0; int rodJournals = 0; double slaveThrow = 0; Piston *piston = nullptr; Crankshaft *crankshaft = nullptr; ConnectingRod *master = nullptr; int journal = 0; }; public: ConnectingRod(); virtual ~ConnectingRod(); void initialize(const Parameters ¶ms); double getBigEndLocal() const; double getLittleEndLocal() const; void setMaster(ConnectingRod *rod) { m_master = rod; } void setCrankshaft(Crankshaft *crank) { m_crankshaft = crank; } inline int getRodJournalCount() const { return m_rodJournalCount; } void setRodJournalAngle(int i, double angle); void getRodJournalPositionLocal(int i, double *x, double *y); void getRodJournalPositionGlobal(int i, double *x, double *y); double getRodJournalAngle(int i) { return m_rodJournalAngles[i]; } inline double getSlaveThrow() const { return m_slaveThrow; } inline double getCenterOfMass() const { return m_centerOfMass; } inline double getLength() const { return m_length; } inline double getMass() const { return m_m; } inline double getMomentOfInertia() const { return m_I; } inline int getJournal() const { return m_journal; } int getLayer() const; inline ConnectingRod *getMasterRod() const { return m_master; } inline Crankshaft *getCrankshaft() const { return m_crankshaft; } inline Piston *getPiston() const { return m_piston; } protected: double m_centerOfMass; double m_length; double m_m; double m_I; int m_journal; ConnectingRod *m_master; Crankshaft *m_crankshaft; Piston *m_piston; double m_slaveThrow; double *m_rodJournalAngles; int m_rodJournalCount; }; #endif /* ATG_ENGINE_SIM_CONNECTING_ROD_H */ ================================================ FILE: include/connecting_rod_object.h ================================================ #ifndef ATG_ENGINE_SIM_CONNECTING_ROD_OBJECT_H #define ATG_ENGINE_SIM_CONNECTING_ROD_OBJECT_H #include "simulation_object.h" #include "connecting_rod.h" #include "geometry_generator.h" class ConnectingRodObject : public SimulationObject { public: ConnectingRodObject(); virtual ~ConnectingRodObject(); virtual void generateGeometry(); virtual void render(const ViewParameters *view); virtual void process(float dt); virtual void destroy(); ConnectingRod *m_connectingRod; protected: GeometryGenerator::GeometryIndices m_connectingRodBody, m_pins; }; #endif /* ATG_ENGINE_SIM_CONNECTING_ROD_OBJECT_H */ ================================================ FILE: include/constants.h ================================================ #ifndef ATG_ENGINE_SIM_CONSTANTS_H #define ATG_ENGINE_SIM_CONSTANTS_H namespace constants { extern constexpr double pi = 3.14159265359; extern constexpr double R = 8.31446261815324; extern constexpr double root_2 = 1.41421356237309504880168872420969807856967187537694807317667973799; extern constexpr double e = 2.71828182845904523536028747135266249775724709369995; } /* namespace Constants */ #endif /* ATG_ENGINE_SIM_CONSTANTS_H */ ================================================ FILE: include/convolution_filter.h ================================================ #ifndef ATG_ENGINE_SIM_CONVOLUTION_FILTER_H #define ATG_ENGINE_SIM_CONVOLUTION_FILTER_H #include "filter.h" class ConvolutionFilter : public Filter { public: ConvolutionFilter(); virtual ~ConvolutionFilter(); void initialize(int samples); virtual float f(float sample) override; virtual void destroy(); int getSampleCount() const { return m_sampleCount; } float *getImpulseResponse() { return m_impulseResponse; } protected: float *m_shiftRegister; int m_shiftOffset; float *m_impulseResponse; int m_sampleCount; }; #endif /* ATG_ENGINE_SIM_CONVOLUTION_FILTER_H */ ================================================ FILE: include/crankshaft.h ================================================ #ifndef ATG_ENGINE_SIM_CRANKSHAFT_H #define ATG_ENGINE_SIM_CRANKSHAFT_H #include "part.h" class Crankshaft : public Part { public: struct Parameters { double mass; double flywheelMass; double momentOfInertia; double crankThrow; double pos_x = 0; double pos_y = 0; double tdc = 0; double frictionTorque = 0; int rodJournals; }; public: Crankshaft(); virtual ~Crankshaft(); void initialize(const Parameters ¶ms); virtual void destroy(); inline int getRodJournalCount() const { return m_rodJournalCount; } void setRodJournalAngle(int i, double angle); void getRodJournalPositionLocal(int i, double *x, double *y); void getRodJournalPositionGlobal(int i, double *x, double *y); double getRodJournalAngle(int i) { return m_rodJournalAngles[i]; } void resetAngle(); double getAngle() const; double getCycleAngle(double offset = 0.0); inline double getTdc() const { return m_tdc; } inline double getThrow() const { return m_throw; } inline double getMass() const { return m_m; } inline double getMomentOfInertia() const { return m_I; } inline double getFlywheelMass() const { return m_flywheelMass; } inline double getPosX() const { return m_p_x; } inline double getPosY() const { return m_p_y; } inline double getFrictionTorque() const { return m_frictionTorque; } protected: double *m_rodJournalAngles; int m_rodJournalCount; double m_tdc; double m_throw; double m_m; double m_I; double m_flywheelMass; double m_p_x; double m_p_y; double m_frictionTorque; }; #endif /* ATG_ENGINE_SIM_CRANKSHAFT_H */ ================================================ FILE: include/crankshaft_object.h ================================================ #ifndef ATG_ENGINE_SIM_CRANKSHAFT_OBJECT_H #define ATG_ENGINE_SIM_CRANKSHAFT_OBJECT_H #include "simulation_object.h" #include "crankshaft.h" #include "geometry_generator.h" class CrankshaftObject : public SimulationObject { public: CrankshaftObject(); virtual ~CrankshaftObject(); virtual void generateGeometry(); virtual void render(const ViewParameters *view); virtual void process(float dt); virtual void destroy(); Crankshaft *m_crankshaft; }; #endif /* ATG_ENGINE_SIM_CRANKSHAFT_OBJECT_H */ ================================================ FILE: include/csv_io.h ================================================ #ifndef ATG_ENGINE_SIM_CSV_IO_H #define ATG_ENGINE_SIM_CSV_IO_H #include "../dependencies/submodules/csv-io/include/csv_data.h" #endif /* ATG_ENGINE_SIM_CSV_IO_H */ ================================================ FILE: include/cylinder_bank.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_BANK_H #define ATG_ENGINE_SIM_CYLINDER_BANK_H #include "part.h" #include "crankshaft.h" class CylinderBank { public: struct Parameters { Crankshaft *crankshaft; double positionX; double positionY; double angle; double bore; double deckHeight; double displayDepth; int cylinderCount; int index; }; public: CylinderBank(); ~CylinderBank(); void initialize(const Parameters ¶ms); void destroy(); void getPositionAboveDeck(double h, double *x, double *y) const; double boreSurfaceArea() const; inline double getAngle() const { return m_angle; } inline double getBore() const { return m_bore; } inline double getDeckHeight() const { return m_deckHeight; } inline int getCylinderCount() const { return m_cylinderCount; } inline int getIndex() const { return m_index; } inline double getDx() const { return m_dx; } inline double getDy() const { return m_dy; } inline double getX() const { return m_x; } inline double getY() const { return m_y; } inline double getDisplayDepth() const { return m_displayDepth; } protected: double m_angle; double m_bore; double m_deckHeight; double m_displayDepth; int m_cylinderCount; int m_index; double m_dx; double m_dy; double m_x; double m_y; }; #endif /* ATG_ENGINE_SIM_CYLINDER_BANK_H */ ================================================ FILE: include/cylinder_bank_object.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_BANK_OBJECT_H #define ATG_ENGINE_SIM_CYLINDER_BANK_OBJECT_H #include "simulation_object.h" #include "geometry_generator.h" #include "cylinder_bank.h" #include "cylinder_head.h" class CylinderBankObject : public SimulationObject { public: CylinderBankObject(); virtual ~CylinderBankObject(); virtual void generateGeometry(); virtual void render(const ViewParameters *view); virtual void process(float dt); virtual void destroy(); CylinderBank *m_bank; CylinderHead *m_head; GeometryGenerator::GeometryIndices m_walls; }; #endif /* ATG_ENGINE_SIM_CYLINDER_BANK_OBJECT_H */ ================================================ FILE: include/cylinder_head.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_HEAD_H #define ATG_ENGINE_SIM_CYLINDER_HEAD_H #include "part.h" #include "function.h" #include "camshaft.h" #include "exhaust_system.h" #include "intake.h" class Valvetrain; class CylinderBank; class CylinderHead : public Part { public: struct Parameters { CylinderBank *Bank; Function *ExhaustPortFlow; Function *IntakePortFlow; Valvetrain *Valvetrain; double CombustionChamberVolume; double IntakeRunnerVolume; double IntakeRunnerCrossSectionArea; double ExhaustRunnerVolume; double ExhaustRunnerCrossSectionArea; bool FlipDisplay = false; }; struct Cylinder { ExhaustSystem *exhaustSystem = nullptr; Intake *intake = nullptr; double soundAttenuation = 1.0; double headerPrimaryLength = 0.0; }; public: CylinderHead(); virtual ~CylinderHead(); void initialize(const Parameters ¶ms); virtual void destroy(); double intakeFlowRate(int cylinder) const; double exhaustFlowRate(int cylinder) const; double intakeValveLift(int cylinder) const; double exhaustValveLift(int cylinder) const; inline ExhaustSystem *getExhaustSystem(int cylinderIndex) const { return m_cylinders[cylinderIndex].exhaustSystem; } void setAllExhaustSystems(ExhaustSystem *system); void setExhaustSystem(int i, ExhaustSystem *system); inline double getSoundAttenuation(int cylinderIndex) const { return m_cylinders[cylinderIndex].soundAttenuation; } void setSoundAttenuation(int i, double soundAttenuation); inline Intake *getIntake(int cylinderIndex) const { return m_cylinders[cylinderIndex].intake; } void setAllIntakes(Intake *intake); void setIntake(int i, Intake *intake); inline double getHeaderPrimaryLength(int cylinderIndex) const { return m_cylinders[cylinderIndex].headerPrimaryLength; } void setAllHeaderPrimaryLengths(double length); void setHeaderPrimaryLength(int i, double length); inline bool getFlipDisplay() const { return m_flipDisplay; } inline double getCombustionChamberVolume() const { return m_combustionChamberVolume; } inline CylinderBank *getCylinderBank() const { return m_bank; } double getIntakeRunnerVolume() const { return m_intakeRunnerVolume; } double getIntakeRunnerCrossSectionArea() const { return m_intakeRunnerCrossSectionArea; } double getExhaustRunnerVolume() const { return m_exhaustRunnerVolume; } double getExhaustRunnerCrossSectionArea() const { return m_exhaustRunnerCrossSectionArea; } Camshaft *getExhaustCamshaft(); Camshaft *getIntakeCamshaft(); protected: Cylinder *m_cylinders; CylinderBank *m_bank; Valvetrain *m_valvetrain; Function *m_exhaustPortFlow; Function *m_intakePortFlow; double m_intakeRunnerVolume; double m_intakeRunnerCrossSectionArea; double m_exhaustRunnerVolume; double m_exhaustRunnerCrossSectionArea; double m_combustionChamberVolume; bool m_flipDisplay; }; #endif /* ATG_ENGINE_SIM_CYLINDER_HEAD_H */ ================================================ FILE: include/cylinder_head_object.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_HEAD_OBJECT_H #define ATG_ENGINE_SIM_CYLINDER_HEAD_OBJECT_H #include "simulation_object.h" #include "cylinder_head.h" #include "geometry_generator.h" #include "engine.h" class CylinderHeadObject : public SimulationObject { public: CylinderHeadObject(); virtual ~CylinderHeadObject(); virtual void generateGeometry(); virtual void render(const ViewParameters *view); virtual void process(float dt); virtual void destroy(); CylinderHead *m_head; Engine *m_engine; protected: void generateCamshaft( Camshaft *camshaft, double padding, double rollerRadius, GeometryGenerator::GeometryIndices *indices); }; #endif /* ATG_ENGINE_SIM_CYLINDER_HEAD_OBJECT_H */ ================================================ FILE: include/cylinder_pressure_gauge.h ================================================ #ifndef ATG_ENGINE_SIM_UI_CYLINDER_PRESSURE_GAUGE_H #define ATG_ENGINE_SIM_UI_CYLINDER_PRESSURE_GAUGE_H #include "ui_element.h" #include "engine.h" #include "gauge.h" class CylinderPressureGauge : public UiElement { public: CylinderPressureGauge(); virtual ~CylinderPressureGauge(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Engine *m_engine; protected: std::vector m_gauges; }; #endif /* ATG_ENGINE_SIM_UI_CYLINDER_PRESSURE_GAUGE_H */ ================================================ FILE: include/cylinder_temperature_gauge.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_TEMPERATURE_GAUGE_H #define ATG_ENGINE_SIM_CYLINDER_TEMPERATURE_GAUGE_H #include "ui_element.h" #include "engine.h" #include "gauge.h" class CylinderTemperatureGauge : public UiElement { public: CylinderTemperatureGauge(); virtual ~CylinderTemperatureGauge(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Engine *m_engine; double m_maxTemperature; double m_minTemperature; }; #endif /* ATG_ENGINE_SIM_CYLINDER_TEMPERATURE_GAUGE_H */ ================================================ FILE: include/delay_filter.h ================================================ #ifndef ATG_ENGINE_SIM_DELAY_FILTER_H #define ATG_ENGINE_SIM_DELAY_FILTER_H #include "filter.h" #include "ring_buffer.h" #include class DelayFilter : public Filter { public: DelayFilter() { m_latencySamples = 0; } virtual ~DelayFilter() { /* void */ } void initialize(double delay, double audioFrequency) { const int samples = static_cast(std::round(delay * audioFrequency)); const int capacity = samples + 32; m_history.initialize(capacity); m_latencySamples = samples; } virtual float f(float sample) override { return static_cast(fast_f(static_cast(sample))); } inline double fast_f(double sample) { m_history.write(sample); if (m_history.size() <= static_cast(m_latencySamples)) { return 0; } else { double v; m_history.readAndRemove(1, &v); return v; } } protected: int m_latencySamples; RingBuffer m_history; }; #endif /* ATG_ENGINE_SIM_DELAY_FILTER_H */ ================================================ FILE: include/delta.h ================================================ #ifndef DELTA_TEMPLATE_DELTA_INCLUDES_H #define DELTA_TEMPLATE_DELTA_INCLUDES_H #include #include #endif /* DELTA_TEMPLATE_DELTA_INCLUDES_H */ ================================================ FILE: include/derivative_filter.h ================================================ #ifndef ATG_ENGINE_SIM_DERIVATIVE_FILTER_H #define ATG_ENGINE_SIM_DERIVATIVE_FILTER_H #include "filter.h" class DerivativeFilter : public Filter { public: DerivativeFilter(); virtual ~DerivativeFilter(); virtual float f(float sample) override; float m_dt; protected: float m_previous; }; #endif /* ATG_ENGINE_SIM_DERIVATIVE_FILTER_H */ ================================================ FILE: include/direct_throttle_linkage.h ================================================ #ifndef ATG_ENGINE_SIM_DIRECT_THROTTLE_LINKAGE_H #define ATG_ENGINE_SIM_DIRECT_THROTTLE_LINKAGE_H #include "throttle.h" class DirectThrottleLinkage : public Throttle { public: struct Parameters { double gamma; }; public: DirectThrottleLinkage(); virtual ~DirectThrottleLinkage(); void initialize(const Parameters ¶ms); virtual void setSpeedControl(double s); virtual void update(double dt, Engine *engine); protected: double m_gamma; double m_throttlePosition; }; #endif /* ATG_ENGINE_SIM_DIRECT_THROTTLE_LINKAGE_H */ ================================================ FILE: include/dtv.h ================================================ #ifndef ATG_ENGINE_SIM_DTV_H #define ATG_ENGINE_SIM_DTV_H #include "../dependencies/submodules/direct-to-video/include/dtv.h" #endif /* ATG_ENGINE_SIM_DTV_H */ ================================================ FILE: include/dynamometer.h ================================================ #ifndef ATG_ENGINE_SIM_DYNAMOMETER_H #define ATG_ENGINE_SIM_DYNAMOMETER_H #include "scs.h" #include "crankshaft.h" class Dynamometer : public atg_scs::Constraint { public: Dynamometer(); virtual ~Dynamometer(); void connectCrankshaft(Crankshaft *crankshaft); virtual void calculate(Output *output, atg_scs::SystemState *state); double getTorque() const; double m_rotationSpeed; double m_ks; double m_kd; double m_maxTorque; bool m_hold; bool m_enabled; }; #endif /* ATG_ENGINE_SIM_DYNAMOMETER_H */ ================================================ FILE: include/engine.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_H #define ATG_ENGINE_SIM_ENGINE_H #include "part.h" #include "piston.h" #include "connecting_rod.h" #include "crankshaft.h" #include "cylinder_bank.h" #include "cylinder_head.h" #include "exhaust_system.h" #include "ignition_module.h" #include "intake.h" #include "combustion_chamber.h" #include "units.h" #include "throttle.h" #include class Simulator; class Vehicle; class Transmission; class Engine : public Part { public: struct Parameters { int cylinderBanks; int cylinderCount; int crankshaftCount; int exhaustSystemCount; int intakeCount; std::string name; double starterTorque = units::torque(90.0, units::ft_lb); double starterSpeed = units::rpm(200); double redline = units::rpm(6500); double dynoMinSpeed = units::rpm(1000); double dynoMaxSpeed = units::rpm(6500); double dynoHoldStep = units::rpm(100); Throttle *throttle; double initialSimulationFrequency; double initialHighFrequencyGain; double initialNoise; double initialJitter; }; public: Engine(); virtual ~Engine(); void initialize(const Parameters ¶ms); virtual void destroy(); std::string getName() const { return m_name; } virtual Crankshaft *getOutputCrankshaft() const; virtual void setSpeedControl(double s); virtual double getSpeedControl(); virtual void setThrottle(double throttle); virtual double getThrottle() const; virtual double getThrottlePlateAngle() const; virtual void calculateDisplacement(); double getDisplacement() const { return m_displacement; } virtual double getIntakeFlowRate() const; virtual void update(double dt); virtual double getManifoldPressure() const; virtual double getIntakeAfr() const; virtual double getExhaustO2() const; virtual double getRpm() const; virtual double getSpeed() const; virtual bool isSpinningCw() const; virtual void resetFuelConsumption(); virtual double getTotalFuelMassConsumed() const; double getTotalVolumeFuelConsumed() const; inline double getStarterTorque() const { return m_starterTorque; } inline double getStarterSpeed() const { return m_starterSpeed; } inline double getRedline() const { return m_redline; } inline double getDynoMinSpeed() const { return m_dynoMinSpeed; } inline double getDynoMaxSpeed() const { return m_dynoMaxSpeed; } inline double getDynoHoldStep() const { return m_dynoHoldStep; } int getCylinderBankCount() const { return m_cylinderBankCount; } int getCylinderCount() const { return m_cylinderCount; } int getCrankshaftCount() const { return m_crankshaftCount; } int getExhaustSystemCount() const { return m_exhaustSystemCount; } int getIntakeCount() const { return m_intakeCount; } int getMaxDepth() const; Crankshaft *getCrankshaft(int i) const { return &m_crankshafts[i]; } CylinderBank *getCylinderBank(int i) const { return &m_cylinderBanks[i]; } CylinderHead *getHead(int i) const { return &m_heads[i]; } Piston *getPiston(int i) const { return &m_pistons[i]; } ConnectingRod *getConnectingRod(int i) const { return &m_connectingRods[i]; } IgnitionModule *getIgnitionModule() { return &m_ignitionModule; } ExhaustSystem *getExhaustSystem(int i) const { return &m_exhaustSystems[i]; } Intake *getIntake(int i) const { return &m_intakes[i]; } CombustionChamber *getChamber(int i) const { return &m_combustionChambers[i]; } Fuel *getFuel() { return &m_fuel; } double getSimulationFrequency() const { return m_initialSimulationFrequency; } double getInitialHighFrequencyGain() const { return m_initialHighFrequencyGain; } double getInitialNoise() const { return m_initialNoise; } double getInitialJitter() const { return m_initialJitter; } virtual Simulator *createSimulator(Vehicle *vehicle, Transmission *transmission); protected: std::string m_name; Crankshaft *m_crankshafts; int m_crankshaftCount; CylinderBank *m_cylinderBanks; CylinderHead *m_heads; int m_cylinderBankCount; Piston *m_pistons; ConnectingRod *m_connectingRods; CombustionChamber *m_combustionChambers; int m_cylinderCount; double m_starterTorque; double m_starterSpeed; double m_redline; double m_dynoMinSpeed; double m_dynoMaxSpeed; double m_dynoHoldStep; double m_initialSimulationFrequency; double m_initialHighFrequencyGain; double m_initialNoise; double m_initialJitter; ExhaustSystem *m_exhaustSystems; int m_exhaustSystemCount; Intake *m_intakes; int m_intakeCount; IgnitionModule m_ignitionModule; Fuel m_fuel; Throttle *m_throttle; double m_throttleValue; double m_displacement; }; #endif /* ATG_ENGINE_SIM_ENGINE_H */ ================================================ FILE: include/engine_sim_application.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_SIM_APPLICATION_H #define ATG_ENGINE_SIM_ENGINE_SIM_APPLICATION_H #include "geometry_generator.h" #include "simulator.h" #include "engine.h" #include "simulation_object.h" #include "ui_manager.h" #include "dynamometer.h" #include "oscilloscope.h" #include "audio_buffer.h" #include "convolution_filter.h" #include "shaders.h" #include "engine_view.h" #include "right_gauge_cluster.h" #include "cylinder_temperature_gauge.h" #include "synthesizer.h" #include "oscilloscope_cluster.h" #include "performance_cluster.h" #include "load_simulation_cluster.h" #include "mixer_cluster.h" #include "info_cluster.h" #include "application_settings.h" #include "transmission.h" #include "delta.h" #include "dtv.h" #include class EngineSimApplication { private: static std::string s_buildVersion; public: EngineSimApplication(); virtual ~EngineSimApplication(); static std::string getBuildVersion() { return s_buildVersion; } void initialize(void *instance, ysContextObject::DeviceAPI api); void run(); void destroy(); void loadEngine(Engine *engine, Vehicle *vehicle, Transmission *transmission); void drawGenerated( const GeometryGenerator::GeometryIndices &indices, int layer = 0); void drawGeneratedUi( const GeometryGenerator::GeometryIndices &indices, int layer = 0); void drawGenerated( const GeometryGenerator::GeometryIndices &indices, int layer, dbasic::StageEnableFlags flags); void configure(const ApplicationSettings &settings); GeometryGenerator *getGeometryGenerator() { return &m_geometryGenerator; } Shaders *getShaders() { return &m_shaders; } dbasic::TextRenderer *getTextRenderer() { return &m_textRenderer; } void createObjects(Engine *engine); void destroyObjects(); dbasic::DeltaEngine *getEngine() { return &m_engine; } float pixelsToUnits(float pixels) const; float unitsToPixels(float units) const; ysVector getBackgroundColor() const { return m_background; } ysVector getForegroundColor() const { return m_foreground; } ysVector getHightlight1Color() const { return m_highlight1; } ysVector getPink() const { return m_pink; } ysVector getGreen() const { return m_green; } ysVector getYellow() const { return m_yellow; } ysVector getRed() const { return m_red; } ysVector getOrange() const { return m_orange; } ysVector getBlue() const { return m_blue; } const SimulationObject::ViewParameters &getViewParameters() const; void setViewLayer(int view) { m_viewParameters.Layer0 = view; } dbasic::AssetManager *getAssetManager() { return &m_assetManager; } int getScreenWidth() const { return m_screenWidth; } int getScreenHeight() const { return m_screenHeight; } Simulator *getSimulator() { return m_simulator; } InfoCluster *getInfoCluster() { return m_infoCluster; } ApplicationSettings* getAppSettings() { return &m_applicationSettings; } protected: void loadScript(); void processEngineInput(); void renderScene(); void refreshUserInterface(); protected: double m_speedSetting = 1.0; double m_targetSpeedSetting = 1.0; double m_clutchPressure = 1.0; double m_targetClutchPressure = 1.0; int m_lastMouseWheel = 0; protected: virtual void initialize(); virtual void process(float dt); virtual void render(); float m_displayAngle; float m_displayHeight; int m_gameWindowHeight; int m_screenWidth; int m_screenHeight; ApplicationSettings m_applicationSettings; dbasic::ShaderSet m_shaderSet; Shaders m_shaders; dbasic::DeltaEngine m_engine; dbasic::AssetManager m_assetManager; std::string m_assetPath; ysRenderTarget *m_mainRenderTarget; ysGPUBuffer *m_geometryVertexBuffer; ysGPUBuffer *m_geometryIndexBuffer; GeometryGenerator m_geometryGenerator; dbasic::TextRenderer m_textRenderer; std::vector m_objects; Engine *m_iceEngine; Vehicle *m_vehicle; Transmission *m_transmission; Simulator *m_simulator; double m_dynoSpeed; double m_torque; UiManager m_uiManager; EngineView *m_engineView; RightGaugeCluster *m_rightGaugeCluster; OscilloscopeCluster *m_oscCluster; CylinderTemperatureGauge *m_temperatureGauge; PerformanceCluster *m_performanceCluster; LoadSimulationCluster *m_loadSimulationCluster; MixerCluster *m_mixerCluster; InfoCluster *m_infoCluster; SimulationObject::ViewParameters m_viewParameters; bool m_paused; protected: void startRecording(); void updateScreenSizeStability(); bool readyToRecord(); void stopRecording(); void recordFrame(); bool isRecording() const { return m_recording; } static constexpr int ScreenResolutionHistoryLength = 5; int m_screenResolution[ScreenResolutionHistoryLength][2]; int m_screenResolutionIndex; bool m_recording; ysVector m_background; ysVector m_foreground; ysVector m_shadow; ysVector m_highlight1; ysVector m_highlight2; ysVector m_pink; ysVector m_orange; ysVector m_yellow; ysVector m_red; ysVector m_green; ysVector m_blue; ysAudioBuffer *m_outputAudioBuffer; AudioBuffer m_audioBuffer; ysAudioSource *m_audioSource; int m_oscillatorSampleOffset; int m_screen; #ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE atg_dtv::Encoder m_encoder; #endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */ }; #endif /* ATG_ENGINE_SIM_ENGINE_SIM_APPLICATION_H */ ================================================ FILE: include/engine_view.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_VIEW_H #define ATG_ENGINE_SIM_ENGINE_VIEW_H #include "ui_element.h" class EngineView : public UiElement { public: EngineView(); virtual ~EngineView(); virtual void update(float dt); virtual void render(); virtual void onMouseDown(const Point &mouseLocal); virtual void onDrag(const Point &p0, const Point &mouse0, const Point &mouse); virtual void onMouseScroll(int scroll); void setDrawFrame(bool drawFrame) { m_drawFrame = drawFrame; } void setBounds(const Bounds &bounds); Point getCenter() const; Point getCameraPosition() const; float m_zoom; protected: Point m_pan; Point m_dragStart; int m_lastScroll; bool m_drawFrame; }; #endif /* ATG_ENGINE_SIM_ENGINE_VIEW_H */ ================================================ FILE: include/exhaust_system.h ================================================ #ifndef ATG_ENGINE_SIM_EXHAUST_SYSTEM_H #define ATG_ENGINE_SIM_EXHAUST_SYSTEM_H #include "part.h" #include "gas_system.h" #include "impulse_response.h" class ExhaustSystem : public Part { friend class Engine; public: struct Parameters { double length; double collectorCrossSectionArea; double outletFlowRate; double primaryTubeLength; double primaryFlowRate; double velocityDecay; double audioVolume; ImpulseResponse *impulseResponse; }; public: ExhaustSystem(); virtual ~ExhaustSystem(); void initialize(const Parameters ¶ms); virtual void destroy(); void process(double dt); inline int getIndex() const { return m_index; } inline double getLength() const { return m_length; } inline double getFlow() const { return m_flow; } inline double getAudioVolume() const { return m_audioVolume; } inline double getPrimaryFlowRate() const { return m_primaryFlowRate; } inline double getCollectorCrossSectionArea() const { return m_collectorCrossSectionArea; } inline double getPrimaryTubeLength() const { return m_primaryTubeLength; } inline double getVelocityDecay() const { return m_velocityDecay; } inline ImpulseResponse *getImpulseResponse() const { return m_impulseResponse; } inline GasSystem *getSystem() { return &m_system; } protected: GasSystem m_atmosphere; GasSystem m_system; ImpulseResponse *m_impulseResponse; double m_length; double m_primaryTubeLength; double m_collectorCrossSectionArea; double m_primaryFlowRate; double m_outletFlowRate; double m_audioVolume; double m_velocityDecay; int m_index; double m_flow; }; #endif /* ATG_ENGINE_SIM_EXHAUST_SYSTEM_H */ ================================================ FILE: include/feedback_comb_filter.h ================================================ #ifndef ATG_ENGINE_SIM_FEEDBACK_COMB_FILTER_H #define ATG_ENGINE_SIM_FEEDBACK_COMB_FILTER_H #include "filter.h" class FeedbackCombFilter : public Filter { public: FeedbackCombFilter(); virtual ~FeedbackCombFilter(); void initialize(int M); virtual float f(float sample) override; virtual void destroy(); float a_M; protected: float *m_y; int m_offset; protected: int M; }; #endif /* ATG_ENGINE_SIM_FEEDBACK_COMB_FILTER_H */ ================================================ FILE: include/filter.h ================================================ #ifndef ATG_ENGINE_SIM_FILTER_H #define ATG_ENGINE_SIM_FILTER_H class Filter { public: Filter(); virtual ~Filter(); virtual float f(float sample); virtual void destroy(); }; #endif /* ATG_ENGINE_SIM_FILTER_H */ ================================================ FILE: include/firing_order_display.h ================================================ #ifndef ATG_ENGINE_SIM_FIRING_ORDER_DISPLAY_H #define ATG_ENGINE_SIM_FIRING_ORDER_DISPLAY_H #include "ui_element.h" #include "engine.h" #include "gauge.h" class FiringOrderDisplay : public UiElement { public: FiringOrderDisplay(); virtual ~FiringOrderDisplay(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Engine *m_engine; protected: int m_cylinderCount; float *m_cylinderLit; }; #endif /* ATG_ENGINE_SIM_FIRING_ORDER_DISPLAY_H */ ================================================ FILE: include/fuel.h ================================================ #ifndef ATG_ENGINE_FUEL_H #define ATG_ENGINE_FUEL_H #include "units.h" #include "function.h" #include class Fuel { public: struct Parameters { std::string name = "Gasoline"; double molecularMass = units::mass(100.0, units::g); double energyDensity = units::energy(48.1, units::kJ) / units::mass(1.0, units::g); double density = units::mass(0.755, units::kg) / units::volume(1.0, units::L); double molecularAfr = 25 / 2.0; double burningEfficiencyRandomness = 0.5; double lowEfficiencyAttenuation = 0.6; double maxBurningEfficiency = 0.8; double maxTurbulenceEffect = 2.0; double maxDilutionEffect = 50.0; Function *turbulenceToFlameSpeedRatio = nullptr; }; Fuel(); ~Fuel(); void initialize(const Parameters ¶ms); inline double getMolecularMass() const { return m_molecularMass; } inline double getEnergyDensity() const { return m_energyDensity; } inline double getDensity() const { return m_density; } inline double getBurningEfficiencyRandomness() const { return m_burningEfficiencyRandomness; } inline double getLowEfficiencyAttenuation() const { return m_lowEfficiencyAttenuation; } inline double getMaxBurningEfficiency() const { return m_maxBurningEfficiency; } inline double getMaxTurbulenceEffect() const { return m_maxTurbulenceEffect; } inline double getMaxDilutionEffect() const { return m_maxDilutionEffect; } double flameSpeed( double turbulence, double molecularAfr, double T, double P, double firingPressure, double motoringPressure) const; virtual double laminarBurningVelocity(double molecularAfr, double T, double P) const; double getMolecularAfr() const { return m_molecularAfr; } protected: std::string m_name; double m_molecularMass; double m_energyDensity; double m_density; double m_molecularAfr; double m_maxBurningEfficiency; double m_burningEfficiencyRandomness; double m_lowEfficiencyAttenuation; double m_maxTurbulenceEffect; double m_maxDilutionEffect; Function *m_turbulenceToFlameSpeedRatio; }; #endif /* ATG_ENGINE_FUEL_H */ ================================================ FILE: include/fuel_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_FUEL_CLUSTER_H #define ATG_ENGINE_SIM_FUEL_CLUSTER_H #include "ui_element.h" #include "engine.h" #include "simulator.h" class FuelCluster : public UiElement { public: FuelCluster(); virtual ~FuelCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Engine *m_engine; Simulator *m_simulator; private: double getTotalVolumeFuelConsumed() const; }; #endif /* ATG_ENGINE_SIM_FUEL_CLUSTER_H */ ================================================ FILE: include/function.h ================================================ #ifndef ATG_ENGINE_SIM_FUNCTION_H #define ATG_ENGINE_SIM_FUNCTION_H #include "gaussian_filter.h" class Function { protected: static GaussianFilter *DefaultGaussianFilter; public: Function(); virtual ~Function(); void initialize(int size, double filterRadius, GaussianFilter *filter = nullptr); void resize(int newCapacity); void destroy(); void setInputScale(double s) { m_inputScale = s; } void setOutputScale(double s) { m_outputScale = s; } void addSample(double x, double y); double sampleTriangle(double x) const; double sampleGaussian(double x) const; double triangle(double x) const; int closestSample(double x) const; bool isOrdered() const; void getDomain(double *x0, double *x1); void getRange(double *y0, double *y1); protected: double *m_x; double *m_y; double m_yMin; double m_yMax; double m_inputScale; double m_outputScale; double m_filterRadius; int m_capacity; int m_size; GaussianFilter *m_gaussianFilter; }; #endif /* ATG_ENGINE_SIM_FUNCTION_H */ ================================================ FILE: include/gas_system.h ================================================ #ifndef ATG_ENGINE_SIM_GAS_SYSTEM_H #define ATG_ENGINE_SIM_GAS_SYSTEM_H #include "constants.h" #include "units.h" #include #include class GasSystem { public: struct Mix { double p_fuel = 0.0; double p_inert = 1.0; double p_o2 = 0.0; }; struct State { double n_mol = 0.0; double E_k = 0.0; double V = 0.0; double momentum[2] = { 0.0, 0.0 }; Mix mix; }; struct FlowParameters { double k_flow; double dt; double direction_x, direction_y; double crossSectionArea_0, crossSectionArea_1; GasSystem *system_0, *system_1; }; public: GasSystem() { /* void */ } ~GasSystem() { /* void */ } void setGeometry(double width, double height, double dx, double dy); void initialize(double P, double V, double T, const Mix &mix = {}, int degreesOfFreedom = 5); void reset(double P, double T, const Mix &mix = {}); void setVolume(double V); void setN(double n); void changeVolume(double dV); void changePressure(double pressure); void changeTemperature(double dT); void changeTemperature(double dT, double n); void changeEnergy(double dE); void changeMix(const Mix &mix); void injectFuel(double n); double react(double n, const Mix &mix); static double flowConstant(double flowRate, double P, double pressureDrop, double T, double hcr); static double k_28inH2O(double flowRateScfm); static double k_carb(double flowRateScfm); static double flowRate( double k_flow, double P0, double P1, double T0, double T1, double hcr, double chokedFlowLimit, double chokedFlowRateCached); double loseN(double dn, double E_k_per_mol); double gainN(double dn, double E_k_per_mol, const Mix &mix = {}); void dissipateExcessVelocity(); void updateVelocity(double dt, double beta = 1.0); void dissipateVelocity(double dt, double timeConstant); static double flow(const FlowParameters ¶ms); double flow(double k_flow, double dt, double P_env, double T_env, const Mix &mix = {}); double pressureEquilibriumMaxFlow(const GasSystem *b) const; double pressureEquilibriumMaxFlow(double P_env, double T_env) const; inline static constexpr double kineticEnergyPerMol(double T, int degreesOfFreedom); inline static constexpr double heatCapacityRatio(int degreesOfFreedom); inline static double chokedFlowLimit(int degreesOfFreedom); inline static double chokedFlowRate(int degreesOfFreedom); inline double approximateDensity() const; inline int degreesOfFreedom() const { return m_degreesOfFreedom; } inline double n() const; inline double n(double V) const; inline double kineticEnergy() const; inline double kineticEnergy(double n) const; inline double kineticEnergyPerMol() const { return kineticEnergy(1.0); } inline double totalEnergy() const; inline double bulkKineticEnergy() const; inline double c() const; inline double dynamicPressure(double dx, double dy) const; inline double mass() const; inline double pressure() const; inline double temperature() const; inline double velocity_x() const; inline double velocity_y() const; inline double volume() const; inline double volume(double n) const; inline double n_fuel() const; inline double n_inert() const; inline double n_o2() const; inline double heatCapacityRatio() const; inline Mix mix() const { return m_state.mix; } protected: State m_state; int m_degreesOfFreedom = 5; double m_chokedFlowLimit = 0; double m_chokedFlowFactorCached = 0; double m_width = 0.0; double m_height = 0.0; double m_dx = 0.0; double m_dy = 0.0; }; inline constexpr double GasSystem::kineticEnergyPerMol(double T, int degreesOfFreedom) { return 0.5 * T * constants::R * degreesOfFreedom; } inline constexpr double GasSystem::heatCapacityRatio(int degreesOfFreedom) { return 1.0 + (2.0 / degreesOfFreedom); } inline double GasSystem::chokedFlowLimit(int degreesOfFreedom) { const double hcr = heatCapacityRatio(degreesOfFreedom); return std::pow((2.0 / (hcr + 1)), hcr / (hcr - 1)); } inline double GasSystem::chokedFlowRate(int degreesOfFreedom) { const double hcr = heatCapacityRatio(degreesOfFreedom); double flowRate = std::sqrt(hcr) * std::pow(2 / (hcr + 1), (hcr + 1) / (2 * (hcr - 1))); return flowRate; } inline double GasSystem::approximateDensity() const { return (units::AirMolecularMass * n()) / volume(); } inline double GasSystem::n() const { return m_state.n_mol; } inline double GasSystem::n(double V) const { return (V / volume()) * n(); } inline double GasSystem::kineticEnergy() const { return m_state.E_k; } inline double GasSystem::kineticEnergy(double n) const { return (kineticEnergy() / this->n()) * n; } inline double GasSystem::c() const { if (n() == 0 || kineticEnergy() == 0) return 0; const double hcr = heatCapacityRatio(); const double staticPressure = pressure(); const double density = approximateDensity(); const double c = std::sqrt(staticPressure * hcr / density); return c; } inline double GasSystem::totalEnergy() const { if (n() == 0) return 0; const double invMass = 1 / mass(); const double v_x = m_state.momentum[0] * invMass; const double v_y = m_state.momentum[1] * invMass; const double v_squared = v_x * v_x + v_y * v_y; return kineticEnergy() + 0.5 * mass() * v_squared; } inline double GasSystem::bulkKineticEnergy() const { const double m = mass(); if (m == 0) return 0; const double v_x = m_state.momentum[0] / m; const double v_y = m_state.momentum[1] / m; const double v_squared = v_x * v_x + v_y * v_y; return 0.5 * m * v_squared; } inline double GasSystem::dynamicPressure(double dx, double dy) const { if (n() == 0 || kineticEnergy() == 0) return 0; const double inverseMass = 1 / this->mass(); const double v = inverseMass * (dx * m_state.momentum[0] + dy * m_state.momentum[1]); if (v <= 0) { return 0; } const double hcr = heatCapacityRatio(); const double staticPressure = pressure(); const double density = approximateDensity(); const double c_squared = staticPressure * hcr / density; const double machNumber_squared = v * v / c_squared; // Below is equivalent to: // staticPressure * pow(1 + ((hcr - 1) / 2) * machNumber * machNumber, hcr / (hcr - 1)) - 1) const double x = 1 + ((hcr - 1) / 2) * machNumber_squared; double x_d; switch (m_degreesOfFreedom) { case 3: x_d = x * x * x * x * x; break; case 5: { const double x_2 = x * x; const double x_3 = x_2 * x; x_d = x_3 * x_3 * x; break; } default: x_d = x; } return staticPressure * (std::sqrt(x_d) - 1); } inline double GasSystem::mass() const { return units::AirMolecularMass * n(); } inline double GasSystem::pressure() const { const double volume = this->volume(); return (volume != 0) ? kineticEnergy() / (0.5 * m_degreesOfFreedom * volume) : 0; } inline double GasSystem::temperature() const { if (n() == 0) return 0; else return kineticEnergy() / (0.5 * m_degreesOfFreedom * n() * constants::R); } inline double GasSystem::velocity_x() const { if (n() == 0) return 0; else return m_state.momentum[0] / mass(); } inline double GasSystem::velocity_y() const { if (n() == 0) return 0; else return m_state.momentum[1] / mass(); } inline double GasSystem::volume() const { return m_state.V; } inline double GasSystem::volume(double n) const { return n * this->n() / volume(); } inline double GasSystem::n_fuel() const { return m_state.mix.p_fuel * n(); } inline double GasSystem::n_inert() const { return m_state.mix.p_inert * n(); } inline double GasSystem::n_o2() const { return m_state.mix.p_o2 * n(); } inline double GasSystem::heatCapacityRatio() const { return heatCapacityRatio(m_degreesOfFreedom); } #endif /* ATG_ENGINE_SIM_GAS_SYSTEM_H */ ================================================ FILE: include/gauge.h ================================================ #ifndef ATG_ENGINE_SIM_GAUGE_H #define ATG_ENGINE_SIM_GAUGE_H #include "ui_element.h" #include class Gauge : public UiElement { public: struct Band { ysVector color = ysMath::Constants::One; float start = 0.0; float end = 0.0; float width = 0.0; float radial_offset = 0.0f; float shorten_start = 0.0f; float shorten_end = 0.0f; }; public: Gauge(); virtual ~Gauge(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void setBandCount(int bands) { m_bands.resize(bands); } void setBand(const Band &band, int index) { m_bands[index] = band; } float m_value; float m_thetaMin; float m_thetaMax; int m_min; int m_max; int m_maxMinorTick; float m_gamma; int m_minorStep; int m_majorStep; float m_minorTickWidth; float m_majorTickWidth; float m_minorTickLength; float m_majorTickLength; float m_outerRadius; float m_needleInnerRadius; float m_needleOuterRadius; float m_needleWidth; float m_needleMaxVelocity; float m_needleKs; float m_needleKd; bool m_renderText; Point m_center; protected: float m_needlePosition; float m_needleVelocity; protected: std::vector m_bands; }; #endif /* ATG_ENGINE_SIM_GAUGE_H */ ================================================ FILE: include/gaussian_filter.h ================================================ #ifndef ATG_ENGINE_SIM_GUASSIAN_FILTER_H #define ATG_ENGINE_SIM_GUASSIAN_FILTER_H #include "scs.h" #include "crankshaft.h" class GaussianFilter { public: GaussianFilter(); ~GaussianFilter(); void initialize(double alpha, double radius, int cacheSteps=1024); double evaluate(double s) const; double getRadius() const { return m_radius; } double getAlpha() const { return m_alpha; } protected: double calculate(double s) const; void generateCache(); protected: double *m_cache; int m_cacheSteps; double m_radius; double m_alpha; double m_exp_s; double m_inv_r; }; #endif /* ATG_ENGINE_SIM_GAUSSIAN_FILTER_H */ ================================================ FILE: include/geometry_generator.h ================================================ #ifndef ATG_ENGINE_SIM_GEOMETRY_GENERATOR_H #define ATG_ENGINE_SIM_GEOMETRY_GENERATOR_H #include "delta.h" #include "../include/function.h" #include "../include/ui_math.h" class GeometryGenerator { public: struct GeometryIndices { int BaseIndex = -1; int BaseVertex = -1; int FaceCount = -1; dbasic::Vertex *VertexData = nullptr; }; struct LineRingParameters { ysVector normal; ysVector center; float radius; float patternHeight; float maxEdgeLength; float startAngle = 0.0f; float endAngle = ysMath::Constants::TWO_PI; float taperTail = 0.0f; float textureOffset = 0.0f; float textureWidthHeightRatio = 1.0f; }; struct LineParameters { ysVector start; ysVector end; ysVector normal = ysMath::Constants::ZAxis; float patternHeight; float taperTail; float textureOffset = 0.0f; float textureWidthHeightRatio = 1.0f; }; struct Line2dParameters { float x0, y0; float x1, y1; float lineWidth; }; struct Ring2dParameters { float center_x = 0.0f, center_y = 0.0f; float startAngle = 0.0f, endAngle = ysMath::Constants::TWO_PI; float innerRadius = 0.5f, outerRadius = 1.0f; float maxEdgeLength = 2.0f; bool drawArrow = false; bool arrowOnEnd = true; float arrowLength = 0.0f; }; struct Circle2dParameters { float center_x = 0.0f, center_y = 0.0f; float radius = 1.0f; float maxEdgeLength = 0.1f; float smallestAngle = ysMath::Constants::PI * 0.95f; }; struct Cam2dParameters { float center_x = 0.0f, center_y = 0.0f; float baseRadius = 1.0f; float rollerRadius = 1.0f; Function *lift = nullptr; float maxEdgeLength = 0.1f; float smallestAngle = ysMath::Constants::PI * 0.95f; }; struct Rhombus2dParameters { float center_x = 0.0f, center_y = 0.0f; float height = 0; float shear = 0; float width = 0; }; struct Trapezoid2dParameters { float center_x = 0, center_y = 0; float height = 0; float base = 0; float top = 0; }; struct FrameParameters { float x, y; float frameWidth, frameHeight; float lineWidth; }; struct GridParameters { float x, y; float width, height; float div_x, div_y; float lineWidth; }; struct PathParameters { Point *p0; Point *p1; int n0; int n1; int i = 0; float width; int v0 = -1; int v1 = -1; float pdir_x, pdir_y; float perp_x, perp_y; }; public: GeometryGenerator(); ~GeometryGenerator(); void initialize(int vertexBufferSize, int indexBufferSize); void destroy(); const dbasic::Vertex *getVertexData() const { return m_vertexData; } const unsigned short *getIndexData() const { return m_indexData; } int getCurrentVertexCount() const { return m_state.vertexPointer; } int getCurrentIndexCount() const { return m_state.indexPointer; } void reset(); bool generateFilledCircle( const ysVector &normal, const ysVector ¢er, float radius, float maxEdgeLength ); bool generateFilledFanPolygon( const ysVector &normal, const ysVector &up, const ysVector ¢er, float radius, float rotation, int segmentCount ); bool generateLineRing( const LineRingParameters ¶ms); bool generateLineRingBalanced( const LineRingParameters ¶ms); bool generateLine( const LineParameters ¶ms); bool generateLine2d( const Line2dParameters ¶ms); bool generateRing2d( const Ring2dParameters ¶ms); bool generateFrame( const FrameParameters ¶ms); bool generateGrid( const GridParameters ¶ms); bool generateCircle2d( const Circle2dParameters ¶ms); bool generateCam( const Cam2dParameters ¶ms); bool generateRhombus( const Rhombus2dParameters ¶ms); bool generateTrapezoid2d( const Trapezoid2dParameters ¶ms); bool generateIsoscelesTriangle( float x, float y, float width, float height); bool startPath(PathParameters ¶ms); bool generatePathSegment(PathParameters ¶ms, bool detached = false); void startShape(); void endShape(GeometryIndices *indices); protected: void startSubshape(); dbasic::Vertex *writeVertex(); void writeFace(unsigned short i0, unsigned short i1, unsigned short i2); bool checkCapacity(int vertexCount, int indexCount); protected: static ysVector findOrthogonal(const ysVector &v); protected: dbasic::Vertex *m_vertexData; unsigned short *m_indexData; int m_vertexBufferSize; int m_indexBufferSize; struct State { int vertexPointer = 0; int indexPointer = 0; GeometryIndices currentShape; int subshapeVertexPointer = 0; } m_state; }; #endif /* ATG_ENGINE_SIM_GEOMETRY_GENERATOR_H */ ================================================ FILE: include/governor.h ================================================ #ifndef ATG_ENGINE_SIM_GOVERNOR_H #define ATG_ENGINE_SIM_GOVERNOR_H #include "throttle.h" class Governor : public Throttle { public: struct Parameters { double minSpeed; double maxSpeed; double minVelocity; double maxVelocity; double k_s; double k_d; double gamma; }; public: Governor(); virtual ~Governor(); void initialize(const Parameters ¶ms); virtual void setSpeedControl(double s); virtual void update(double dt, Engine *engine); protected: double m_minSpeed; double m_maxSpeed; double m_minVelocity; double m_maxVelocity; double m_k_s; double m_k_d; double m_gamma; double m_targetSpeed; double m_currentThrottle; double m_velocity; }; #endif /* ATG_ENGINE_SIM_GOVERNOR_H */ ================================================ FILE: include/ignition_module.h ================================================ #ifndef ATG_ENGINE_SIM_IGNITION_MODULE_H #define ATG_ENGINE_SIM_IGNITION_MODULE_H #include "part.h" #include "crankshaft.h" #include "function.h" #include "units.h" class IgnitionModule : public Part { public: struct Parameters { int cylinderCount; Crankshaft *crankshaft; Function *timingCurve; double revLimit = units::rpm(6000.0); double limiterDuration = 0.5 * units::sec; }; struct SparkPlug { double angle = 0; bool ignitionEvent = false; bool enabled = false; }; public: IgnitionModule(); virtual ~IgnitionModule(); virtual void destroy(); void initialize(const Parameters ¶ms); void setFiringOrder(int cylinderIndex, double angle); void reset(); void update(double dt); bool getIgnitionEvent(int index) const; void resetIgnitionEvents(); double getTimingAdvance(); bool m_enabled; protected: SparkPlug *getPlug(int i); Function *m_timingCurve; SparkPlug *m_plugs; Crankshaft *m_crankshaft; int m_cylinderCount; double m_lastCrankshaftAngle; double m_revLimit; double m_revLimitTimer; double m_limiterDuration; }; #endif /* ATG_ENGINE_SIM_IGNITION_MODULE_H */ ================================================ FILE: include/impulse_response.h ================================================ #ifndef ATG_ENGINE_SIM_IMPULSE_RESPONSE_H #define ATG_ENGINE_SIM_IMPULSE_RESPONSE_H #include class ImpulseResponse { public: ImpulseResponse(); virtual ~ImpulseResponse(); void initialize(const std::string &filename, double volume); std::string getFilename() const { return m_filename; } double getVolume() const { return m_volume; } protected: std::string m_filename; double m_volume; }; #endif /* ATG_ENGINE_SIM_IMPULSE_RESPONSE_H */ ================================================ FILE: include/info_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_INFO_CLUSTER_H #define ATG_ENGINE_SIM_INFO_CLUSTER_H #include "ui_element.h" #include "engine.h" #include class InfoCluster : public UiElement { public: InfoCluster(); virtual ~InfoCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void setEngine(Engine *engine) { m_engine = engine; } void setLogMessage(const std::string &logMessage) { m_logMessage = logMessage; } std::string getLogMessage() const { return m_logMessage; } protected: Engine *m_engine; std::string m_logMessage; }; #endif /* ATG_ENGINE_SIM_INFO_CLUSTER_H */ ================================================ FILE: include/intake.h ================================================ #ifndef ATG_ENGINE_SIM_INTAKE_H #define ATG_ENGINE_SIM_INTAKE_H #include "part.h" #include "gas_system.h" class Intake : public Part { public: struct Parameters { // Plenum volume double volume; // Plenum dimensions double CrossSectionArea; // Input flow constant double InputFlowK; // Idle-circuit flow constant double IdleFlowK; // Flow rate from plenum to runner double RunnerFlowRate; // Molecular air fuel ratio (defaults to ideal for octane) double MolecularAfr = (25.0 / 2.0); // Throttle plate position at idle double IdleThrottlePlatePosition = 0.975; // Runner volume double RunnerLength = units::distance(4.0, units::inch); // Velocity decay factor double VelocityDecay = 0.5; }; public: Intake(); virtual ~Intake(); void initialize(Parameters ¶ms); virtual void destroy(); void process(double dt); inline double getRunnerFlowRate() const { return m_runnerFlowRate; } inline double getThrottlePlatePosition() const { return m_idleThrottlePlatePosition * m_throttle; } inline double getRunnerLength() const { return m_runnerLength; } inline double getPlenumCrossSectionArea() const { return m_crossSectionArea; } inline double getVelocityDecay() const { return m_velocityDecay; } GasSystem m_system; double m_throttle; double m_flow; double m_flowRate; double m_totalFuelInjected; protected: double m_crossSectionArea; double m_inputFlowK; double m_idleFlowK; double m_runnerFlowRate; double m_molecularAfr; double m_idleThrottlePlatePosition; double m_runnerLength; double m_velocityDecay; GasSystem m_atmosphere; }; #endif /* ATG_ENGINE_SIM_INTAKE_H */ ================================================ FILE: include/jitter_filter.h ================================================ #ifndef ATG_ENGINE_SIM_JITTER_FILTER_H #define ATG_ENGINE_SIM_JITTER_FILTER_H #include "filter.h" #include "butterworth_low_pass_filter.h" #include "utilities.h" #include class JitterFilter : public Filter { public: JitterFilter(); virtual ~JitterFilter(); void initialize( int maxJitter, float noiseCutoffFrequency, float audioFrequency); virtual float f(float sample) override; __forceinline float fast_f(float sample, float jitterScale = 1.0f) { m_history[m_offset] = sample; ++m_offset; if (m_offset >= m_maxJitter) { m_offset = 0; } std::uniform_real_distribution dist( 0.0f, static_cast(m_maxJitter - 1)); const float s = m_noiseFilter.fast_f(dist(m_generator) * m_jitterScale * jitterScale); const float s_i_0 = clamp(std::floor(s), 0.0f, static_cast(m_maxJitter - 1)); const float s_i_1 = clamp(std::ceil(s), 0.0f, static_cast(m_maxJitter - 1)); const float s_frac = (s - s_i_0); const int i_0 = static_cast(s_i_0) + m_offset; const int i_1 = static_cast(s_i_1) + m_offset; const float v0 = m_history[i_0 >= m_maxJitter ? i_0 - m_maxJitter : i_0]; const float v1 = m_history[i_1 >= m_maxJitter ? i_1 - m_maxJitter : i_1]; return v1 * s_frac + v0 * (1 - s_frac); } inline void setJitterScale(float jitterScale) { m_jitterScale = jitterScale; } inline float getJitterScale() const { return m_jitterScale; } protected: ButterworthLowPassFilter m_noiseFilter; float m_jitterScale; int m_maxJitter; int m_offset; float *m_history; std::default_random_engine m_generator; }; #endif /* ATG_ENGINE_SIM_JITTER_FILTER_H */ ================================================ FILE: include/labeled_gauge.h ================================================ #ifndef ATG_ENGINE_SIM_LABELED_GAUGE_H #define ATG_ENGINE_SIM_LABELED_GAUGE_H #include "ui_element.h" #include "gauge.h" #include class LabeledGauge : public UiElement { public: LabeledGauge(); virtual ~LabeledGauge(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Gauge *m_gauge; std::string m_title; int m_precision; bool m_spaceBeforeUnit; std::string m_unit; float m_margin = 10.0f; float m_needleInnerRadius = 0.1f; float m_needleOuterRadius = 0.7f; }; #endif /* ATG_ENGINE_SIM_LABELED_GAUGE_H */ ================================================ FILE: include/leveling_filter.h ================================================ #ifndef ATG_ENGINE_SIM_LEVELING_FILTER_H #define ATG_ENGINE_SIM_LEVELING_FILTER_H #include "filter.h" #include "function.h" class LevelingFilter : public Filter { public: LevelingFilter(); virtual ~LevelingFilter(); virtual float f(float sample); float getAttenuation() const { return m_attenuation; } protected: float m_peak; float m_attenuation; public: float p_maxLevel; float p_minLevel; float p_target; }; #endif /* ATG_ENGINE_SIM_LEVELING_FILTER_H */ ================================================ FILE: include/load_simulation_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_LOAD_SIMULATION_CLUSTER_H #define ATG_ENGINE_SIM_LOAD_SIMULATION_CLUSTER_H #include "ui_element.h" #include "simulator.h" #include "labeled_gauge.h" class LoadSimulationCluster : public UiElement { public: LoadSimulationCluster(); virtual ~LoadSimulationCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void setUnits(); void setSimulator(Simulator *simulator) { m_simulator = simulator; } private: Transmission *getTransmission() const { return m_simulator->getTransmission(); } protected: void drawCurrentGear(const Bounds &bounds); void drawClutchPressureGauge(const Bounds &bounds); void drawSystemStatus(const Bounds &bounds); void updateHpAndTorque(float dt); bool isIgnitionOn() const; float m_systemStatusLights[4]; LabeledGauge *m_dynoSpeedGauge; LabeledGauge *m_torqueGauge; LabeledGauge *m_hpGauge; LabeledGauge *m_clutchPressureGauge; double m_filteredHorsepower; double m_filteredTorque; double m_peakHorsepowerRpm; double m_peakHorsepower; double m_peakTorqueRpm; double m_peakTorque; std::string m_powerUnits; std::string m_torqueUnits; Simulator *m_simulator; }; #endif /* ATG_ENGINE_SIM_LOAD_SIMULATION_CLUSTER_H */ ================================================ FILE: include/low_pass_filter.h ================================================ #ifndef ATG_ENGINE_SIM_LOW_PASS_FILTER_H #define ATG_ENGINE_SIM_LOW_PASS_FILTER_H #include "filter.h" #include "constants.h" class LowPassFilter : public Filter { public: LowPassFilter(); virtual ~LowPassFilter(); virtual float f(float sample) override; __forceinline float fast_f(float sample) { const float alpha = m_dt / (m_rc + m_dt); m_y = alpha * sample + (1 - alpha) * m_y; return m_y; } inline void setCutoffFrequency(float f) { m_rc = 1.0f / (f * 2.0f * static_cast(constants::pi)); } float m_dt; protected: float m_y; float m_rc; }; #endif /* ATG_ENGINE_SIM_LOW_PASS_FILTER_H */ ================================================ FILE: include/mixer_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_MIXER_CLUSTER_H #define ATG_ENGINE_SIM_MIXER_CLUSTER_H #include "ui_element.h" #include "engine.h" #include "gauge.h" #include "cylinder_temperature_gauge.h" #include "cylinder_pressure_gauge.h" #include "labeled_gauge.h" #include "throttle_display.h" #include "simulator.h" class MixerCluster : public UiElement { public: MixerCluster(); virtual ~MixerCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void setSimulator(Simulator *simulator) { m_simulator = simulator; } protected: Simulator *m_simulator; protected: LabeledGauge *m_volumeGauge, *m_convolutionGauge, *m_highFreqFilterGauge, *m_levelerGauge, *m_noise0Gauge, *m_noise1Gauge; }; #endif /* ATG_ENGINE_SIM_MIXER_CLUSTER_H */ ================================================ FILE: include/oscilloscope.h ================================================ #ifndef ATG_ENGINE_SIM_OSCILLOSCOPE_H #define ATG_ENGINE_SIM_OSCILLOSCOPE_H #include "ui_element.h" class Oscilloscope : public UiElement { public: struct DataPoint { double x, y; }; public: Oscilloscope(); virtual ~Oscilloscope(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void render(const Bounds &bounds); Point dataPointToRenderPosition( const DataPoint &p, const Bounds &bounds) const; void addDataPoint(double x, double y); void setBufferSize(int n); void reset(); double m_xMin; double m_xMax; double m_yMin; double m_yMax; double m_dynamicallyResizeX; double m_dynamicallyResizeY; double m_lineWidth; bool m_drawReverse; bool m_drawZero; ysVector i_color; protected: DataPoint *m_points; Point *m_renderBuffer; int m_writeIndex; int m_bufferSize; int m_pointCount; }; #endif /* ATG_ENGINE_SIM_OSCILLOSCOPE_H */ ================================================ FILE: include/oscilloscope_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_OSCILLOSCOPE_CLUSTER_H #define ATG_ENGINE_SIM_OSCILLOSCOPE_CLUSTER_H #include "ui_element.h" #include "simulator.h" #include "oscilloscope.h" class OscilloscopeCluster : public UiElement { private: static constexpr int MaxLayeredScopes = 5; public: OscilloscopeCluster(); virtual ~OscilloscopeCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void signal(UiElement *element, Event event); virtual void update(float dt); virtual void render(); void sample(); void setSimulator(Simulator *simulator); Oscilloscope *getTotalExhaustFlowOscilloscope() const { return m_totalExhaustFlowScope; } Oscilloscope *getExhaustFlowOscilloscope() const { return m_exhaustFlowScope; } Oscilloscope *getIntakeFlowOscilloscope() const { return m_intakeFlowScope; } Oscilloscope *getAudioWaveformOscilloscope() const { return m_audioWaveformScope; } Oscilloscope *getIntakeValveLiftOscilloscope() const { return m_intakeValveLiftScope; } Oscilloscope *getExhaustValveLiftOscilloscope() const { return m_exhaustValveLiftScope; } Oscilloscope *getCylinderPressureScope() const { return m_cylinderPressureScope; } Oscilloscope *getSparkAdvanceScope() const { return m_sparkAdvanceScope; } Oscilloscope *getCylinderMoleculesScope() const { return m_cylinderMoleculesScope; } Oscilloscope *getPvScope() const { return m_pvScope; } void setDynoMaxRange(double redline) { m_torqueScope->m_xMax = redline + 500; m_powerScope->m_xMax = redline + 500; } protected: void renderScope( Oscilloscope *osc, const Bounds &bounds, const std::string &title, bool overlay=false); Simulator *m_simulator; Oscilloscope *m_torqueScope, *m_powerScope, *m_audioWaveformScope, *m_exhaustValveLiftScope, *m_intakeValveLiftScope, *m_cylinderPressureScope, *m_totalExhaustFlowScope, *m_exhaustFlowScope, *m_intakeFlowScope, *m_cylinderMoleculesScope, *m_sparkAdvanceScope, *m_pvScope, *m_currentFocusScopes[MaxLayeredScopes]; float m_updatePeriod; float m_updateTimer; double m_torque; double m_power; std::string m_powerUnits; std::string m_torqueUnits; }; #endif /* ATG_ENGINE_SIM_OSCILLOSCOPE_CLUSTER_H */ ================================================ FILE: include/part.h ================================================ #ifndef ATG_ENGINE_SIM_PART_H #define ATG_ENGINE_SIM_PART_H #include "scs.h" class Part { public: Part(); virtual ~Part(); virtual void destroy(); atg_scs::RigidBody m_body; }; #endif /* ATG_ENGINE_SIM_PART_H */ ================================================ FILE: include/performance_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_PERFORMANCE_CLUSTER_H #define ATG_ENGINE_SIM_PERFORMANCE_CLUSTER_H #include "ui_element.h" #include "engine.h" #include "gauge.h" #include "cylinder_temperature_gauge.h" #include "cylinder_pressure_gauge.h" #include "labeled_gauge.h" #include "throttle_display.h" #include "simulator.h" class PerformanceCluster : public UiElement { public: PerformanceCluster(); virtual ~PerformanceCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void setSimulator(Simulator *simulator) { m_simulator = simulator; } void addTimePerTimestepSample(double sample); void addAudioLatencySample(double sample); void addInputBufferUsageSample(double sample); LabeledGauge *m_timePerTimestepGauge; LabeledGauge *m_fpsGauge; LabeledGauge *m_simSpeedGauge; LabeledGauge *m_simulationFrequencyGauge; LabeledGauge *m_inputSamplesGauge; LabeledGauge *m_audioLagGauge; protected: double m_timePerTimestep; double m_filteredSimulationFrequency; double m_audioLatency; double m_inputBufferUsage; Simulator *m_simulator; }; #endif /* ATG_ENGINE_SIM_PERFORMANCE_CLUSTER_H */ ================================================ FILE: include/piston.h ================================================ #ifndef ATG_ENGINE_SIM_PISTON_H #define ATG_ENGINE_SIM_PISTON_H #include "part.h" class ConnectingRod; class CylinderBank; class Piston : public Part { public: struct Parameters { ConnectingRod *Rod; CylinderBank *Bank; int CylinderIndex; double BlowbyFlowCoefficient; double CompressionHeight; double WristPinPosition; double Displacement; double mass; }; public: Piston(); virtual ~Piston(); void initialize(const Parameters ¶ms); inline void setCylinderConstraint(atg_scs::LineConstraint *constraint); virtual void destroy(); double relativeX() const; double relativeY() const; double calculateCylinderWallForce() const; inline ConnectingRod *getRod() const { return m_rod; } inline CylinderBank *getCylinderBank() const { return m_bank; } inline int getCylinderIndex() const { return m_cylinderIndex; } inline double getCompressionHeight() const { return m_compressionHeight; } inline double getDisplacement() const { return m_displacement; } inline double getWristPinLocation() const { return m_wristPinLocation; } inline double getMass() const { return m_mass; } inline double getBlowbyK() const { return m_blowby_k; } protected: ConnectingRod *m_rod; CylinderBank *m_bank; atg_scs::LineConstraint *m_cylinderConstraint; int m_cylinderIndex; double m_compressionHeight; double m_displacement; double m_wristPinLocation; double m_mass; double m_blowby_k; }; void Piston::setCylinderConstraint(atg_scs::LineConstraint *constraint) { m_cylinderConstraint = constraint; } #endif /* ATG_ENGINE_SIM_PISTON_H */ ================================================ FILE: include/piston_engine_simulator.h ================================================ #ifndef ATG_ENGINE_SIM_PISTON_ENGINE_SIMULATOR_H #define ATG_ENGINE_SIM_PISTON_ENGINE_SIMULATOR_H #include "simulator.h" #include "engine.h" #include "transmission.h" #include "combustion_chamber.h" #include "vehicle.h" #include "synthesizer.h" #include "dynamometer.h" #include "starter_motor.h" #include "derivative_filter.h" #include "vehicle_drag_constraint.h" #include "delay_filter.h" #include "scs.h" #include class PistonEngineSimulator : public Simulator { public: PistonEngineSimulator(); virtual ~PistonEngineSimulator() override; void loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission); virtual double getTotalExhaustFlow() const; void endFrame(); virtual void destroy() override; void setFluidSimulationSteps(int steps) { m_fluidSimulationSteps = steps; } int getFluidSimulationSteps() const { return m_fluidSimulationSteps; } int getFluidSimulationFrequency() const { return m_fluidSimulationSteps * getSimulationFrequency(); } virtual double getAverageOutputSignal() const override; DerivativeFilter m_derivativeFilter; protected: virtual void simulateStep_() override; protected: void placeAndInitialize(); void placeCylinder(int i); protected: virtual void writeToSynthesizer() override; protected: DelayFilter *m_delayFilters; atg_scs::FixedPositionConstraint *m_crankConstraints; atg_scs::ClutchConstraint *m_crankshaftLinks; atg_scs::RotationFrictionConstraint *m_crankshaftFrictionConstraints; atg_scs::LineConstraint *m_cylinderWallConstraints; atg_scs::LinkConstraint *m_linkConstraints; atg_scs::RigidBody m_vehicleMass; VehicleDragConstraint m_vehicleDrag; std::chrono::steady_clock::time_point m_simulationStart; std::chrono::steady_clock::time_point m_simulationEnd; Engine *m_engine; Transmission *m_transmission; Vehicle *m_vehicle; double *m_exhaustFlowStagingBuffer; int m_fluidSimulationSteps; }; #endif /* ATG_ENGINE_SIM_PISTON_ENGINE_SIMULATOR_H */ ================================================ FILE: include/piston_object.h ================================================ #ifndef ATG_ENGINE_SIM_PISTON_OBJECT_H #define ATG_ENGINE_SIM_PISTON_OBJECT_H #include "simulation_object.h" #include "piston.h" #include "geometry_generator.h" class PistonObject : public SimulationObject { public: PistonObject(); virtual ~PistonObject(); virtual void generateGeometry(); virtual void render(const ViewParameters *view); virtual void process(float dt); virtual void destroy(); Piston *m_piston; protected: GeometryGenerator::GeometryIndices m_wristPinHole; }; #endif /* ATG_ENGINE_SIM_PISTON_OBJECT_H */ ================================================ FILE: include/preemphasis_filter.h ================================================ #ifndef ATG_ENGINE_SIM_PREEMPHASIS_FILTER_H #define ATG_ENGINE_SIM_PREEMPHASIS_FILTER_H #include "filter.h" #include "low_pass_filter.h" #include class PreemphasisFilter : public Filter { public: PreemphasisFilter() { m_lastSample = 0; } virtual ~PreemphasisFilter() {} virtual float f(float sample) override { return fast_f(sample); } __forceinline float fast_f(float sample) { const float s = -0.95f * sample + m_lastSample; m_lastSample = sample; return s; } protected: float m_lastSample; }; #endif /* ATG_ENGINE_SIM_PREEMPHASIS_FILTER_H */ ================================================ FILE: include/right_gauge_cluster.h ================================================ #ifndef ATG_ENGINE_SIM_RIGHT_GAUGE_CLUSTER_H #define ATG_ENGINE_SIM_RIGHT_GAUGE_CLUSTER_H #include "ui_element.h" #include "engine.h" #include "simulator.h" #include "gauge.h" #include "firing_order_display.h" #include "labeled_gauge.h" #include "throttle_display.h" #include "afr_cluster.h" #include "fuel_cluster.h" class RightGaugeCluster : public UiElement { public: RightGaugeCluster(); virtual ~RightGaugeCluster(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); void setEngine(Engine *engine); void setUnits(); double getManifoldPressureWithUnits(double ambientPressure); Simulator *m_simulator; private: double getRpm() const; double getRedline() const; double getSpeed() const; double getManifoldPressure() const; protected: Engine *m_engine; void renderTachSpeedCluster(const Bounds &bounds); void renderFuelAirCluster(const Bounds &bounds); LabeledGauge *m_tachometer; LabeledGauge *m_speedometer; LabeledGauge *m_manifoldVacuumGauge; LabeledGauge *m_intakeCfmGauge; LabeledGauge *m_volumetricEffGauge; FuelCluster *m_fuelCluster; ThrottleDisplay *m_throttleDisplay; AfrCluster *m_afrCluster; FiringOrderDisplay *m_combusionChamberStatus; std::string m_speedUnits; std::string m_pressureUnits; bool m_isAbsolute; }; #endif /* ATG_ENGINE_SIM_GAUGE_CLUSTER_H */ ================================================ FILE: include/ring_buffer.h ================================================ #ifndef ATG_ENGINE_SIM_RING_BUFFER_H #define ATG_ENGINE_SIM_RING_BUFFER_H #include "part.h" #include template class RingBuffer { public: RingBuffer() { m_buffer = nullptr; m_capacity = 0; m_writeIndex = 0; m_start = 0; } ~RingBuffer() { destroy(); } void initialize(size_t capacity) { m_buffer = new T_Data[capacity]; m_capacity = capacity; m_writeIndex = 0; m_start = 0; } void destroy() { if (m_buffer != nullptr) { delete[] m_buffer; m_buffer = nullptr; } m_capacity = 0; m_writeIndex = 0; m_start = 0; } inline void write(T_Data data) { m_buffer[m_writeIndex] = data; if (++m_writeIndex >= m_capacity) { m_writeIndex = 0; } } inline void overwrite(T_Data data, size_t index) { if (start + index < m_capacity) { m_buffer[m_start + index] = data; } else { m_buffer[m_start + index - m_capacity] = data; } } inline size_t index(size_t base, int offset) { if (offset == 0) return base; else if (offset < 0) { const size_t offset_u = -offset; if (offset_u <= base) return base - offset_u; else return (base + m_capacity) - offset_u; } else { const size_t offset_u = offset; const size_t rawOffset = base + offset_u; if (rawOffset >= m_capacity) return rawOffset - m_capacity; else return rawOffset; } } inline T_Data read(size_t index) const { return (m_start + index) >= m_capacity ? m_buffer[m_start + index - m_capacity] : m_buffer[m_start + index]; } inline void read(size_t n, T_Data *target) { if (m_start + n < m_capacity) { memcpy(target, m_buffer + m_start, n * sizeof(T_Data)); } else { memcpy( target, m_buffer + m_start, (m_capacity - m_start) * sizeof(T_Data)); memcpy( target + (m_capacity - m_start), m_buffer, (n - (m_capacity - m_start)) * sizeof(T_Data)); } } inline void readAndRemove(size_t n, T_Data *target) { if (m_start + n < m_capacity) { memcpy(target, m_buffer + m_start, n * sizeof(T_Data)); } else { memcpy( target, m_buffer + m_start, (m_capacity - m_start) * sizeof(T_Data)); memcpy( target + (m_capacity - m_start), m_buffer, (n - (m_capacity - m_start)) * sizeof(T_Data)); } m_start += n; if (m_start >= m_capacity) { m_start -= m_capacity; } } inline void setWriteIndex(size_t writeIndex) { m_writeIndex = writeIndex; } inline void removeBeginning(size_t n) { m_start += n; if (m_start >= m_capacity) { m_start -= m_capacity; } } inline void setStartIndex(size_t startIndex) { m_start = startIndex; } inline size_t size() const { return (m_writeIndex < m_start) ? m_writeIndex + (m_capacity - m_start) : m_writeIndex - m_start; } inline size_t writeIndex() const { return m_writeIndex; } inline size_t start() const { return m_start; } private: T_Data *m_buffer; size_t m_capacity; size_t m_writeIndex; size_t m_start; }; #endif /* ATG_ENGINE_SIM_RING_BUFFER_H */ ================================================ FILE: include/scs.h ================================================ #ifndef ATG_ENGINE_SIM_SCS_H #define ATG_ENGINE_SIM_SCS_H #include "../dependencies/submodules/simple-2d-constraint-solver/include/scs.h" #endif /* ATG_ENGINE_SIM_SCS_H */ ================================================ FILE: include/shaders.h ================================================ #ifndef ATG_ENGINE_SIM_SHADERS_H #define ATG_ENGINE_SIM_SHADERS_H #include "delta.h" #include "ui_math.h" class Shaders : public dbasic::ShaderBase { public: Shaders(); ~Shaders(); ysError Initialize( dbasic::ShaderSet *shaderSet, ysRenderTarget *mainRenderTarget, ysRenderTarget *uiRenderTarget, ysShaderProgram *shaderProgram, ysInputLayout *inputLayout); virtual ysError UseMaterial(dbasic::Material *material); virtual void SetObjectTransform(const ysMatrix &mat); virtual void ConfigureModel(float scale, dbasic::ModelAsset *model); void SetBaseColor(const ysVector &color); void ResetBaseColor(); dbasic::StageEnableFlags GetRegularFlags() const; dbasic::StageEnableFlags GetUiFlags() const; void CalculateCamera( float width, float height, const Bounds &cameraBounds, float screenWidth, float screenHeight, float angle = 0.0f); void CalculateUiCamera(float screenWidth, float screenHeight); void SetClearColor(const ysVector &col); public: dbasic::ShaderScreenVariables m_screenVariables; dbasic::ShaderScreenVariables m_uiScreenVariables; dbasic::ShaderObjectVariables m_objectVariables; ysVector m_cameraPosition; protected: dbasic::ShaderStage *m_mainStage; dbasic::ShaderStage *m_uiStage; dbasic::LightingControls m_lightingControls; }; #endif /* ATG_ENGINE_SIM_SHADERS_H */ ================================================ FILE: include/simulation_object.h ================================================ #ifndef ATG_ENGINE_SIM_SIMULATION_OBJECT_H #define ATG_ENGINE_SIM_SIMULATION_OBJECT_H #include "scs.h" #include "delta.h" class Piston; class CylinderBank; class EngineSimApplication; class SimulationObject { public: struct ViewParameters { int Layer0; int Layer1; int Sublayer; }; public: SimulationObject(); virtual ~SimulationObject(); virtual void initialize(EngineSimApplication *app); virtual void generateGeometry(); virtual void render(const ViewParameters *settings); virtual void process(float dt); virtual void destroy(); Piston *getForemostPiston(CylinderBank *bank, int layer); protected: void resetShader(); void setTransform( atg_scs::RigidBody *rigidBody, float scale = 1.0f, float lx = 0.0f, float ly = 0.0f, float theta = 0.0f, float z = 0.0f); ysVector tintByLayer(const ysVector &col, int layers) const; EngineSimApplication *m_app; }; #endif /* ATG_ENGINE_SIM_SIMULATION_OBJECT_H */ ================================================ FILE: include/simulator.h ================================================ #ifndef ATG_ENGINE_SIM_SIMULATOR_H #define ATG_ENGINE_SIM_SIMULATOR_H #include "engine.h" #include "transmission.h" #include "vehicle.h" #include "synthesizer.h" #include "dynamometer.h" #include "starter_motor.h" #include "derivative_filter.h" #include "vehicle_drag_constraint.h" #include "delay_filter.h" #include "engine.h" #include class Simulator { public: enum class SystemType { NsvOptimized, Generic }; struct Parameters { SystemType systemType = SystemType::NsvOptimized; }; static constexpr int DynoTorqueSamples = 512; public: Simulator(); virtual ~Simulator(); virtual void initialize(const Parameters ¶ms); void loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission); void releaseSimulation(); virtual void startFrame(double dt); bool simulateStep(); virtual double getTotalExhaustFlow() const; int readAudioOutput(int samples, int16_t *target); virtual void endFrame(); virtual void destroy(); void startAudioRenderingThread(); void endAudioRenderingThread(); int getFrameIterationCount() const { return m_steps; } Synthesizer &synthesizer() { return m_synthesizer; } Engine *getEngine() const { return m_engine; } Transmission *getTransmission() const { return m_transmission; } Vehicle *getVehicle() const { return m_vehicle; } atg_scs::RigidBodySystem *getSystem() { return m_system; } void setSimulationFrequency(int frequency) { m_simulationFrequency = frequency; } int getSimulationFrequency() const { return m_simulationFrequency; } double getTimestep() const { return 1.0 / m_simulationFrequency; } void setTargetSynthesizerLatency(double latency) { m_targetSynthesizerLatency = latency; } double getTargetSynthesizerLatency() const { return m_targetSynthesizerLatency; } double getSynthesizerInputLatency() const { return m_synthesizer.getLatency(); } double getSynthesizerInputLatencyTarget() const; void setSimulationSpeed(double simSpeed) { m_simulationSpeed = simSpeed; } double getSimulationSpeed() const { return m_simulationSpeed; } int getCurrentIteration() const { return m_currentIteration; } double getAverageProcessingTime() const { return m_physicsProcessingTime; } int simulationSteps() const { return m_steps; } virtual double getFilteredDynoTorque() const; virtual double getDynoPower() const; virtual double getAverageOutputSignal() const; double filteredEngineSpeed() const { return m_filteredEngineSpeed; } Dynamometer m_dyno; StarterMotor m_starterMotor; protected: void initializeSynthesizer(); virtual void simulateStep_(); virtual void writeToSynthesizer() = 0; atg_scs::RigidBodySystem *m_system; private: void updateFilteredEngineSpeed(double dt); private: atg_scs::RigidBody m_vehicleMass; VehicleDragConstraint m_vehicleDrag; Synthesizer m_synthesizer; std::chrono::steady_clock::time_point m_simulationStart; std::chrono::steady_clock::time_point m_simulationEnd; int m_currentIteration; Engine *m_engine; Transmission *m_transmission; Vehicle *m_vehicle; double m_physicsProcessingTime; int m_simulationFrequency; double m_targetSynthesizerLatency; double m_simulationSpeed; double *m_dynoTorqueSamples; int m_lastDynoTorqueSample; double m_filteredEngineSpeed; int m_steps; }; #endif /* ATG_ENGINE_SIM_SIMULATOR_H */ ================================================ FILE: include/standard_valvetrain.h ================================================ #ifndef ATG_ENGINE_SIM_STANDARD_VALVETRAIN_H #define ATG_ENGINE_SIM_STANDARD_VALVETRAIN_H #include "valvetrain.h" class StandardValvetrain : public Valvetrain { public: struct Parameters { Camshaft *intakeCamshaft; Camshaft *exhaustCamshaft; }; public: StandardValvetrain(); virtual ~StandardValvetrain(); void initialize(const Parameters ¶meters); virtual double intakeValveLift(int cylinder) override; virtual double exhaustValveLift(int cylinder) override; virtual Camshaft *getActiveIntakeCamshaft() override; virtual Camshaft *getActiveExhaustCamshaft() override; private: Camshaft *m_intakeCamshaft; Camshaft *m_exhaustCamshaft; }; #endif /* ATG_ENGINE_SIM_STANDARD_VALVETRAIN_H */ ================================================ FILE: include/starter_motor.h ================================================ #ifndef ATG_ENGINE_SIM_STARTER_MOTOR_H #define ATG_ENGINE_SIM_STARTER_MOTOR_H #include "scs.h" #include "crankshaft.h" class StarterMotor : public atg_scs::Constraint { public: StarterMotor(); virtual ~StarterMotor(); void connectCrankshaft(Crankshaft *crankshaft); virtual void calculate(Output *output, atg_scs::SystemState *state); double m_ks; double m_kd; double m_maxTorque; double m_rotationSpeed; bool m_enabled; }; #endif /* ATG_ENGINE_SIM_STARTER_MOTOR_H */ ================================================ FILE: include/synthesizer.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_SYNTHESIZER_H #define ATG_ENGINE_SIM_ENGINE_SYNTHESIZER_H #include "convolution_filter.h" #include "leveling_filter.h" #include "derivative_filter.h" #include "low_pass_filter.h" #include "jitter_filter.h" #include "ring_buffer.h" #include "butterworth_low_pass_filter.h" #include #include #include #include #include class Synthesizer { public: struct AudioParameters { float volume = 1.0f; float convolution = 1.0f; float dF_F_mix = 0.01f; float inputSampleNoise = 0.5f; float inputSampleNoiseFrequencyCutoff = 10000.0f; float airNoise = 1.0f; float airNoiseFrequencyCutoff = 2000.0f; float levelerTarget = 30000.0f; float levelerMaxGain = 1.9f; float levelerMinGain = 0.00001f; }; struct Parameters { int inputChannelCount = 1; int inputBufferSize = 1024; int audioBufferSize = 44100; float inputSampleRate = 10000; float audioSampleRate = 44100; AudioParameters initialAudioParameters; }; struct InputChannel { RingBuffer data; float *transferBuffer = nullptr; double lastInputSample = 0.0f; }; struct ProcessingFilters { ConvolutionFilter convolution; DerivativeFilter derivative; JitterFilter jitterFilter; ButterworthLowPassFilter airNoiseLowPass; LowPassFilter inputDcFilter; ButterworthLowPassFilter antialiasing; }; public: Synthesizer(); ~Synthesizer(); void initialize(const Parameters &p); void initializeImpulseResponse( const int16_t *impulseResponse, unsigned int samples, float volume, int index); void startAudioRenderingThread(); void endAudioRenderingThread(); void destroy(); int readAudioOutput(int samples, int16_t *buffer); void writeInput(const double *data); void endInputBlock(); void waitProcessed(); void audioRenderingThread(); void renderAudio(); double getLatency() const; int inputDelta(int s1, int s0) const; double inputDistance(double s1, double s0) const; void setInputSampleRate(double sampleRate); double getInputSampleRate() const { return m_inputSampleRate; } int16_t renderAudio(int inputOffset); double getLevelerGain(); AudioParameters getAudioParameters(); void setAudioParameters(const AudioParameters ¶ms); //protected: ButterworthLowPassFilter m_antialiasing; LevelingFilter m_levelingFilter; InputChannel *m_inputChannels; AudioParameters m_audioParameters; int m_inputChannelCount; int m_inputBufferSize; int m_inputSamplesRead; int m_latency; double m_inputWriteOffset; double m_lastInputSampleOffset; RingBuffer m_audioBuffer; int m_audioBufferSize; float m_inputSampleRate; float m_audioSampleRate; std::thread *m_thread; std::atomic m_run; bool m_processed; std::mutex m_inputLock; std::mutex m_lock0; std::condition_variable m_cv0; ProcessingFilters *m_filters; }; #endif /* ATG_ENGINE_SIM_ENGINE_SYNTHESIZER_H */ ================================================ FILE: include/throttle.h ================================================ #ifndef ATG_ENGINE_SIM_THROTTLE_H #define ATG_ENGINE_SIM_THROTTLE_H #include "part.h" class Engine; class Throttle { public: Throttle(); virtual ~Throttle(); virtual void setSpeedControl(double s); virtual void update(double dt, Engine *engine); inline double getSpeedControl() const { return m_speedControl; } protected: double m_speedControl; }; #endif /* ATG_ENGINE_SIM_THROTTLE_H */ ================================================ FILE: include/throttle_display.h ================================================ #ifndef ATG_ENGINE_SIM_THROTTLE_DISPLAY_H #define ATG_ENGINE_SIM_THROTTLE_DISPLAY_H #include "ui_element.h" #include "engine.h" #include "geometry_generator.h" class ThrottleDisplay : public UiElement { public: ThrottleDisplay(); virtual ~ThrottleDisplay(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); Engine *m_engine; protected: void renderThrottle(const Bounds &bounds); void renderSpeedControl(const Bounds &bounds); }; #endif /* ATG_ENGINE_SIM_THROTTLE_DISPLAY_H */ ================================================ FILE: include/transmission.h ================================================ #ifndef ATG_ENGINE_SIM_TRANSMISSION_H #define ATG_ENGINE_SIM_TRANSMISSION_H #include "vehicle.h" #include "engine.h" #include "scs.h" class Transmission { public: struct Parameters { int GearCount; const double *GearRatios; double MaxClutchTorque; }; public: Transmission(); ~Transmission(); void initialize(const Parameters ¶ms); void update(double dt); void addToSystem( atg_scs::RigidBodySystem *system, atg_scs::RigidBody *rotatingMass, Vehicle *vehicle, Engine *engine); void changeGear(int newGear); inline int getGear() const { return m_gear; } inline void setClutchPressure(double pressure) { m_clutchPressure = pressure; } inline double getClutchPressure() const { return m_clutchPressure; } protected: atg_scs::ClutchConstraint m_clutchConstraint; atg_scs::RigidBody *m_rotatingMass; Vehicle *m_vehicle; int m_gear; int m_newGear; int m_gearCount; double *m_gearRatios; double m_maxClutchTorque; double m_clutchPressure; }; #endif /* ATG_ENGINE_SIM_TRANSMISSION_H */ ================================================ FILE: include/ui_button.h ================================================ #ifndef ATG_ENGINE_SIM_UI_BUTTON_H #define ATG_ENGINE_SIM_UI_BUTTON_H #include "ui_element.h" class UiButton : public UiElement { public: UiButton(); virtual ~UiButton(); virtual void update(float dt); virtual void render(); std::string m_text; float m_fontSize; }; #endif /* ATG_ENGINE_SIM_UI_BUTTON_H */ ================================================ FILE: include/ui_element.h ================================================ #ifndef ATG_ENGINE_SIM_UI_ELEMENT_H #define ATG_ENGINE_SIM_UI_ELEMENT_H #include "ui_math.h" #include "delta.h" #include class EngineSimApplication; class UiElement { public: enum class Event { Clicked }; public: UiElement(); virtual ~UiElement(); virtual void initialize(EngineSimApplication *app); virtual void destroy(); virtual void update(float dt); virtual void render(); virtual void signal(UiElement *element, Event event); virtual void onMouseDown(const Point &mouseLocal); virtual void onMouseUp(const Point &mouseLocal); virtual void onMouseClick(const Point &mouseLocal); virtual void onDrag(const Point &p0, const Point &mouse0, const Point &mouse); virtual void onMouseOver(const Point &mouseLocal); virtual void onMouseLeave(); virtual void onMouseScroll(int mouseScroll); bool isMouseOver() const { return m_mouseOver; } bool isMouseHeld() const { return m_mouseHeld; } template T_Element *addElement(UiElement *signalTarget = nullptr) { T_Element *newElement = new T_Element; newElement->initialize(m_app); newElement->m_parent = this; newElement->m_signalTarget = signalTarget; newElement->m_index = (int)m_children.size(); m_children.push_back(newElement); return newElement; } UiElement *mouseOver(const Point &mouseLocal); Point getWorldPosition() const; Point getLocalPosition() const { return m_localPosition; } void setLocalPosition(const Point &p) { m_localPosition = p; } void setLocalPosition(const Point &p, const Point &ref); Point worldToLocal(const Point &wp) const { return wp - getWorldPosition(); } Point localToWorld(const Point &lp) const { return lp + getWorldPosition(); } void setVisible(bool visible) { m_visible = visible; } bool isVisible() const { return m_visible; } size_t getChildCount() const { return m_children.size(); } void bringToFront(UiElement *element); void activate(); Bounds m_bounds; protected: void signal(Event event); float pixelsToUnits(float length) const; Point pixelsToUnits(const Point &p) const; float unitsToPixels(float x) const; Point unitsToPixels(const Point &p) const; Point getRenderPoint(const Point &p) const; Bounds getRenderBounds(const Bounds &b) const; Bounds unitsToPixels(const Bounds &b) const; void resetShader(); void drawModel( dbasic::ModelAsset *model, const ysVector &color, const Point &p, const Point &scale = { 1.0f, 1.0f }); void drawFrame( const Bounds &bounds, float thickness, const ysVector &frameColor, const ysVector &fillColor, bool fill = true); void drawBox( const Bounds &bounds, const ysVector &fillColor); void drawText( const std::string &s, const Bounds &bounds, float height, const Point &ref = Bounds::tl); void drawAlignedText( const std::string &s, const Bounds &bounds, float height, const Point &ref = Bounds::tl, const Point &refText = Bounds::tl); void drawCenteredText( const std::string &s, const Bounds &bounds, float height, const Point &ref = Bounds::tm); protected: std::vector m_children; UiElement *m_parent; UiElement *m_signalTarget; protected: Point m_localPosition; Bounds m_mouseBounds; bool m_checkMouse; bool m_disabled; int m_index; bool m_draggable; bool m_mouseOver; bool m_mouseHeld; bool m_visible; protected: EngineSimApplication *m_app; }; #endif /* ATG_ENGINE_SIM_UI_ELEMENT_H */ ================================================ FILE: include/ui_manager.h ================================================ #ifndef ATG_ENGINE_SIM_UI_MANAGER_H #define ATG_ENGINE_SIM_UI_MANAGER_H #include "ui_element.h" #include class EngineSimApplication; class UiManager { public: UiManager(); ~UiManager(); void initialize(EngineSimApplication *app); void destroy(); void update(float dt); void render(); UiElement *getRoot() { return &m_root; } protected: UiElement m_root; UiElement *m_dragStart; UiElement *m_hover; Point m_mouse_p0; Point m_drag_p0; int m_lastMouseScroll; protected: EngineSimApplication *m_app; }; #endif /* ATG_ENGINE_SIM_UI_MANAGER_H */ ================================================ FILE: include/ui_math.h ================================================ #ifndef ATG_ENGINE_SIM_UI_MATH_H #define ATG_ENGINE_SIM_UI_MATH_H #include struct Point { float x, y; Point(float x, float y) : x(x), y(y) { /* void */ } Point(float s) : x(s), y(s) { /* void */ } Point() : x(0), y(0) { /* void */ } Point operator+(const Point &b) const { return { x + b.x, y + b.y }; } Point operator-(const Point &b) const { return { x - b.x, y - b.y }; } Point operator-() const { return { -x, -y }; } Point operator*(const Point &b) const { return { x * b.x, y * b.y }; } Point operator*(float s) const { return { x * s, y * s }; } Point operator/(const Point &b) const { return { x / b.x, y / b.y }; } Point operator/(float s) const { return { x / s, y / s }; } Point operator+=(const Point &b) { x += b.x; y += b.y; return { x, y }; } Point operator-=(const Point &b) { x -= b.x; y -= b.y; return { x, y }; } Point operator/=(const Point &b) { x /= b.x; y /= b.y; return { x, y }; } Point operator*=(const Point &b) { x *= b.x; y *= b.y; return { x, y }; } bool operator>(const Point &b) const { return x > b.x && y > b.y; } bool operator>=(const Point &b) const { return x >= b.x && y >= b.y; } bool operator<(const Point &b) const { return x < b.x && y < b.y; } bool operator<=(const Point &b) const { return x <= b.x && y <= b.y; } float length() const { return std::sqrt(x * x + y * y); } float lengthSquared() const { return x * x + y * y; } float dot(const Point &b) const { return x * b.x + y * b.y; } Point normalized() const { return (*this) / length(); } Point componentMax(const Point &b) const { return { std::fmax(x, b.x), std::fmax(y, b.y) }; } Point componentMin(const Point &b) const { return { std::fmin(x, b.x), std::fmin(y, b.y) }; } }; struct Bounds { static const Point center; static const Point tl, tr, tm; static const Point bl, br, bm; static const Point lm, rm; Point m0; Point m1; Bounds() { /* void */ } Bounds(const Point &a, const Point &b) : m0(a), m1(b) { /* void */ } Bounds(float x0, float x1, float y0, float y1) { m0 = { std::fmin(x0, x1), std::fmin(y0, y1) }; m1 = { std::fmax(x0, x1), std::fmax(y0, y1) }; } Bounds(float width, float height, const Point &pos, const Point &ref = tl) { m0 = Point(0, 0); m1 = Point(width, height); setPosition(pos, ref); } bool overlaps(const Point &p) const { return p >= m0 && p <= m1; } Bounds add(const Bounds &b) const { return { m0.componentMin(b.m0), m1.componentMax(b.m1) }; } Bounds move(const Point &delta) { m0 += delta; m1 += delta; return *this; } void setPosition(const Point &pos, const Point &ref = tl) { move(pos - getPosition(ref)); } Point getPosition(const Point &ref = tl) const { const Point offset = ref * dim(); return m0 + offset; } float left() const { return m0.x; } float right() const { return m1.x; } float top() const { return m1.y; } float bottom() const { return m0.y; } float center_h() const { return (m0.x + m1.x) / 2; } float center_v() const { return (m0.y + m1.y) / 2; } float width() const { return m1.x - m0.x; } float height() const { return m1.y - m0.y; } Point dim() const { return Point(width(), height()); } Bounds inset(float amount) const { return { m0 + amount, m1 - amount }; } Bounds grow(float amount) const { return inset(-amount); } Bounds verticalSplit(float s0, float s1) const { const float s_min = std::fmin(s0, s1); const float s_max = std::fmax(s0, s1); return { m0 + Point(0.0f, height() * s_min), m0 + Point(0.0f, height() * s_max) + Point(width(), 0.0f) }; } Bounds horizontalSplit(float s0, float s1) const { const float s_min = std::fmin(s0, s1); const float s_max = std::fmax(s0, s1); return { m0 + Point(width() * s_min, 0.0f), m0 + Point(width() * s_max, 0.0f) + Point(0.0f, height()) }; } }; struct Grid { int h_cells; int v_cells; Bounds get(const Bounds &a, int x, int y, int w = 1, int h = 1) const { const float cellWidth = a.width() / h_cells; const float cellHeight = a.height() / v_cells; const float width = cellWidth * w; const float height = cellHeight * h; const Point p0 = a.getPosition(Bounds::tl) + Point(x * cellWidth, -y * cellHeight); return Bounds(width, height, p0, Bounds::tl); } }; #endif /* ATG_ENGINE_SIM_UI_MATH_H */ ================================================ FILE: include/ui_utilities.h ================================================ #ifndef ATG_ENGINE_SIM_UI_UTILITIES_H #define ATG_ENGINE_SIM_UI_UTILITIES_H #include "delta.h" ysVector mix(const ysVector &c1, const ysVector &c2, float s); #endif /* ATG_ENGINE_SIM_UI_UTILITIES_H */ ================================================ FILE: include/units.h ================================================ #ifndef ATG_ENGINE_SIM_UNITS_H #define ATG_ENGINE_SIM_UNITS_H #include "constants.h" namespace units { // Force extern constexpr double N = 1.0; extern constexpr double lbf = N * 4.44822; // Mass extern constexpr double kg = 1.0; extern constexpr double g = kg / 1000.0; extern constexpr double lb = 0.45359237 * kg; // Distance extern constexpr double m = 1.0; extern constexpr double cm = m / 100.0; extern constexpr double mm = m / 1000.0; extern constexpr double km = m * 1000.0; extern constexpr double inch = cm * 2.54; extern constexpr double foot = inch * 12.0; extern constexpr double thou = inch / 1000.0; extern constexpr double mile = m * 1609.344; // Time extern constexpr double sec = 1.0; extern constexpr double minute = 60 * sec; extern constexpr double hour = 60 * minute; // Torque extern constexpr double Nm = N * m; extern constexpr double ft_lb = foot * lbf; // Power extern constexpr double W = Nm / sec; extern constexpr double kW = W * 1000.0; extern constexpr double hp = 745.699872 * W; // Volume extern constexpr double m3 = 1.0; extern constexpr double cc = cm * cm * cm; extern constexpr double mL = cc; extern constexpr double L = mL * 1000.0; extern constexpr double cubic_feet = foot * foot * foot; extern constexpr double cubic_inches = inch * inch * inch; extern constexpr double gal = 3.785411784 * L; // Molecular extern constexpr double mol = 1.0; extern constexpr double kmol = mol / 1000.0; extern constexpr double mmol = mol / 1000000.0; extern constexpr double lbmol = mol * 453.59237; // Flow-rate (moles) extern constexpr double mol_per_sec = mol / sec; extern constexpr double scfm = 0.002641 * lbmol / minute; // Area extern constexpr double m2 = 1.0; extern constexpr double cm2 = cm * cm; // Pressure extern constexpr double Pa = 1.0; extern constexpr double kPa = Pa * 1000.0; extern constexpr double MPa = Pa * 1000000.0; extern constexpr double atm = 101.325 * kPa; extern constexpr double mbar = Pa * 100.0; extern constexpr double bar = mbar * 1000.0; extern constexpr double psi = lbf / (inch * inch); extern constexpr double psig = psi; extern constexpr double inHg = Pa * 3386.3886666666713; extern constexpr double inH2O = inHg * 0.0734824; // Temperature extern constexpr double K = 1.0; extern constexpr double K0 = 273.15; extern constexpr double C = K; extern constexpr double F = (5.0 / 9.0) * K; extern constexpr double F0 = -459.67; // Energy extern constexpr double J = 1.0; extern constexpr double kJ = J * 1000; extern constexpr double MJ = J * 1000000; // Angles extern constexpr double rad = 1.0; extern constexpr double deg = rad * (constants::pi / 180); // Conversions inline constexpr double distance(double v, double unit) { return v * unit; } inline constexpr double area(double v, double unit) { return v * unit; } inline constexpr double torque(double v, double unit) { return v * unit; } inline constexpr double rpm(double rpm) { return rpm * 0.104719755; } inline constexpr double toRpm(double rad_s) { return rad_s / 0.104719755; } inline constexpr double pressure(double v, double unit) { return v * unit; } inline constexpr double psia(double p) { return units::pressure(p, units::psig) - units::pressure(1.0, units::atm); } inline constexpr double toPsia(double p) { return (p + units::pressure(1.0, units::atm)) / units::psig; } inline constexpr double mass(double v, double unit) { return v * unit; } inline constexpr double force(double v, double unit) { return v * unit; } inline constexpr double volume(double v, double unit) { return v * unit; } inline constexpr double flow(double v, double unit) { return v * unit; } inline constexpr double convert(double v, double unit0, double unit1) { return v * (unit0 / unit1); } inline constexpr double convert(double v, double unit) { return v / unit; } inline constexpr double celcius(double T_C) { return T_C * C + K0; } inline constexpr double kelvin(double T) { return T * K; } inline constexpr double fahrenheit(double T_F) { return F * (T_F - F0); } inline constexpr double toAbsoluteFahrenheit(double T) { return T / F; } inline constexpr double angle(double v, double unit) { return v * unit; } inline constexpr double energy(double v, double unit) { return v * unit; } // Physical Constants constexpr double AirMolecularMass = units::mass(28.97, units::g) / units::mol; }; #endif /* ATG_ENGINE_SIM_UNITS_H */ ================================================ FILE: include/utilities.h ================================================ #ifndef ATG_ENGINE_SIM_UTILITIES_H #define ATG_ENGINE_SIM_UTILITIES_H double modularDistance(double a, double b, double mod = 1.0); double positiveMod(double x, double mod); double erfApproximation(double x); template inline t clamp(t x, t x0 = static_cast(0.0), t x1 = static_cast(1.0)) { if (x <= x0) return x0; else if (x >= x1) return x1; else return x; } #endif /* ATG_ENGINE_SIM_UTILITIES_H */ ================================================ FILE: include/valvetrain.h ================================================ #ifndef ATG_ENGINE_SIM_VALVETRAIN_H #define ATG_ENGINE_SIM_VALVETRAIN_H class Camshaft; class Valvetrain { public: Valvetrain(); virtual ~Valvetrain(); virtual double intakeValveLift(int cylinder) = 0; virtual double exhaustValveLift(int cylinder) = 0; virtual Camshaft *getActiveIntakeCamshaft() = 0; virtual Camshaft *getActiveExhaustCamshaft() = 0; }; #endif /* ATG_ENGINE_SIM_VALVETRAIN_H */ ================================================ FILE: include/vehicle.h ================================================ #ifndef ATG_ENGINE_SIM_VEHICLE_H #define ATG_ENGINE_SIM_VEHICLE_H #include "scs.h" class Vehicle { public: struct Parameters { double mass; double dragCoefficient; double crossSectionArea; double diffRatio; double tireRadius; double rollingResistance; }; public: Vehicle(); ~Vehicle(); void initialize(const Parameters ¶ms); void update(double dt); void addToSystem(atg_scs::RigidBodySystem *system, atg_scs::RigidBody *rotatingMass); inline double getMass() const { return m_mass; } inline double getRollingResistance() const { return m_rollingResistance; } inline double getDragCoefficient() const { return m_dragCoefficient; } inline double getCrossSectionArea() const { return m_crossSectionArea; } inline double getDiffRatio() const { return m_diffRatio; } inline double getTireRadius() const { return m_tireRadius; } double getSpeed() const; inline double getTravelledDistance() const { return m_travelledDistance; } inline void resetTravelledDistance() { m_travelledDistance = 0; } double linearForceToVirtualTorque(double force) const; protected: atg_scs::RigidBody *m_rotatingMass; double m_mass; double m_dragCoefficient; double m_crossSectionArea; double m_diffRatio; double m_tireRadius; double m_travelledDistance; double m_rollingResistance; }; #endif /* ATG_ENGINE_SIM_VEHICLE_H */ ================================================ FILE: include/vehicle_drag_constraint.h ================================================ #ifndef ATG_ENGINE_SIM_VEHICLE_DRAG_CONSTRAINT_H #define ATG_ENGINE_SIM_VEHICLE_DRAG_CONSTRAINT_H #include "scs.h" class Vehicle; class VehicleDragConstraint : public atg_scs::Constraint { public: VehicleDragConstraint(); virtual ~VehicleDragConstraint(); void initialize(atg_scs::RigidBody *rotatingMass, Vehicle *vehicle); virtual void calculate(Output *output, atg_scs::SystemState *system); double m_ks; double m_kd; private: Vehicle *m_vehicle; }; #endif /* ATG_ENGINE_SIM_VEHICLE_DRAG_CONSTRAINT_H */ ================================================ FILE: include/vtec_valvetrain.h ================================================ #ifndef ATG_ENGINE_SIM_VTEC_STANDARD_VALVETRAIN_H #define ATG_ENGINE_SIM_VTEC_STANDARD_VALVETRAIN_H #include "valvetrain.h" class Engine; class VtecValvetrain : public Valvetrain { public: struct Parameters { double minRpm; double minSpeed; double manifoldVacuum; double minThrottlePosition; Camshaft *intakeCamshaft; Camshaft *exhaustCamshaft; Camshaft *vtecIntakeCamshaft; Camshaft *vtexExhaustCamshaft; Engine *engine; }; public: VtecValvetrain(); virtual ~VtecValvetrain(); void initialize(const Parameters ¶meters); virtual double intakeValveLift(int cylinder) override; virtual double exhaustValveLift(int cylinder) override; virtual Camshaft *getActiveIntakeCamshaft() override; virtual Camshaft *getActiveExhaustCamshaft() override; private: bool isVtecEnabled() const; Camshaft *m_intakeCamshaft; Camshaft *m_exhaustCamshaft; Camshaft *m_vtecIntakeCamshaft; Camshaft *m_vtecExhaustCamshaft; Engine *m_engine; double m_minRpm; double m_minSpeed; double m_manifoldVacuum; double m_minThrottlePosition; }; #endif /* ATG_ENGINE_SIM_VTEC_STANDARD_VALVETRAIN_H */ ================================================ FILE: scripting/include/actions.h ================================================ #ifndef ATG_ENGINE_SIM_ACTIONS_H #define ATG_ENGINE_SIM_ACTIONS_H #include "piranha.h" #include "compiler.h" #include "object_reference_node.h" #include "engine_node.h" #include "crankshaft_node.h" #include "rod_journal_node.h" #include "cylinder_bank_node.h" #include "function_node.h" #include "camshaft_node.h" #include "cylinder_head_node.h" #include "cylinder_bank_node.h" #include "ignition_module_node.h" #include "transmission_node.h" #include "vehicle_node.h" namespace es_script { class SetEngineNode : public piranha::Node { public: SetEngineNode() { /* void */ } virtual ~SetEngineNode() { /* void */ } protected: virtual void registerInputs() { registerInput(&m_engineInput, "engine"); } virtual void _evaluate() { EngineNode *engineNode = getObject(m_engineInput); Engine *engine = new Engine; engineNode->buildEngine(engine); Compiler::output()->engine = engine; } protected: piranha::pNodeInput m_engineInput = nullptr; }; class AddRodJournalNode : public Node { public: AddRodJournalNode() { /* void */ } virtual ~AddRodJournalNode() { /* void */ } protected: virtual void registerInputs() { addInput("crankshaft", &m_crankshaft, InputTarget::Type::Object); addInput("rod_journal", &m_rodJournal, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_crankshaft->addRodJournal(m_rodJournal); } protected: CrankshaftNode *m_crankshaft = nullptr; RodJournalNode *m_rodJournal = nullptr; }; class AddSlaveJournalNode : public Node { public: AddSlaveJournalNode() { /* void */ } virtual ~AddSlaveJournalNode() { /* void */ } protected: virtual void registerInputs() { addInput("rod", &m_rod, InputTarget::Type::Object); addInput("rod_journal", &m_rodJournal, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_rod->addRodJournal(m_rodJournal); } protected: ConnectingRodNode *m_rod = nullptr; RodJournalNode *m_rodJournal = nullptr; }; class AddCrankshaftNode : public Node { public: AddCrankshaftNode() { /* void */ } virtual ~AddCrankshaftNode() { /* void */ } protected: virtual void registerInputs() { addInput("engine", &m_engine, InputTarget::Type::Object); addInput("crankshaft", &m_crankshaft, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_engine->addCrankshaft(m_crankshaft); } protected: CrankshaftNode *m_crankshaft = nullptr; EngineNode *m_engine = nullptr; }; class AddCylinderBankNode : public Node { public: AddCylinderBankNode() { /* void */ } virtual ~AddCylinderBankNode() { /* void */ } protected: virtual void registerInputs() { addInput("engine", &m_engine, InputTarget::Type::Object); addInput("cylinder_bank", &m_cylinderBankNode, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_engine->addCylinderBank(m_cylinderBankNode); } protected: CylinderBankNode *m_cylinderBankNode = nullptr; EngineNode *m_engine = nullptr; }; class AddCylinderNode : public Node { public: AddCylinderNode() { /* void */ } virtual ~AddCylinderNode() { /* void */ } protected: virtual void registerInputs() { addInput("piston", &m_pistonNode, InputTarget::Type::Object); addInput("connecting_rod", &m_connectingRod, InputTarget::Type::Object); addInput("rod_journal", &m_rodJournal, InputTarget::Type::Object); addInput("exhaust_system", &m_exhaustSystem, InputTarget::Type::Object); addInput("intake", &m_intake, InputTarget::Type::Object); addInput("cylinder_bank", &m_cylinderBank, InputTarget::Type::Object); addInput("ignition_wire", &m_ignitionWire, InputTarget::Type::Object); addInput("primary_length", &m_primaryLength); addInput("sound_attenuation", &m_soundAttenuation); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_cylinderBank->addCylinder( m_pistonNode, m_connectingRod, m_rodJournal, m_intake, m_exhaustSystem, m_ignitionWire, m_soundAttenuation, m_primaryLength ); } protected: PistonNode *m_pistonNode = nullptr; ConnectingRodNode *m_connectingRod = nullptr; RodJournalNode *m_rodJournal = nullptr; CylinderBankNode *m_cylinderBank = nullptr; ExhaustSystemNode *m_exhaustSystem = nullptr; IntakeNode *m_intake = nullptr; IgnitionWireNode *m_ignitionWire = nullptr; double m_primaryLength = 0.0; double m_soundAttenuation = 1.0; }; class AddSampleNode : public Node { public: AddSampleNode() { /* void */ } virtual ~AddSampleNode() { /* void */ } protected: virtual void registerInputs() { addInput("x", &m_x); addInput("y", &m_y); addInput("function", &m_function, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_function->addSample(m_x, m_y); } protected: double m_x = 0; double m_y = 0; FunctionNode *m_function = nullptr; }; class AddLobeNode : public Node { public: AddLobeNode() { /* void */ } virtual ~AddLobeNode() { /* void */ } protected: virtual void registerInputs() { addInput("centerline", &m_centerline); addInput("camshaft", &m_camshaft, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_camshaft->addLobe(m_centerline); } protected: double m_centerline = 0; CamshaftNode *m_camshaft = nullptr; }; class SetCylinderHeadNode : public Node { public: SetCylinderHeadNode() { /* void */ } virtual ~SetCylinderHeadNode() { /* void */ } protected: virtual void registerInputs() { addInput("head", &m_head, InputTarget::Type::Object); addInput("bank", &m_bank, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); if (m_bank->getCylinderHead() != nullptr) { throwError("Cylinder bank already has a head"); return; } else if (m_head->getBank() != nullptr) { throwError("Cylinder head already attached to a bank"); return; } m_bank->setCylinderHead(m_head); m_head->setBank(m_bank); } protected: CylinderHeadNode *m_head = nullptr; CylinderBankNode *m_bank = nullptr; }; class k_28inH2ONode : public Node { class k_28inH2ONodeOutput : public piranha::NodeOutput { public: k_28inH2ONodeOutput() : NodeOutput(&piranha::FundamentalType::FloatType) { m_input = 0.0; } virtual ~k_28inH2ONodeOutput() { /* void */ } virtual void fullCompute(void *target) const { *reinterpret_cast(target) = GasSystem::k_28inH2O(m_input); } double getInput() const { return m_input; } void setInput(double data) { m_input = data; } protected: double m_input; }; public: k_28inH2ONode() { /* void */ } virtual ~k_28inH2ONode() { /* void */ } protected: virtual void registerInputs() { addInput("flow", &m_flowInput); Node::registerInputs(); } virtual void registerOutputs() { registerOutput(&m_output, "__out"); setPrimaryOutput("__out"); } virtual void _evaluate() { readAllInputs(); m_output.setInput(m_flowInput); } protected: k_28inH2ONodeOutput m_output; double m_flowInput = 0.0; }; class k_CarbNode : public Node { class k_CarbNodeOutput : public piranha::NodeOutput { public: k_CarbNodeOutput() : NodeOutput(&piranha::FundamentalType::FloatType) { m_input = 0.0; } virtual ~k_CarbNodeOutput() { /* void */ } virtual void fullCompute(void *target) const { *reinterpret_cast(target) = GasSystem::k_carb(m_input); } double getInput() const { return m_input; } void setInput(double data) { m_input = data; } protected: double m_input; }; public: k_CarbNode() { /* void */ } virtual ~k_CarbNode() { /* void */ } protected: virtual void registerInputs() { addInput("flow", &m_flowInput); Node::registerInputs(); } virtual void registerOutputs() { registerOutput(&m_output, "__out"); setPrimaryOutput("__out"); } virtual void _evaluate() { readAllInputs(); m_output.setInput(m_flowInput); } protected: k_CarbNodeOutput m_output; double m_flowInput = 0.0; }; class ConnectIgnitionWireNode : public Node { public: ConnectIgnitionWireNode() { /* void */ } virtual ~ConnectIgnitionWireNode() { /* void */ } protected: virtual void registerInputs() { addInput("wire", &m_wire, InputTarget::Type::Object); addInput("ignition_module", &m_module, InputTarget::Type::Object); addInput("angle", &m_angle); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_module->connect(m_wire, m_angle); } protected: IgnitionWireNode *m_wire = nullptr; IgnitionModuleNode *m_module = nullptr; double m_angle = 0.0; }; class AddIgnitionModuleNode : public Node { public: AddIgnitionModuleNode() { /* void */ } virtual ~AddIgnitionModuleNode() { /* void */ } protected: virtual void registerInputs() { addInput("engine", &m_engine, InputTarget::Type::Object); addInput("ignition_module", &m_ignitionModule, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_engine->addIgnitionModule(m_ignitionModule); } protected: IgnitionModuleNode *m_ignitionModule = nullptr; EngineNode *m_engine = nullptr; }; class GenerateHarmonicCamLobeNode : public Node { public: GenerateHarmonicCamLobeNode() { /* void */ } virtual ~GenerateHarmonicCamLobeNode() { /* void */ } protected: virtual void registerInputs() { addInput("duration_at_50_thou", &m_durationAt50Thou); addInput("gamma", &m_gamma); addInput("lift", &m_lift); addInput("steps", &m_steps); addInput("function", &m_function, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); const double angle = m_durationAt50Thou / 4; const double s = std::pow(2 * units::distance(50, units::thou) / m_lift, 1 / m_gamma) - 1; const double k = std::acos(s) / angle; const double extents = constants::pi / k; // pi / 2 = k * x const double step = extents / (m_steps - 5.0); for (int i = 0; i < m_steps; ++i) { if (i == 0) { m_function->addSample(0.0, m_lift); } else { const double x = i * step; const double lift = (x >= extents) ? 0.0 : m_lift * std::pow(0.5 + 0.5 * std::cos(k * x), m_gamma); m_function->addSample(x, lift); m_function->addSample(-x, lift); } } m_function->setFilterRadius(step); } protected: double m_durationAt50Thou = 0.0; double m_gamma = 1.0; double m_lift = 300.0; int m_steps = 100; FunctionNode *m_function = nullptr; }; class AddGearNode : public Node { public: AddGearNode() { /* void */ } virtual ~AddGearNode() { /* void */ } protected: virtual void registerInputs() { addInput("ratio", &m_ratio, InputTarget::Type::Object); addInput("transmission", &m_transmission, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); m_transmission->addGear(m_ratio); } protected: double m_ratio = 1.0; TransmissionNode *m_transmission = nullptr; }; class SetTransmissionNode : public Node { public: SetTransmissionNode() { /* void */ } virtual ~SetTransmissionNode() { /* void */ } protected: virtual void registerInputs() override { addInput("transmission", &m_transmission, InputTarget::Type::Object); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); Transmission *transmission = new Transmission; m_transmission->generate(transmission); Compiler::output()->transmission = transmission; } protected: TransmissionNode *m_transmission = nullptr; }; class SetVehicleNode : public piranha::Node { public: SetVehicleNode() { /* void */ } virtual ~SetVehicleNode() { /* void */ } protected: virtual void registerInputs() { registerInput(&m_vehicle, "vehicle"); Node::registerInputs(); } virtual void _evaluate() { VehicleNode *vehicleNode = getObject(m_vehicle); Vehicle *vehicle = new Vehicle; vehicleNode->generate(vehicle); Compiler::output()->vehicle = vehicle; } protected: piranha::pNodeInput m_vehicle = nullptr; }; class SetApplicationSettingsNode : public Node { public: SetApplicationSettingsNode() { /* void */ } virtual ~SetApplicationSettingsNode() { /* void */ } protected: virtual void registerInputs() { addInput("start_fullscreen", &m_settings.startFullscreen); addInput("power_units", &m_settings.powerUnits); addInput("torque_units", &m_settings.torqueUnits); addInput("speed_units", &m_settings.speedUnits); addInput("pressure_units", &m_settings.pressureUnits); addInput("boost_units", &m_settings.boostUnits); addInput("color_background", &m_settings.colorBackground); addInput("color_foreground", &m_settings.colorForeground); addInput("color_shadow", &m_settings.colorShadow); addInput("color_highlight1", &m_settings.colorHighlight1); addInput("color_highlight2", &m_settings.colorHighlight2); addInput("color_pink", &m_settings.colorPink); addInput("color_red", &m_settings.colorRed); addInput("color_orange", &m_settings.colorOrange); addInput("color_yellow", &m_settings.colorYellow); addInput("color_blue", &m_settings.colorBlue); addInput("color_green", &m_settings.colorGreen); Node::registerInputs(); } virtual void _evaluate() { readAllInputs(); Compiler::output()->applicationSettings = m_settings; } protected: ApplicationSettings m_settings; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_ACTIONS_H */ ================================================ FILE: scripting/include/camshaft_node.h ================================================ #ifndef ATG_ENGINE_SIM_CAMSHAFT_NODE_H #define ATG_ENGINE_SIM_CAMSHAFT_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "function_node.h" #include "engine_sim.h" #include #include namespace es_script { class CamshaftNode : public ObjectReferenceNode { public: CamshaftNode() { /* void */ } virtual ~CamshaftNode() { /* void */ } void generate( Camshaft *camshaft, Crankshaft *crankshaft, EngineContext *context) const { Camshaft::Parameters parameters = m_parameters; parameters.crankshaft = crankshaft; parameters.lobes = (int)m_lobes.size(); parameters.lobeProfile = m_lobeProfile->generate(context); camshaft->initialize(parameters); for (int i = 0; i < parameters.lobes; ++i) { camshaft->setLobeCenterline(i, m_lobes[i]); } } void addLobe(double lobeCenterline) { m_lobes.push_back(lobeCenterline); } protected: virtual void registerInputs() { addInput("advance", &m_parameters.advance); addInput("base_radius", &m_parameters.baseRadius); addInput("lobe_profile", &m_lobeProfile); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); m_parameters.crankshaft = nullptr; } Camshaft::Parameters m_parameters; FunctionNode *m_lobeProfile = nullptr; std::vector m_lobes; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_CAMSHAFT_NODE_H */ ================================================ FILE: scripting/include/channel_types.h ================================================ #ifndef ATG_ENGINE_SIM_CHANNEL_TYPES_H #define ATG_ENGINE_SIM_CHANNEL_TYPES_H #include "piranha.h" namespace es_script { struct ObjectChannel { static const piranha::ChannelType EngineChannel; static const piranha::ChannelType CrankshaftChannel; static const piranha::ChannelType RodJournalChannel; static const piranha::ChannelType ConnectingRodChannel; static const piranha::ChannelType CylinderBankChannel; static const piranha::ChannelType PistonChannel; static const piranha::ChannelType FunctionChannel; static const piranha::ChannelType IntakeChannel; static const piranha::ChannelType ExhaustSystemChannel; static const piranha::ChannelType CylinderHeadChannel; static const piranha::ChannelType CamshaftChannel; static const piranha::ChannelType IgnitionModuleChannel; static const piranha::ChannelType IgnitionWireChannel; static const piranha::ChannelType FuelChannel; static const piranha::ChannelType ImpulseResponseChannel; static const piranha::ChannelType ValvetrainChannel; static const piranha::ChannelType VehicleChannel; static const piranha::ChannelType TransmissionChannel; static const piranha::ChannelType ThrottleChannel; }; template extern inline const piranha::ChannelType *LookupChannelType() { static_assert(false, "Invalid type lookup"); return nullptr; } #define ASSIGN_CHANNEL_TYPE(type, channel) \ class type; \ template <> extern inline const piranha::ChannelType *LookupChannelType() { \ return &ObjectChannel::channel; \ } // Register all types ASSIGN_CHANNEL_TYPE(EngineNode, EngineChannel); ASSIGN_CHANNEL_TYPE(CrankshaftNode, CrankshaftChannel); ASSIGN_CHANNEL_TYPE(RodJournalNode, RodJournalChannel); ASSIGN_CHANNEL_TYPE(ConnectingRodNode, ConnectingRodChannel); ASSIGN_CHANNEL_TYPE(CylinderBankNode, CylinderBankChannel); ASSIGN_CHANNEL_TYPE(PistonNode, PistonChannel); ASSIGN_CHANNEL_TYPE(FunctionNode, FunctionChannel); ASSIGN_CHANNEL_TYPE(IntakeNode, IntakeChannel); ASSIGN_CHANNEL_TYPE(ExhaustSystemNode, ExhaustSystemChannel); ASSIGN_CHANNEL_TYPE(CylinderHeadNode, CylinderHeadChannel); ASSIGN_CHANNEL_TYPE(CamshaftNode, CamshaftChannel); ASSIGN_CHANNEL_TYPE(IgnitionModuleNode, IgnitionModuleChannel); ASSIGN_CHANNEL_TYPE(IgnitionWireNode, IgnitionWireChannel); ASSIGN_CHANNEL_TYPE(FuelNode, FuelChannel); ASSIGN_CHANNEL_TYPE(ImpulseResponseNode, ImpulseResponseChannel); ASSIGN_CHANNEL_TYPE(ValvetrainNode, ValvetrainChannel); ASSIGN_CHANNEL_TYPE(VehicleNode, VehicleChannel); ASSIGN_CHANNEL_TYPE(TransmissionNode, VehicleChannel); ASSIGN_CHANNEL_TYPE(ThrottleNode, ThrottleChannel); } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_CHANNEL_TYPES_H */ ================================================ FILE: scripting/include/compiler.h ================================================ #ifndef ATG_ENGINE_SIM_COMPILER_H #define ATG_ENGINE_SIM_COMPILER_H #include "language_rules.h" #include "engine_sim.h" #include "piranha.h" #include namespace es_script { class Compiler { public: struct Output { Engine *engine = nullptr; Vehicle *vehicle = nullptr; Transmission *transmission = nullptr; Simulator::Parameters simulatorParameters; ApplicationSettings applicationSettings; std::vector functions; }; private: static Output *s_output; public: Compiler(); ~Compiler(); static Output *output(); void initialize(); bool compile(const piranha::IrPath &path); Output execute(); void destroy(); private: void printError(const piranha::CompilationError *err, std::ofstream &file) const; private: LanguageRules m_rules; piranha::Compiler *m_compiler; piranha::NodeProgram m_program; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_COMPILER_H */ ================================================ FILE: scripting/include/connecting_rod_node.h ================================================ #ifndef ATG_ENGINE_SIM_CONNECTING_ROD_NODE_H #define ATG_ENGINE_SIM_CONNECTING_ROD_NODE_H #include "object_reference_node.h" #include "rod_journal_node.h" #include "engine_sim.h" #include #include namespace es_script { class ConnectingRodNode : public ObjectReferenceNode { public: ConnectingRodNode() { /* void */ } virtual ~ConnectingRodNode() { /* void */ } void addRodJournal(RodJournalNode *rodJournal) { m_rodJournals.push_back(rodJournal); rodJournal->m_rod = this; } bool isMaster() const { return !m_rodJournals.empty(); } void indexSlaveJournals(EngineContext *context) const { int index = 0; for (RodJournalNode *journal : m_rodJournals) { context->addRodJournal(journal, index++); } } void generate( ConnectingRod *connectingRod, Crankshaft *crankshaft, Piston *piston, int rodJournal) const { ConnectingRod::Parameters params = m_parameters; params.crankshaft = crankshaft; params.journal = rodJournal; params.piston = piston; params.rodJournals = static_cast(m_rodJournals.size()); params.master = nullptr; connectingRod->initialize(params); for (int i = 0; i < params.rodJournals; ++i) { connectingRod->setRodJournalAngle(i, m_rodJournals[i]->getAngle() + constants::pi / 2); } } protected: virtual void registerInputs() { addInput("mass", &m_parameters.mass); addInput("moment_of_inertia", &m_parameters.momentOfInertia); addInput("center_of_mass", &m_parameters.centerOfMass); addInput("length", &m_parameters.length); addInput("slave_throw", &m_parameters.slaveThrow); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); m_parameters.crankshaft = nullptr; m_parameters.piston = nullptr; } ConnectingRod::Parameters m_parameters; std::vector m_rodJournals; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_CONNECTING_ROD_NODE_H */ ================================================ FILE: scripting/include/crankshaft_node.h ================================================ #ifndef ATG_ENGINE_SIM_CRANKSHAFT_NODE_H #define ATG_ENGINE_SIM_CRANKSHAFT_NODE_H #include "object_reference_node.h" #include "rod_journal_node.h" #include "engine_context.h" #include "engine_sim.h" #include #include namespace es_script { class CrankshaftNode : public ObjectReferenceNode { public: CrankshaftNode() { /* void */ } virtual ~CrankshaftNode() { /* void */ } void addRodJournal(RodJournalNode *rodJournal) { m_rodJournals.push_back(rodJournal); rodJournal->m_crankshaft = this; } void generate(Crankshaft *crankshaft, EngineContext *context) { Crankshaft::Parameters params = m_parameters; params.rodJournals = (int)m_rodJournals.size(); crankshaft->initialize(params); for (int i = 0; i < params.rodJournals; ++i) { RodJournalNode *rodJournal = m_rodJournals[i]; crankshaft->setRodJournalAngle( i, rodJournal->getAngle() ); context->addRodJournal(rodJournal, i); } context->addCrankshaft(this, crankshaft); } protected: virtual void registerInputs() { addInput("throw", &m_parameters.crankThrow); addInput("flywheel_mass", &m_parameters.flywheelMass); addInput("mass", &m_parameters.mass); addInput("friction_torque", &m_parameters.frictionTorque); addInput("moment_of_inertia", &m_parameters.momentOfInertia); addInput("position_x", &m_parameters.pos_x); addInput("position_y", &m_parameters.pos_y); addInput("tdc", &m_parameters.tdc); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); m_parameters.rodJournals = 0; } Crankshaft::Parameters m_parameters; std::vector m_rodJournals; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_CRANKSHAFT_NODE_H */ ================================================ FILE: scripting/include/cylinder_bank_node.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_BANK_NODE_H #define ATG_ENGINE_SIM_CYLINDER_BANK_NODE_H #include "object_reference_node.h" #include "rod_journal_node.h" #include "piston_node.h" #include "connecting_rod_node.h" #include "cylinder_head_node.h" #include "ignition_wire_node.h" #include "engine_sim.h" #include #include namespace es_script { class CylinderBankNode : public ObjectReferenceNode { public: struct Cylinder { PistonNode *piston; ConnectingRodNode *rod; RodJournalNode *rodJournal; IntakeNode *intake; ExhaustSystemNode *exhaust; double soundAttenuation; double primaryLength; }; public: CylinderBankNode() { /* void */ } virtual ~CylinderBankNode() { /* void */ } void connectRodAssemblies(EngineContext *context) const { const int n = getCylinderCount(); for (int i = 0; i < n; ++i) { ConnectingRodNode *rodNode = m_cylinders[i].rod; ConnectingRod *rod = context->getConnectingRod(rodNode); RodJournalNode *journal = m_cylinders[i].rodJournal; if (journal->getRod() != nullptr) { rod->setMaster(context->getConnectingRod(journal->getRod())); rod->setCrankshaft(rod->getMasterRod()->getCrankshaft()); } } } void indexSlaveJournals(EngineContext *context) const { const int n = getCylinderCount(); for (int i = 0; i < n; ++i) { m_cylinders[i].rod->indexSlaveJournals(context); } } void generate( int index, int cylinderBaseIndex, CylinderBank *cylinderBank, Crankshaft *crankshaft, Engine *engine, EngineContext *context) const { CylinderBank::Parameters params = m_parameters; params.cylinderCount = static_cast(m_cylinders.size()); params.index = index; params.crankshaft = crankshaft; cylinderBank->initialize(params); const int n = getCylinderCount(); for (int i = 0; i < n; ++i) { Piston *piston = engine->getPiston(cylinderBaseIndex + i); ConnectingRod *rod = engine->getConnectingRod(cylinderBaseIndex + i); Crankshaft *crankshaft = context->getCrankshaft(m_cylinders[i].rodJournal->getCrankshaft()); context->addConnectingRod(m_cylinders[i].rod, rod); context->setCylinderIndex( this, i, cylinderBaseIndex + i); m_cylinders[i].piston->generate( piston, rod, cylinderBank, i); m_cylinders[i].rod->generate( rod, crankshaft, piston, context->getRodJournalIndex(m_cylinders[i].rodJournal)); } CylinderHead *head = context->getHead(m_head); m_head->generate(head, cylinderBank, crankshaft, context); const int n_cylinders = getCylinderCount(); for (int i = 0; i < n_cylinders; ++i) { ExhaustSystemNode *exhaustNode = getCylinder(i).exhaust; IntakeNode *intakeNode = getCylinder(i).intake; ExhaustSystem *exhaust = exhaustNode->generate(context); Intake *intake = intakeNode->generate(context); head->setIntake(i, intake); head->setExhaustSystem(i, exhaust); head->setSoundAttenuation(i, getCylinder(i).soundAttenuation); head->setHeaderPrimaryLength(i, getCylinder(i).primaryLength); } } void addCylinder( PistonNode *piston, ConnectingRodNode *rod, RodJournalNode *rodJournal, IntakeNode *intake, ExhaustSystemNode *exhaust, IgnitionWireNode *wire, double soundAttenuation, double primaryLength) { m_cylinders.push_back({ piston, rod, rodJournal, intake, exhaust, soundAttenuation, primaryLength }); wire->connect(this, getCylinderCount() - 1); } const Cylinder &getCylinder(int i) const { return m_cylinders[i]; } int getCylinderCount() const { return (int)m_cylinders.size(); } void setCylinderHead(CylinderHeadNode *head) { m_head = head; } CylinderHeadNode *getCylinderHead() const { return m_head; } protected: virtual void registerInputs() { addInput("angle", &m_parameters.angle); addInput("bore", &m_parameters.bore); addInput("deck_height", &m_parameters.deckHeight); addInput("position_x", &m_parameters.positionX); addInput("position_y", &m_parameters.positionY); addInput("display_depth", &m_parameters.displayDepth); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); m_parameters.cylinderCount = 0; m_parameters.index = 0; } CylinderBank::Parameters m_parameters; std::vector m_cylinders; CylinderHeadNode *m_head = nullptr; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_CYLINDER_BANK_NODE_H */ ================================================ FILE: scripting/include/cylinder_head_node.h ================================================ #ifndef ATG_ENGINE_SIM_CYLINDER_HEAD_NODE_H #define ATG_ENGINE_SIM_CYLINDER_HEAD_NODE_H #include "object_reference_node.h" #include "valvetrain_node.h" #include "function_node.h" #include "exhaust_system_node.h" #include "intake_node.h" #include "engine_sim.h" #include #include namespace es_script { class CylinderHeadNode : public ObjectReferenceNode { public: CylinderHeadNode() { /* void */ } virtual ~CylinderHeadNode() { /* void */ } void generate( CylinderHead *head, CylinderBank *bank, Crankshaft *crankshaft, EngineContext *context) const { CylinderHead::Parameters params = m_parameters; params.Bank = bank; params.Valvetrain = m_valvetrain->generate(context, crankshaft); params.IntakePortFlow = m_intakePortFlow->generate(context); params.ExhaustPortFlow = m_exhaustPortFlow->generate(context); head->initialize(params); } void setBank(CylinderBankNode *bank) { m_bank = bank; } CylinderBankNode *getBank() const { return m_bank; } protected: virtual void registerInputs() { addInput( "intake_port_flow", &m_intakePortFlow, InputTarget::Type::Object); addInput( "exhaust_port_flow", &m_exhaustPortFlow, InputTarget::Type::Object); addInput( "valvetrain", &m_valvetrain, InputTarget::Type::Object); addInput("chamber_volume", &m_parameters.CombustionChamberVolume); addInput("flip_display", &m_parameters.FlipDisplay); addInput("intake_runner_volume", &m_parameters.IntakeRunnerVolume); addInput("intake_runner_cross_section_area", &m_parameters.IntakeRunnerCrossSectionArea); addInput("exhaust_runner_volume", &m_parameters.ExhaustRunnerVolume); addInput("exhaust_runner_cross_section_area", &m_parameters.ExhaustRunnerCrossSectionArea); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); m_parameters.Bank = nullptr; m_parameters.Valvetrain = nullptr; m_parameters.IntakePortFlow = nullptr; m_parameters.ExhaustPortFlow = nullptr; } CylinderHead::Parameters m_parameters; CylinderBankNode *m_bank = nullptr; FunctionNode *m_intakePortFlow = nullptr; FunctionNode *m_exhaustPortFlow = nullptr; ValvetrainNode *m_valvetrain = nullptr; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_CYLINDER_HEAD_NODE_H */ ================================================ FILE: scripting/include/engine_context.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_CONTEXT_H #define ATG_ENGINE_SIM_ENGINE_CONTEXT_H #include "engine_sim.h" #include namespace es_script { class CylinderHeadNode; class CylinderBankNode; class CrankshaftNode; class FunctionNode; class ExhaustSystemNode; class IntakeNode; class RodJournalNode; class CamshaftNode; class ImpulseResponseNode; class ValvetrainNode; class ConnectingRodNode; class EngineContext { public: EngineContext(); ~EngineContext(); void addHead(CylinderHeadNode *node, CylinderHead *head); CylinderHead *getHead(CylinderHeadNode *head); void addBank(CylinderBankNode *node, CylinderBank *bank); CylinderBank *getBank(CylinderBankNode *bank) const; void addRodJournal(RodJournalNode *node, int index); int getRodJournalIndex(RodJournalNode *node) const; void addIntake(IntakeNode *node, Intake *intake); Intake *getIntake(IntakeNode *intake) const; void addExhaust(ExhaustSystemNode *node, ExhaustSystem *exhaust); ExhaustSystem *getExhaust(ExhaustSystemNode *exhaust) const; void addFunction(FunctionNode *node, Function *function); Function *getFunction(FunctionNode *node) const; void addImpulseResponse(ImpulseResponseNode *node, ImpulseResponse *impulse); ImpulseResponse *getImpulseResponse(ImpulseResponseNode *node) const; void addCrankshaft(CrankshaftNode *node, Crankshaft *crankshaft); Crankshaft *getCrankshaft(CrankshaftNode *node) const; void addConnectingRod(ConnectingRodNode *node, ConnectingRod *rod); ConnectingRod *getConnectingRod(ConnectingRodNode *node) const; void setEngine(Engine *engine) { m_engine = engine; } Engine *getEngine() const { return m_engine; } void setCylinderIndex(const CylinderBankNode *bank, int localIndex, int globalIndex); int getCylinderIndex(const CylinderBankNode *bank, int localIndex) const; protected: Engine *m_engine = nullptr; std::map m_heads; std::map m_banks; std::map m_exhaustSystems; std::map m_intakes; std::map m_functions; std::map m_impulseResponses; std::map m_rodJournals; std::map m_crankshafts; std::map m_rods; std::map, int> m_cylinderIndices; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_ENGINE_CONTEXT_H */ ================================================ FILE: scripting/include/engine_node.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_NODE_H #define ATG_ENGINE_SIM_ENGINE_NODE_H #include "object_reference_node.h" #include "crankshaft_node.h" #include "cylinder_bank_node.h" #include "ignition_module_node.h" #include "engine_context.h" #include "fuel_node.h" #include "throttle_nodes.h" #include "engine_sim.h" #include #include namespace es_script { class EngineNode : public ObjectReferenceNode { public: EngineNode() { /* void */ } virtual ~EngineNode() { /* void */ } void buildEngine(Engine *engine) { int cylinderCount = 0; for (const CylinderBankNode *bank : m_cylinderBanks) { cylinderCount += bank->getCylinderCount(); } std::set exhaustSystems; std::set intakes; for (const CylinderBankNode *bank : m_cylinderBanks) { const int n = bank->getCylinderCount(); for (int i = 0; i < n; ++i) { exhaustSystems.insert(bank->getCylinder(i).exhaust); intakes.insert(bank->getCylinder(i).intake); } } EngineContext context; context.setEngine(engine); Engine::Parameters parameters = m_parameters; parameters.crankshaftCount = (int)m_crankshafts.size(); parameters.cylinderBanks = (int)m_cylinderBanks.size(); parameters.cylinderCount = cylinderCount; parameters.exhaustSystemCount = (int)exhaustSystems.size(); parameters.intakeCount = (int)intakes.size(); parameters.throttle = m_throttle->generate(); engine->initialize(parameters); { int i = 0; for (ExhaustSystemNode *exhaust : exhaustSystems) { context.addExhaust( exhaust, engine->getExhaustSystem(i++)); } } { int i = 0; for (IntakeNode *intake : intakes) { context.addIntake( intake, engine->getIntake(i++)); } } { int i = 0; for (const CylinderBankNode *bank : m_cylinderBanks) { context.addHead(bank->getCylinderHead(), engine->getHead(i++)); } } for (const CylinderBankNode *bank : m_cylinderBanks) { const int n = bank->getCylinderCount(); for (int i = 0; i < n; ++i) { exhaustSystems.insert(bank->getCylinder(i).exhaust); intakes.insert(bank->getCylinder(i).intake); } } for (int i = 0; i < parameters.crankshaftCount; ++i) { m_crankshafts[i]->generate(engine->getCrankshaft(i), &context); } for (int i = 0; i < parameters.cylinderBanks; ++i) { m_cylinderBanks[i]->indexSlaveJournals(&context); } int cylinderIndex = 0; for (int i = 0; i < parameters.cylinderBanks; ++i) { m_cylinderBanks[i]->generate( i, cylinderIndex, engine->getCylinderBank(i), engine->getCrankshaft(0), engine, &context); cylinderIndex += m_cylinderBanks[i]->getCylinderCount(); } for (int i = 0; i < parameters.cylinderBanks; ++i) { m_cylinderBanks[i]->connectRodAssemblies(&context); } m_ignitionModule->generate(engine, &context); Function *meanPistonSpeedToTurbulence = new Function; meanPistonSpeedToTurbulence->initialize(30, 1); for (int i = 0; i < 30; ++i) { const double s = (double)i; meanPistonSpeedToTurbulence->addSample(s, s * 0.5); } Fuel *fuel = engine->getFuel(); m_fuel->generate(fuel, &context); CombustionChamber::Parameters ccParams; ccParams.CrankcasePressure = units::pressure(1.0, units::atm); ccParams.Fuel = fuel; ccParams.StartingPressure = units::pressure(1.0, units::atm); ccParams.StartingTemperature = units::celcius(25.0); ccParams.MeanPistonSpeedToTurbulence = meanPistonSpeedToTurbulence; for (int i = 0; i < engine->getCylinderCount(); ++i) { ccParams.Piston = engine->getPiston(i); ccParams.Head = engine->getHead(ccParams.Piston->getCylinderBank()->getIndex()); engine->getChamber(i)->initialize(ccParams); } } void addCrankshaft(CrankshaftNode *crankshaft) { m_crankshafts.push_back(crankshaft); } void addCylinderBank(CylinderBankNode *bank) { m_cylinderBanks.push_back(bank); } int getIgnitionModuleCount() const { return m_ignitionModule == nullptr ? 0 : 1; } void addIgnitionModule(IgnitionModuleNode *ignitionModule) { m_ignitionModule = ignitionModule; } protected: virtual void registerInputs() { addInput("name", &m_parameters.name); addInput("starter_torque", &m_parameters.starterTorque); addInput("starter_speed", &m_parameters.starterSpeed); addInput("dyno_min_speed", &m_parameters.dynoMinSpeed); addInput("dyno_max_speed", &m_parameters.dynoMaxSpeed); addInput("dyno_hold_step", &m_parameters.dynoHoldStep); addInput("redline", &m_parameters.redline); addInput("fuel", &m_fuel, InputTarget::Type::Object); addInput("throttle", &m_throttle, InputTarget::Type::Object); addInput("simulation_frequency", &m_parameters.initialSimulationFrequency); addInput("hf_gain", &m_parameters.initialHighFrequencyGain); addInput("jitter", &m_parameters.initialJitter); addInput("noise", &m_parameters.initialNoise); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } ThrottleNode *m_throttle = nullptr; IgnitionModuleNode *m_ignitionModule = nullptr; FuelNode *m_fuel = nullptr; Engine::Parameters m_parameters; std::vector m_crankshafts; std::vector m_cylinderBanks; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_ENGINE_NODE_H */ ================================================ FILE: scripting/include/engine_sim.h ================================================ #ifndef ATG_ENGINE_SIM_ENGINE_SIM_H #define ATG_ENGINE_SIM_ENGINE_SIM_H #include "../../include/engine.h" #include "../../include/crankshaft.h" #include "../../include/connecting_rod.h" #include "../../include/cylinder_bank.h" #include "../../include/piston.h" #include "../../include/function.h" #include "../../include/vehicle.h" #include "../../include/transmission.h" #include "../../include/simulator.h" #include "../../include/fuel.h" #include "../../include/impulse_response.h" #include "../../include/standard_valvetrain.h" #include "../../include/vtec_valvetrain.h" #include "../../include/application_settings.h" #include "../../include/throttle.h" #include "../../include/direct_throttle_linkage.h" #include "../../include/governor.h" #endif /* ATG_ENGINE_SIM_ENGINE_SIM_H */ ================================================ FILE: scripting/include/exhaust_system_node.h ================================================ #ifndef ATG_ENGINE_SIM_EXHAUST_SYSTEM_NODE_H #define ATG_ENGINE_SIM_EXHAUST_SYSTEM_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "impulse_response_node.h" #include "engine_sim.h" namespace es_script { class ExhaustSystemNode : public ObjectReferenceNode { public: ExhaustSystemNode() { /* void */ } virtual ~ExhaustSystemNode() { /* void */ } ExhaustSystem *generate(EngineContext *context) { ExhaustSystem *exhaust = context->getExhaust(this); ExhaustSystem::Parameters parameters = m_parameters; parameters.impulseResponse = m_impulseResponse->generate(context); exhaust->initialize(parameters); return exhaust; } protected: virtual void registerInputs() { addInput("length", &m_parameters.length); addInput("collector_cross_section_area", &m_parameters.collectorCrossSectionArea); addInput("outlet_flow_rate", &m_parameters.outletFlowRate); addInput("primary_tube_length", &m_parameters.primaryTubeLength); addInput("primary_flow_rate", &m_parameters.primaryFlowRate); addInput("audio_volume", &m_parameters.audioVolume); addInput("velocity_decay", &m_parameters.velocityDecay); addInput("impulse_response", &m_impulseResponse, InputTarget::Type::Object); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } ImpulseResponseNode *m_impulseResponse = nullptr; ExhaustSystem::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_EXHAUST_SYSTEM_NODE_H */ ================================================ FILE: scripting/include/fuel_node.h ================================================ #ifndef ATG_ENGINE_SIM_FUEL_NODE_H #define ATG_ENGINE_SIM_FUEL_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "function_node.h" #include "engine_sim.h" #include #include namespace es_script { class FuelNode : public ObjectReferenceNode { public: FuelNode() { /* void */ } virtual ~FuelNode() { /* void */ } void generate(Fuel *fuel, EngineContext *context) const { Fuel::Parameters params = m_parameters; params.turbulenceToFlameSpeedRatio = m_turbulenceToFlameSpeedRatio->generate(context); fuel->initialize(params); } protected: virtual void registerInputs() { addInput( "turbulence_to_flame_speed_ratio", &m_turbulenceToFlameSpeedRatio, InputTarget::Type::Object); addInput("name", &m_parameters.name); addInput("molecular_mass", &m_parameters.molecularMass); addInput("energy_density", &m_parameters.energyDensity); addInput("density", &m_parameters.density); addInput("molecular_afr", &m_parameters.molecularAfr); addInput("max_burning_efficiency", &m_parameters.maxBurningEfficiency); addInput("burning_efficiency_randomness", &m_parameters.burningEfficiencyRandomness); addInput("low_efficiency_attenuation", &m_parameters.lowEfficiencyAttenuation); addInput("max_turbulence_effect", &m_parameters.maxTurbulenceEffect); addInput("max_dilution_effect", &m_parameters.maxDilutionEffect); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } FunctionNode *m_turbulenceToFlameSpeedRatio = nullptr; Fuel::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_FUEL_NODE_H */ ================================================ FILE: scripting/include/function_node.h ================================================ #ifndef ATG_ENGINE_SIM_FUNCTION_NODE_H #define ATG_ENGINE_SIM_FUNCTION_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "engine_sim.h" namespace es_script { class FunctionNode : public ObjectReferenceNode { public: struct Sample { double x, y; }; public: FunctionNode() { /* void */ } virtual ~FunctionNode() { /* void */ } void addSample(double x, double y) { m_samples.push_back({ x, y }); } void setFilterRadius(double filterRadius) { m_filterRadius = filterRadius; } virtual Function *generate(EngineContext *context) { Function *existingFunction = context->getFunction(this); if (existingFunction != nullptr) { return existingFunction; } else { Function *function = new Function; function->initialize((int)m_samples.size(), m_filterRadius); for (const Sample &sample : m_samples) { function->addSample(sample.x, sample.y); } context->addFunction(this, function); return function; } } protected: virtual void registerInputs() { addInput("filter_radius", &m_filterRadius); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } std::vector m_samples; double m_filterRadius = 0.0; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_FUNCTION_NODE_H */ ================================================ FILE: scripting/include/ignition_module_node.h ================================================ #ifndef ATG_ENGINE_SIM_IGNITION_MODULE_NODE_H #define ATG_ENGINE_SIM_IGNITION_MODULE_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "function_node.h" #include "engine_sim.h" #include namespace es_script { class IgnitionModuleNode : public ObjectReferenceNode { public: struct Post { IgnitionWireNode *wire; double angle; }; public: IgnitionModuleNode() { /* void */ } virtual ~IgnitionModuleNode() { /* void */ } void generate(Engine *engine, EngineContext *context) const { IgnitionModule::Parameters params; params.crankshaft = engine->getCrankshaft(0); params.cylinderCount = engine->getCylinderCount(); params.revLimit = m_revLimit; params.timingCurve = m_timingCurve->generate(context); params.cylinderCount = engine->getCylinderCount(); params.limiterDuration = m_limiterDuration; engine->getIgnitionModule()->initialize(params); for (const Post &post : m_posts) { std::set connections = post.wire->getConnections(); for (const IgnitionWireNode::Connection &connection : connections) { const int index = context->getCylinderIndex( connection.first, connection.second ); engine->getIgnitionModule()->setFiringOrder(index, post.angle); } } } void connect(IgnitionWireNode *wire, double angle) { m_posts.push_back({ wire, angle }); } protected: virtual void registerInputs() { addInput("timing_curve", &m_timingCurve); addInput("rev_limit", &m_revLimit); addInput("limiter_duration", &m_limiterDuration); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } double m_revLimit = 0.0; FunctionNode *m_timingCurve = nullptr; double m_limiterDuration = 0.0; std::vector m_posts; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_IGNITION_MODULE_NODE_H */ ================================================ FILE: scripting/include/ignition_wire_node.h ================================================ #ifndef ATG_ENGINE_SIM_IGNITION_WIRE_NODE_H #define ATG_ENGINE_SIM_IGNITION_WIRE_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "function_node.h" #include "engine_sim.h" #include namespace es_script { class IgnitionWireNode : public ObjectReferenceNode { public: using Connection = std::pair; public: IgnitionWireNode() { /* void */ } virtual ~IgnitionWireNode() { /* void */ } void connect(CylinderBankNode *bank, int i) { m_connections.insert({ bank, i }); } std::set getConnections() const { return m_connections; } protected: virtual void registerInputs() { ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); } std::set m_connections; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_IGNITION_WIRE_NODE_H */ ================================================ FILE: scripting/include/impulse_response_node.h ================================================ #ifndef ATG_ENGINE_SIM_IMPULSE_RESPONSE_NODE_H #define ATG_ENGINE_SIM_IMPULSE_RESPONSE_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "impulse_response_node.h" #include "engine_sim.h" #include #include namespace es_script { class ImpulseResponseNode : public ObjectReferenceNode { public: ImpulseResponseNode() { /* void */ } virtual ~ImpulseResponseNode() { /* void */ } ImpulseResponse *generate(EngineContext *context) { ImpulseResponse *existingIr = context->getImpulseResponse(this); if (existingIr != nullptr) { return existingIr; } else { piranha::Path path = m_filename; piranha::Path parentPath; m_irStructure->getParentUnit()->getPath().getParentPath(&parentPath); if (!path.isAbsolute()) { path = parentPath.append(path); } ImpulseResponse *impulseResponse = new ImpulseResponse; impulseResponse->initialize( path.toString(), m_volume); return impulseResponse; } } protected: virtual void registerInputs() { addInput("filename", &m_filename); addInput("volume", &m_volume); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } std::string m_filename = ""; double m_volume = 1.0; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_IMPULSE_RESPONSE_NODE_H */ ================================================ FILE: scripting/include/intake_node.h ================================================ #ifndef ATG_ENGINE_SIM_INTAKE_NODE_H #define ATG_ENGINE_SIM_INTAKE_NODE_H #include "object_reference_node.h" #include "engine_context.h" #include "function_node.h" #include "engine_sim.h" #include #include namespace es_script { class IntakeNode : public ObjectReferenceNode { public: IntakeNode() { /* void */ } virtual ~IntakeNode() { /* void */ } Intake *generate(EngineContext *context) { Intake *intake = context->getIntake(this); Intake::Parameters parameters = m_parameters; intake->initialize(parameters); return intake; } protected: virtual void registerInputs() { addInput("plenum_volume", &m_parameters.volume); addInput("plenum_cross_section_area", &m_parameters.CrossSectionArea); addInput("intake_flow_rate", &m_parameters.InputFlowK); addInput("idle_flow_rate", &m_parameters.IdleFlowK); addInput("runner_flow_rate", &m_parameters.RunnerFlowRate); addInput("molecular_afr", &m_parameters.MolecularAfr); addInput("idle_throttle_plate_position", &m_parameters.IdleThrottlePlatePosition); addInput("throttle_gamma", &m_throttleGammaUnused); addInput("runner_length", &m_parameters.RunnerLength); addInput("velocity_decay", &m_parameters.VelocityDecay); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } double m_throttleGammaUnused = 0.0; // Deprecated; to be removed in a future release Intake::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_INTAKE_NODE_H */ ================================================ FILE: scripting/include/language_rules.h ================================================ #ifndef ATG_ENGINE_SIM_LANGUAGE_RULES_H #define ATG_ENGINE_SIM_LANGUAGE_RULES_H #include "piranha.h" namespace es_script { class LanguageRules : public piranha::LanguageRules { public: LanguageRules(); ~LanguageRules(); protected: virtual void registerBuiltinNodeTypes(); }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_LANGUAGE_RULES_H */ ================================================ FILE: scripting/include/node.h ================================================ #ifndef ATG_ENGINE_SIM_NODE_H #define ATG_ENGINE_SIM_NODE_H #include "piranha.h" #include #include namespace es_script { class Node : public piranha::Node { protected: struct InputTarget { enum class Type { Object, Atomic }; piranha::pNodeInput *input = nullptr; void *memoryTarget = nullptr; Type type = Type::Atomic; }; public: Node() { /* void */ } virtual ~Node() { for (auto i : m_inputMap) { delete i.second.input; } } template T_Out readAtomicInput(const std::string &name) { T_Out out; (*m_inputMap[name].input)->fullCompute(&out); return out; } void readAllInputs() { for (auto i : m_inputMap) { if (i.second.type == InputTarget::Type::Atomic || i.second.type == InputTarget::Type::Object) { (*m_inputMap[i.first].input)->fullCompute(i.second.memoryTarget); } } } void addInput( const std::string &name, void *target, InputTarget::Type type = InputTarget::Type::Atomic) { m_inputMap[name] = { new piranha::pNodeInput, target, type }; } virtual void registerInputs() { for (auto i : m_inputMap) { registerInput(i.second.input, i.first); } } private: std::map m_inputMap; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_NODE_H */ ================================================ FILE: scripting/include/object_reference_node.h ================================================ #ifndef ATG_ENGINE_SIM_OBJECT_REFERENCE_H #define ATG_ENGINE_SIM_OBJECT_REFERENCE_H #include "node.h" #include "object_reference_node_output.h" namespace es_script { template class ObjectReferenceNode : public Node { public: ObjectReferenceNode() { /* void */ } ~ObjectReferenceNode() { /* void */ } template void overrideType() { m_output.overrideType(LookupChannelType()); } protected: virtual void registerOutputs() { setPrimaryOutput("__out"); registerOutput(&m_output, "__out"); } virtual void _evaluate() { m_output.setReference(nullptr); } void setOutput(Type *output) { m_output.setReference(output); } ObjectReferenceNodeOutput m_output; }; template using NullReferenceNode = ObjectReferenceNode; } /* namespace manta */ #endif /* ATG_ENGINE_SIM_OBJECT_REFERENCE_H */ ================================================ FILE: scripting/include/object_reference_node_output.h ================================================ #ifndef ATG_ENGINE_SIM_OBJECT_REFERENCE_NODE_OUTPUT_H #define ATG_ENGINE_SIM_OBJECT_REFERENCE_NODE_OUTPUT_H #include "piranha.h" #include "channel_types.h" namespace es_script { template class ObjectReferenceNodeOutput : public piranha::NodeOutput { public: ObjectReferenceNodeOutput() : NodeOutput(LookupChannelType()) { m_data = nullptr; } virtual ~ObjectReferenceNodeOutput() { /* void */ } virtual void fullCompute(void *target) const { *reinterpret_cast(target) = m_data; } Type *getReference() const { return m_data; } void setReference(Type *data) { m_data = data; } protected: Type *m_data; }; template Type *getObject(piranha::pNodeInput input) { return static_cast *>(input)->getReference(); } } /* namespace manta */ #endif /* ATG_ENGINE_SIM_OBJECT_REFERENCE_NODE_OUTPUT_H */ ================================================ FILE: scripting/include/piranha.h ================================================ #ifndef ATG_ENGINE_SIM_PIRANHA_H #define ATG_ENGINE_SIM_PIRANHA_H #include #endif /* ATG_ENGINE_SIM_PIRANHA_H */ ================================================ FILE: scripting/include/piston_node.h ================================================ #ifndef ATG_ENGINE_SIM_PISTON_NODE_H #define ATG_ENGINE_SIM_PISTON_NODE_H #include "object_reference_node.h" #include "rod_journal_node.h" #include "piston_node.h" #include "engine_sim.h" #include #include namespace es_script { class PistonNode : public ObjectReferenceNode { public: PistonNode() { /* void */ } virtual ~PistonNode() { /* void */ } void generate( Piston *piston, ConnectingRod *rod, CylinderBank *cylinderBank, int cylinderIndex) const { Piston::Parameters params = m_parameters; params.Bank = cylinderBank; params.CylinderIndex = cylinderIndex; params.Rod = rod; piston->initialize(params); } protected: virtual void registerInputs() { addInput("mass", &m_parameters.mass); addInput("blowby", &m_parameters.BlowbyFlowCoefficient); addInput("compression_height", &m_parameters.CompressionHeight); addInput("wrist_pin_position", &m_parameters.WristPinPosition); addInput("displacement", &m_parameters.Displacement); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); m_parameters.Bank = nullptr; m_parameters.Rod = nullptr; m_parameters.CylinderIndex = 0; } Piston::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_PISTON_NODE_H */ ================================================ FILE: scripting/include/rod_journal_node.h ================================================ #ifndef ATG_ENGINE_SIM_ROD_JOURNAL_NODE_H #define ATG_ENGINE_SIM_ROD_JOURNAL_NODE_H #include "object_reference_node.h" #include "engine_sim.h" #include namespace es_script { class CrankshaftNode; class ConnectingRodNode; class RodJournalNode : public ObjectReferenceNode { friend CrankshaftNode; friend ConnectingRodNode; public: RodJournalNode() { /* void */ } virtual ~RodJournalNode() { /* void */ } double getAngle() const { return m_angle; } CrankshaftNode *getCrankshaft() const { return m_crankshaft; } ConnectingRodNode *getRod() const { return m_rod; } protected: virtual void registerInputs() { addInput("angle", &m_angle); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } CrankshaftNode *m_crankshaft = nullptr; ConnectingRodNode *m_rod = nullptr; double m_angle = 0.0; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_ROD_JOURNAL_NODE_H */ ================================================ FILE: scripting/include/standard_valvetrain_node.h ================================================ #ifndef ATG_ENGINE_SIM_STANDARD_VALVETRAIN_NODE_H #define ATG_ENGINE_SIM_STANDARD_VALVETRAIN_NODE_H #include "valvetrain_node.h" #include "camshaft_node.h" #include "engine_sim.h" namespace es_script { class StandardValvetrainNode : public ValvetrainNode { public: StandardValvetrainNode() { /* void */ } virtual ~StandardValvetrainNode() { /* void */ } virtual Valvetrain *generate( EngineContext *context, Crankshaft *crank) override { StandardValvetrain *valvetrain = new StandardValvetrain; Camshaft *intakeCam = new Camshaft(), *exhaustCam = new Camshaft(); m_intakeCamshaft->generate(intakeCam, crank, context); m_exhaustCamshaft->generate(exhaustCam, crank, context); StandardValvetrain::Parameters params; params.intakeCamshaft = intakeCam; params.exhaustCamshaft = exhaustCam; valvetrain->initialize(params); return valvetrain; } protected: virtual void registerInputs() { addInput("intake_camshaft", &m_intakeCamshaft, InputTarget::Type::Object); addInput("exhaust_camshaft", &m_exhaustCamshaft, InputTarget::Type::Object); ValvetrainNode::registerInputs(); } private: CamshaftNode *m_intakeCamshaft = nullptr; CamshaftNode *m_exhaustCamshaft = nullptr; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_STANDARD_VALVETRAIN_NODE_H */ ================================================ FILE: scripting/include/throttle_nodes.h ================================================ #ifndef ATG_ENGINE_SIM_THROTTLE_NODES_H #define ATG_ENGINE_SIM_THROTTLE_NODES_H #include "object_reference_node.h" #include "engine_sim.h" namespace es_script { class ThrottleNode : public ObjectReferenceNode { public: ThrottleNode() { /* void */ } virtual ~ThrottleNode() { /* void */ } virtual Throttle *generate() const = 0; protected: virtual void _evaluate() { setOutput(this); readAllInputs(); } }; class DirectThrottleLinkageNode : public ThrottleNode { public: DirectThrottleLinkageNode() { /* void */ } virtual ~DirectThrottleLinkageNode() { /* void */ } virtual Throttle *generate() const override { DirectThrottleLinkage *throttle = new DirectThrottleLinkage; throttle->initialize(m_parameters); return static_cast(throttle); } protected: virtual void registerInputs() override { addInput("gamma", &m_parameters.gamma); ThrottleNode::registerInputs(); } DirectThrottleLinkage::Parameters m_parameters; }; class GovernorNode : public ThrottleNode { public: GovernorNode() { /* void */ } virtual ~GovernorNode() { /* void */ } virtual Throttle *generate() const override { Governor *throttle = new Governor; throttle->initialize(m_parameters); return static_cast(throttle); } protected: virtual void registerInputs() override { addInput("min_speed", &m_parameters.minSpeed); addInput("max_speed", &m_parameters.maxSpeed); addInput("min_v", &m_parameters.minVelocity); addInput("max_v", &m_parameters.maxVelocity); addInput("k_s", &m_parameters.k_s); addInput("k_d", &m_parameters.k_d); addInput("gamma", &m_parameters.gamma); ThrottleNode::registerInputs(); } Governor::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_THROTTLE_NODES_H */ ================================================ FILE: scripting/include/transmission_node.h ================================================ #ifndef ATG_ENGINE_SIM_TRANSMISSION_NODE_H #define ATG_ENGINE_SIM_TRANSMISSION_NODE_H #include "object_reference_node.h" #include "engine_sim.h" #include namespace es_script { class TransmissionNode : public ObjectReferenceNode { public: TransmissionNode() { /* void */ } virtual ~TransmissionNode() { /* void */ } void generate(Transmission *transmission) const { Transmission::Parameters parameters = m_parameters; parameters.GearCount = static_cast(m_gears.size()); parameters.GearRatios = m_gears.data(); transmission->initialize(parameters); } void addGear(double ratio) { m_gears.push_back(ratio); } protected: virtual void registerInputs() { addInput("max_clutch_torque", &m_parameters.MaxClutchTorque); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } Transmission::Parameters m_parameters; std::vector m_gears; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_TRANSMISSION_NODE_H */ ================================================ FILE: scripting/include/valvetrain_node.h ================================================ #ifndef ATG_ENGINE_SIM_VALVETRAIN_NODE_H #define ATG_ENGINE_SIM_VALVETRAIN_NODE_H #include "object_reference_node.h" #include "engine_context.h" namespace es_script { class ValvetrainNode : public ObjectReferenceNode { public: ValvetrainNode() { /* void */ } virtual ~ValvetrainNode() { /* void */ } virtual Valvetrain *generate(EngineContext *context, Crankshaft *crank) = 0; protected: virtual void registerInputs() { ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); readAllInputs(); } }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_VALVETRAIN_NODE_H */ ================================================ FILE: scripting/include/vehicle_node.h ================================================ #ifndef ATG_ENGINE_SIM_VEHICLE_NODE_H #define ATG_ENGINE_SIM_VEHICLE_NODE_H #include "object_reference_node.h" #include "engine_sim.h" namespace es_script { class VehicleNode : public ObjectReferenceNode { public: VehicleNode() { /* void */ } virtual ~VehicleNode() { /* void */ } void generate(Vehicle *vehicle) const { vehicle->initialize(m_parameters); } protected: virtual void registerInputs() { addInput("mass", &m_parameters.mass); addInput("drag_coefficient", &m_parameters.dragCoefficient); addInput("cross_sectional_area", &m_parameters.crossSectionArea); addInput("diff_ratio", &m_parameters.diffRatio); addInput("tire_radius", &m_parameters.tireRadius); addInput("rolling_resistance", &m_parameters.rollingResistance); ObjectReferenceNode::registerInputs(); } virtual void _evaluate() { setOutput(this); // Read inputs readAllInputs(); } Vehicle::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_VEHICLE_NODE_H */ ================================================ FILE: scripting/include/vtec_valvetrain_node.h ================================================ #ifndef ATG_ENGINE_SIM_VTEC_VALVETRAIN_NODE_H #define ATG_ENGINE_SIM_VTEC_VALVETRAIN_NODE_H #include "valvetrain_node.h" #include "camshaft_node.h" #include "engine_sim.h" namespace es_script { class VtecValvetrainNode : public ValvetrainNode { public: VtecValvetrainNode() { /* void */ } virtual ~VtecValvetrainNode() { /* void */ } virtual Valvetrain *generate( EngineContext *context, Crankshaft *crank) override { VtecValvetrain *valvetrain = new VtecValvetrain; Camshaft *intakeCam = new Camshaft(), *exhaustCam = new Camshaft(), *vtecIntakeCam = new Camshaft(), *vtecExhaustCam = new Camshaft(); m_intakeCamshaft->generate(intakeCam, crank, context); m_exhaustCamshaft->generate(exhaustCam, crank, context); m_vtecIntakeCamshaft->generate(vtecIntakeCam, crank, context); m_vtecExhaustCamshaft->generate(vtecExhaustCam, crank, context); VtecValvetrain::Parameters params; params.intakeCamshaft = intakeCam; params.exhaustCamshaft = exhaustCam; params.vtecIntakeCamshaft = vtecIntakeCam; params.vtexExhaustCamshaft = vtecExhaustCam; params.minRpm = m_parameters.minRpm; params.minSpeed = m_parameters.minSpeed; params.minThrottlePosition = m_parameters.minThrottlePosition; params.manifoldVacuum = m_parameters.manifoldVacuum; params.engine = context->getEngine(); valvetrain->initialize(params); return valvetrain; } protected: virtual void registerInputs() { addInput("vtec_intake_camshaft", &m_vtecIntakeCamshaft, InputTarget::Type::Object); addInput("vtec_exhaust_camshaft", &m_vtecExhaustCamshaft, InputTarget::Type::Object); addInput("intake_camshaft", &m_intakeCamshaft, InputTarget::Type::Object); addInput("exhaust_camshaft", &m_exhaustCamshaft, InputTarget::Type::Object); addInput("min_rpm", &m_parameters.minRpm); addInput("min_speed", &m_parameters.minSpeed); addInput("manifold_vacuum", &m_parameters.manifoldVacuum); addInput("min_throttle_position", &m_parameters.minThrottlePosition); ValvetrainNode::registerInputs(); } private: CamshaftNode *m_vtecIntakeCamshaft = nullptr; CamshaftNode *m_vtecExhaustCamshaft = nullptr; CamshaftNode *m_intakeCamshaft = nullptr; CamshaftNode *m_exhaustCamshaft = nullptr; VtecValvetrain::Parameters m_parameters; }; } /* namespace es_script */ #endif /* ATG_ENGINE_SIM_VTEC_VALVETRAIN_NODE_H */ ================================================ FILE: scripting/src/channel_types.cpp ================================================ #include "../include/channel_types.h" #define DEFINE_CHANNEL(channel_type) const piranha::ChannelType es_script::ObjectChannel::channel_type(#channel_type); DEFINE_CHANNEL(EngineChannel); DEFINE_CHANNEL(CrankshaftChannel); DEFINE_CHANNEL(RodJournalChannel); DEFINE_CHANNEL(ConnectingRodChannel); DEFINE_CHANNEL(CylinderBankChannel); DEFINE_CHANNEL(PistonChannel); DEFINE_CHANNEL(FunctionChannel); DEFINE_CHANNEL(IntakeChannel); DEFINE_CHANNEL(ExhaustSystemChannel); DEFINE_CHANNEL(CylinderHeadChannel); DEFINE_CHANNEL(CamshaftChannel); DEFINE_CHANNEL(IgnitionModuleChannel); DEFINE_CHANNEL(IgnitionWireChannel); DEFINE_CHANNEL(FuelChannel); DEFINE_CHANNEL(ImpulseResponseChannel); DEFINE_CHANNEL(ValvetrainChannel); DEFINE_CHANNEL(VehicleChannel); DEFINE_CHANNEL(TransmissionChannel); DEFINE_CHANNEL(ThrottleChannel); ================================================ FILE: scripting/src/compiler.cpp ================================================ #include "../include/compiler.h" es_script::Compiler::Output *es_script::Compiler::s_output = nullptr; es_script::Compiler::Compiler() { m_compiler = nullptr; } es_script::Compiler::~Compiler() { assert(m_compiler == nullptr); } es_script::Compiler::Output *es_script::Compiler::output() { if (s_output == nullptr) { s_output = new Output; } return s_output; } void es_script::Compiler::initialize() { m_compiler = new piranha::Compiler(&m_rules); m_compiler->setFileExtension(".mr"); m_compiler->addSearchPath("../../es/"); m_compiler->addSearchPath("../es/"); m_compiler->addSearchPath("es/"); m_rules.initialize(); } bool es_script::Compiler::compile(const piranha::IrPath &path) { bool successful = false; std::ofstream file("error_log.log", std::ios::out); piranha::IrCompilationUnit *unit = m_compiler->compile(path); if (unit == nullptr) { file << "Can't find file: " << path.toString() << "\n"; } else { const piranha::ErrorList *errors = m_compiler->getErrorList(); if (errors->getErrorCount() == 0) { unit->build(&m_program); m_program.initialize(); successful = true; } else { for (int i = 0; i < errors->getErrorCount(); ++i) { printError(errors->getCompilationError(i), file); } } } file.close(); return successful; } es_script::Compiler::Output es_script::Compiler::execute() { const bool result = m_program.execute(); if (!result) { // Todo: Runtime error } return *output(); } void es_script::Compiler::destroy() { m_program.free(); m_compiler->free(); delete m_compiler; m_compiler = nullptr; } void es_script::Compiler::printError( const piranha::CompilationError *err, std::ofstream &file) const { const piranha::ErrorCode_struct &errorCode = err->getErrorCode(); file << err->getCompilationUnit()->getPath().getStem() << "(" << err->getErrorLocation()->lineStart << "): error " << errorCode.stage << errorCode.code << ": " << errorCode.info << std::endl; piranha::IrContextTree *context = err->getInstantiation(); while (context != nullptr) { piranha::IrNode *instance = context->getContext(); if (instance != nullptr) { const std::string instanceName = instance->getName(); const std::string definitionName = (instance->getDefinition() != nullptr) ? instance->getDefinition()->getName() : ""; const std::string formattedName = (instanceName.empty()) ? " " + definitionName : instanceName + " " + definitionName; file << " While instantiating: " << instance->getParentUnit()->getPath().getStem() << "(" << instance->getSummaryToken()->lineStart << "): " << formattedName << std::endl; } context = context->getParent(); } } ================================================ FILE: scripting/src/engine_context.cpp ================================================ #include "../include/engine_context.h" #include "../include/cylinder_bank_node.h" es_script::EngineContext::EngineContext() { /* void */ } es_script::EngineContext::~EngineContext() { /* void */ } void es_script::EngineContext::addHead(CylinderHeadNode *node, CylinderHead *head) { m_heads[node] = head; } CylinderHead *es_script::EngineContext::getHead(CylinderHeadNode *head) { auto it = m_heads.find(head); if (it != m_heads.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addBank(CylinderBankNode *node, CylinderBank *bank) { m_banks[node] = bank; } CylinderBank *es_script::EngineContext::getBank(CylinderBankNode *bank) const { auto it = m_banks.find(bank); if (it != m_banks.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addRodJournal(RodJournalNode *node, int index) { m_rodJournals[node] = index; } int es_script::EngineContext::getRodJournalIndex(RodJournalNode *node) const { auto it = m_rodJournals.find(node); if (it != m_rodJournals.end()) { return it->second; } else return -1; } void es_script::EngineContext::addIntake(IntakeNode *node, Intake *intake) { m_intakes[node] = intake; } Intake *es_script::EngineContext::getIntake(IntakeNode *intake) const { auto it = m_intakes.find(intake); if (it != m_intakes.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addExhaust( ExhaustSystemNode *node, ExhaustSystem *exhaust) { m_exhaustSystems[node] = exhaust; } ExhaustSystem *es_script::EngineContext::getExhaust(ExhaustSystemNode *exhaust) const { auto it = m_exhaustSystems.find(exhaust); if (it != m_exhaustSystems.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addFunction(FunctionNode *node, Function *function) { m_functions[node] = function; } Function *es_script::EngineContext::getFunction(FunctionNode *exhaust) const { auto it = m_functions.find(exhaust); if (it != m_functions.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addImpulseResponse( ImpulseResponseNode *node, ImpulseResponse *impulse) { m_impulseResponses[node] = impulse; } ImpulseResponse *es_script::EngineContext::getImpulseResponse( ImpulseResponseNode *node) const { auto it = m_impulseResponses.find(node); if (it != m_impulseResponses.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addCrankshaft( CrankshaftNode *node, Crankshaft *crankshaft) { m_crankshafts[node] = crankshaft; } Crankshaft *es_script::EngineContext::getCrankshaft(CrankshaftNode *node) const { auto it = m_crankshafts.find(node); if (it != m_crankshafts.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::addConnectingRod( ConnectingRodNode *node, ConnectingRod *rod) { m_rods[node] = rod; } ConnectingRod *es_script::EngineContext::getConnectingRod( ConnectingRodNode *node) const { auto it = m_rods.find(node); if (it != m_rods.end()) { return it->second; } else return nullptr; } void es_script::EngineContext::setCylinderIndex( const CylinderBankNode *bank, int localIndex, int globalIndex) { m_cylinderIndices[{ bank, localIndex }] = globalIndex; } int es_script::EngineContext::getCylinderIndex( const CylinderBankNode *bank, int localIndex) const { return m_cylinderIndices.at({ bank, localIndex }); } ================================================ FILE: scripting/src/language_rules.cpp ================================================ #include "../include/language_rules.h" #include "../include/channel_types.h" #include "../include/engine_node.h" #include "../include/actions.h" #include "../include/rod_journal_node.h" #include "../include/camshaft_node.h" #include "../include/cylinder_head_node.h" #include "../include/ignition_module_node.h" #include "../include/impulse_response_node.h" #include "../include/standard_valvetrain_node.h" #include "../include/vtec_valvetrain_node.h" #include "../include/vehicle_node.h" #include "../include/transmission_node.h" #include "../include/throttle_nodes.h" es_script::LanguageRules::LanguageRules() { /* void */ } es_script::LanguageRules::~LanguageRules() { /* void */ } void es_script::LanguageRules::registerBuiltinNodeTypes() { // ==================================================== // Builtin types // ==================================================== // Channels registerBuiltinType( "__engine_sim__float", &piranha::FundamentalType::FloatType); registerBuiltinType( "__engine_sim__int", &piranha::FundamentalType::IntType); registerBuiltinType( "__engine_sim__bool", &piranha::FundamentalType::BoolType); registerBuiltinType( "__engine_sim__string", &piranha::FundamentalType::StringType); registerBuiltinType( "__engine_sim__engine_channel", &es_script::ObjectChannel::EngineChannel); registerBuiltinType( "__engine_sim__crankshaft_channel", &es_script::ObjectChannel::CrankshaftChannel); registerBuiltinType( "__engine_sim__rod_journal_channel", &es_script::ObjectChannel::RodJournalChannel); registerBuiltinType( "__engine_sim__connecting_rod_channel", &es_script::ObjectChannel::ConnectingRodChannel); registerBuiltinType( "__engine_sim__piston_channel", &es_script::ObjectChannel::PistonChannel); registerBuiltinType( "__engine_sim__cylinder_bank_channel", &es_script::ObjectChannel::CylinderBankChannel); registerBuiltinType( "__engine_sim__function_channel", &es_script::ObjectChannel::FunctionChannel); registerBuiltinType( "__engine_sim__cylinder_head_channel", &es_script::ObjectChannel::CylinderHeadChannel); registerBuiltinType( "__engine_sim__camshaft_channel", &es_script::ObjectChannel::CamshaftChannel); registerBuiltinType( "__engine_sim__intake_channel", &es_script::ObjectChannel::IntakeChannel); registerBuiltinType( "__engine_sim__exhaust_system_channel", &es_script::ObjectChannel::ExhaustSystemChannel); registerBuiltinType( "__engine_sim__ignition_module_channel", &es_script::ObjectChannel::IgnitionModuleChannel); registerBuiltinType( "__engine_sim__ignition_wire_channel", &es_script::ObjectChannel::IgnitionWireChannel); registerBuiltinType( "__engine_sim__fuel_channel", &es_script::ObjectChannel::FuelChannel); registerBuiltinType( "__engine_sim__impulse_response_channel", &es_script::ObjectChannel::ImpulseResponseChannel); registerBuiltinType( "__engine_sim__valvetrain_channel", &es_script::ObjectChannel::ValvetrainChannel); registerBuiltinType( "__engine_sim__vehicle_channel", &es_script::ObjectChannel::VehicleChannel); registerBuiltinType( "__engine_sim__transmission_channel", &es_script::ObjectChannel::TransmissionChannel); registerBuiltinType( "__engine_sim__throttle_channel", &es_script::ObjectChannel::ThrottleChannel); // Literals registerBuiltinType( "__engine_sim__literal_float", &piranha::FundamentalType::FloatType); registerBuiltinType( "__engine_sim__literal_string", &piranha::FundamentalType::StringType); registerBuiltinType( "__engine_sim__literal_int", &piranha::FundamentalType::IntType); registerBuiltinType( "__engine_sim__literal_bool", &piranha::FundamentalType::BoolType); // Conversions registerBuiltinType( "__engine_sim__int_to_float"); registerBuiltinType( "__engine_sim__int_to_string"); registerBuiltinType( "__engine_sim__string_to_int"); // Float operations registerBuiltinType>( "__engine_sim__float_negate"); registerBuiltinType>("__engine_sim__float_divide"); registerBuiltinType>("__engine_sim__float_multiply"); registerBuiltinType>("__engine_sim__float_divide"); registerBuiltinType>("__engine_sim__float_add"); registerBuiltinType>("__engine_sim__float_subtract"); // Int operations registerBuiltinType>("__engine_sim__int_multiply"); registerBuiltinType>("__engine_sim__int_add"); registerBuiltinType>("__engine_sim__int_subtract"); registerBuiltinType>("__engine_sim__int_divide"); registerBuiltinType>("__engine_sim__int_negate"); // Actions registerBuiltinType("__engine_sim__set_engine"); registerBuiltinType("__engine_sim__add_rod_journal"); registerBuiltinType("__engine_sim__add_slave_journal"); registerBuiltinType("__engine_sim__add_crankshaft"); registerBuiltinType("__engine_sim__add_cylinder_bank"); registerBuiltinType("__engine_sim__add_cylinder"); registerBuiltinType("__engine_sim__add_sample"); registerBuiltinType("__engine_sim__add_lobe"); registerBuiltinType("__engine_sim__set_cylinder_head"); registerBuiltinType("__engine_sim__connect_ignition_wire"); registerBuiltinType("__engine_sim__add_ignition_module"); registerBuiltinType("__engine_sim__k_28inH2O"); registerBuiltinType("__engine_sim__k_carb"); registerBuiltinType("__engine_sim__generate_harmonic_cam_lobe"); registerBuiltinType("__engine_sim__set_application_settings"); registerBuiltinType("__engine_sim__set_vehicle"); registerBuiltinType("__engine_sim__set_transmission"); registerBuiltinType("__engine_sim__add_gear"); // Objects registerBuiltinType("__engine_sim__engine"); registerBuiltinType("__engine_sim__rod_journal"); registerBuiltinType("__engine_sim__crankshaft"); registerBuiltinType("__engine_sim__connecting_rod"); registerBuiltinType("__engine_sim__cylinder_bank"); registerBuiltinType("__engine_sim__piston"); registerBuiltinType("__engine_sim__function"); registerBuiltinType("__engine_sim__cylinder_head"); registerBuiltinType("__engine_sim__camshaft"); registerBuiltinType("__engine_sim__exhaust_system"); registerBuiltinType("__engine_sim__intake"); registerBuiltinType("__engine_sim__ignition_module"); registerBuiltinType("__engine_sim__ignition_wire"); registerBuiltinType("__engine_sim__fuel"); registerBuiltinType("__engine_sim__impulse_response"); registerBuiltinType("__engine_sim__standard_valvetrain"); registerBuiltinType("__engine_sim__vtec_valvetrain"); registerBuiltinType("__engine_sim__vehicle"); registerBuiltinType("__engine_sim__transmission"); registerBuiltinType("__engine_sim__direct_throttle_linkage"); registerBuiltinType("__engine_sim__governor"); // String operations registerBuiltinType>("__engine_sim__string_add"); // ==================================================== // Conversions // ==================================================== registerConversion( { &piranha::FundamentalType::IntType, &piranha::FundamentalType::FloatType }, "__engine_sim__int_to_float" ); registerConversion( { &piranha::FundamentalType::IntType, &piranha::FundamentalType::StringType }, "__engine_sim__int_to_string" ); registerConversion( { &piranha::FundamentalType::StringType, &piranha::FundamentalType::IntType }, "__engine_sim__string_to_int" ); // Literals registerLiteralType(piranha::LiteralType::Float, "__engine_sim__literal_float"); registerLiteralType(piranha::LiteralType::String, "__engine_sim__literal_string"); registerLiteralType(piranha::LiteralType::Integer, "__engine_sim__literal_int"); registerLiteralType(piranha::LiteralType::Boolean, "__engine_sim__literal_bool"); // Operations registerUnaryOperator( { piranha::IrUnaryOperator::Operator::NumericNegate, &piranha::FundamentalType::FloatType }, "__engine_sim__float_negate"); registerOperator( { piranha::IrBinaryOperator::Operator::Mul, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::FloatType }, "__engine_sim__float_multiply"); registerOperator( { piranha::IrBinaryOperator::Operator::Mul, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::IntType }, "__engine_sim__float_multiply"); registerOperator( { piranha::IrBinaryOperator::Operator::Div, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::FloatType }, "__engine_sim__float_divide"); registerOperator( { piranha::IrBinaryOperator::Operator::Div, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::IntType }, "__engine_sim__float_divide"); registerOperator( { piranha::IrBinaryOperator::Operator::Sub, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::FloatType }, "__engine_sim__float_subtract"); registerOperator( { piranha::IrBinaryOperator::Operator::Sub, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::IntType }, "__engine_sim__float_subtract"); registerOperator( { piranha::IrBinaryOperator::Operator::Add, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::FloatType }, "__engine_sim__float_add"); registerOperator( { piranha::IrBinaryOperator::Operator::Add, &piranha::FundamentalType::FloatType, &piranha::FundamentalType::IntType }, "__engine_sim__float_add"); registerUnaryOperator( { piranha::IrUnaryOperator::Operator::NumericNegate, &piranha::FundamentalType::IntType }, "__engine_sim__int_negate"); registerOperator( { piranha::IrBinaryOperator::Operator::Mul, &piranha::FundamentalType::IntType, &piranha::FundamentalType::IntType }, "__engine_sim__int_multiply"); registerOperator( { piranha::IrBinaryOperator::Operator::Div, &piranha::FundamentalType::IntType, &piranha::FundamentalType::IntType }, "__engine_sim__int_divide"); registerOperator( { piranha::IrBinaryOperator::Operator::Sub, &piranha::FundamentalType::IntType, &piranha::FundamentalType::IntType }, "__engine_sim__int_subtract"); registerOperator( { piranha::IrBinaryOperator::Operator::Add, &piranha::FundamentalType::IntType, &piranha::FundamentalType::IntType }, "__engine_sim__int_add"); } ================================================ FILE: src/afr_cluster.cpp ================================================ #include "../include/afr_cluster.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include AfrCluster::AfrCluster() { m_engine = nullptr; m_intakeAfrGauge = nullptr; m_exhaustAfrGauge = nullptr; } AfrCluster::~AfrCluster() { /* void */ } void AfrCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); m_intakeAfrGauge = addElement(); m_exhaustAfrGauge = addElement(); constexpr float shortenAngle = (float)units::angle(1.0, units::deg); m_intakeAfrGauge->m_title = "IN. AFR"; m_intakeAfrGauge->m_unit = ""; m_intakeAfrGauge->m_precision = 1; m_intakeAfrGauge->setLocalPosition({ 0, 0 }); m_intakeAfrGauge->m_gauge->m_min = 0; m_intakeAfrGauge->m_gauge->m_max = 50; m_intakeAfrGauge->m_gauge->m_minorStep = 1; m_intakeAfrGauge->m_gauge->m_majorStep = 5; m_intakeAfrGauge->m_gauge->m_maxMinorTick = 7000; m_intakeAfrGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_intakeAfrGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_intakeAfrGauge->m_gauge->m_needleWidth = 4.0f; m_intakeAfrGauge->m_gauge->m_gamma = 1.0f; m_intakeAfrGauge->m_gauge->m_needleKs = 1000.0f; m_intakeAfrGauge->m_gauge->m_needleKd = 20.0f; m_intakeAfrGauge->m_gauge->setBandCount(0); m_exhaustAfrGauge->m_title = "EX. O2"; m_exhaustAfrGauge->m_unit = ""; m_exhaustAfrGauge->m_precision = 1; m_exhaustAfrGauge->setLocalPosition({ 0, 0 }); m_exhaustAfrGauge->m_gauge->m_min = 0; m_exhaustAfrGauge->m_gauge->m_max = 100; m_exhaustAfrGauge->m_gauge->m_minorStep = 5; m_exhaustAfrGauge->m_gauge->m_majorStep = 10; m_exhaustAfrGauge->m_gauge->m_maxMinorTick = 200; m_exhaustAfrGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_exhaustAfrGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_exhaustAfrGauge->m_gauge->m_needleWidth = 4.0f; m_exhaustAfrGauge->m_gauge->m_gamma = 1.0f; m_exhaustAfrGauge->m_gauge->m_needleKs = 1000.0f; m_exhaustAfrGauge->m_gauge->m_needleKd = 20.0f; m_exhaustAfrGauge->m_gauge->setBandCount(0); } void AfrCluster::destroy() { UiElement::destroy(); } void AfrCluster::update(float dt) { UiElement::update(dt); } void AfrCluster::render() { const Bounds top = m_bounds.verticalSplit(0.5f, 1.0f); const Bounds bottom = m_bounds.verticalSplit(0.0f, 0.5f); m_intakeAfrGauge->m_bounds = top; m_intakeAfrGauge->m_gauge->m_value = (m_engine != nullptr) ? static_cast(m_engine->getIntakeAfr()) : 0.0f; m_exhaustAfrGauge->m_bounds = bottom; m_exhaustAfrGauge->m_gauge->m_value = (m_engine != nullptr) ? static_cast(m_engine->getExhaustO2()) * 100.0f : 0.0f; UiElement::render(); } ================================================ FILE: src/audio_buffer.cpp ================================================ #include "../include/audio_buffer.h" #include AudioBuffer::AudioBuffer() { m_writePointer = 0; m_sampleRate = 0; m_samples = nullptr; m_bufferSize = 0; m_offsetToSeconds = 0; } AudioBuffer::~AudioBuffer() { assert(m_samples == nullptr); } void AudioBuffer::initialize(int sampleRate, int bufferSize) { m_writePointer = 0; m_sampleRate = sampleRate; m_samples = new int16_t[bufferSize]; memset(m_samples, 0, sizeof(int16_t) * bufferSize); m_bufferSize = bufferSize; m_offsetToSeconds = 1 / (double)sampleRate; } void AudioBuffer::destroy() { delete[] m_samples; m_samples = nullptr; m_bufferSize = 0; } bool AudioBuffer::checkForDiscontinuitiy(int threshold) const { for (int i = 0; i < m_bufferSize - 1; ++i) { const int i0 = getBufferIndex(i + m_writePointer); const int i1 = getBufferIndex(i0 + 1); if (std::abs(m_samples[i0] - m_samples[i1]) >= threshold) { return true; } } return false; } ================================================ FILE: src/camshaft.cpp ================================================ #include "../include/camshaft.h" #include "../include/crankshaft.h" #include "../include/constants.h" #include "../include/units.h" #include #include Camshaft::Camshaft() { m_crankshaft = nullptr; m_lobeAngles = nullptr; m_lobeProfile = nullptr; m_lobes = 0; m_advance = 0; m_baseRadius = 0; } Camshaft::~Camshaft() { assert(m_lobeAngles == nullptr); } void Camshaft::initialize(const Parameters ¶ms) { m_lobeAngles = new double[params.lobes]; memset(m_lobeAngles, 0, sizeof(double) * params.lobes); m_lobes = params.lobes; m_crankshaft = params.crankshaft; m_lobeProfile = params.lobeProfile; m_advance = params.advance; m_baseRadius = params.baseRadius; } void Camshaft::destroy() { delete[] m_lobeAngles; m_lobeAngles = nullptr; m_lobes = 0; } double Camshaft::valveLift(int lobe) const { return sampleLobe(getAngle() + m_lobeAngles[lobe]); } double Camshaft::sampleLobe(double theta) const { double clampedTheta = std::fmod(theta, 2 * constants::pi); if (clampedTheta < 0) clampedTheta += 2 * constants::pi; if (clampedTheta >= constants::pi) clampedTheta -= 2 * constants::pi; return m_lobeProfile->sampleTriangle(clampedTheta); } double Camshaft::getAngle() const { const double angle = std::fmod((m_crankshaft->getAngle() + m_advance) * 0.5, 2 * constants::pi); return (angle < 0) ? angle + 2 * constants::pi : angle; } ================================================ FILE: src/combustion_chamber.cpp ================================================ #include "../include/combustion_chamber.h" #include "../include/constants.h" #include "../include/units.h" #include "../include/piston.h" #include "../include/connecting_rod.h" #include "../include/utilities.h" #include "../include/exhaust_system.h" #include "../include/cylinder_bank.h" #include "../include/engine.h" #include CombustionChamber::CombustionChamber() { m_crankcasePressure = 0.0; m_piston = nullptr; m_head = nullptr; m_engine = nullptr; m_pistonSpeed = nullptr; m_pressure = nullptr; m_lit = false; m_litLastFrame = false; m_peakTemperature = 0; m_meanPistonSpeedToTurbulence = nullptr; m_nBurntFuel = 0; m_manifoldToRunnerFlowRate = 0; m_primaryToCollectorFlowRate = 0; m_cylinderWidthApproximation = 0; m_cylinderCrossSectionSurfaceArea = 0; m_lastTimestepTotalExhaustFlow = 0; m_lastTimestepTotalIntakeFlow = 0; m_exhaustFlow = 0; m_exhaustFlowRate = 0; m_intakeFlowRate = 0; m_fuel = nullptr; } CombustionChamber::~CombustionChamber() { assert(m_pistonSpeed == nullptr); assert(m_pressure == nullptr); } void CombustionChamber::initialize(const Parameters ¶ms) { m_piston = params.Piston; m_head = params.Head; m_fuel = params.Fuel; m_crankcasePressure = params.CrankcasePressure; m_meanPistonSpeedToTurbulence = params.MeanPistonSpeedToTurbulence; m_pistonSpeed = new double[StateSamples]; m_pressure = new double[StateSamples]; for (int i = 0; i < StateSamples; ++i) { m_pistonSpeed[i] = 0; m_pressure[i] = 0; } Intake *intake = m_head->getIntake(m_piston->getCylinderIndex()); ExhaustSystem *exhaust = m_head->getExhaustSystem(m_piston->getCylinderIndex()); m_manifoldToRunnerFlowRate = intake->getRunnerFlowRate(); m_primaryToCollectorFlowRate = exhaust->getPrimaryFlowRate(); const double bore_r = m_head->getCylinderBank()->getBore() / 2.0; m_cylinderCrossSectionSurfaceArea = constants::pi * bore_r * bore_r; m_cylinderWidthApproximation = std::sqrt(m_cylinderCrossSectionSurfaceArea); const double height = getVolume() / m_cylinderCrossSectionSurfaceArea; m_system.setGeometry( m_cylinderWidthApproximation, height, 1.0, 0.0); const double intakeRunnerCrossSection = m_head->getIntakeRunnerCrossSectionArea(); const double intakeRunnerWidth = std::sqrt(intakeRunnerCrossSection); const double manifoldRunnerLength = intake->getRunnerLength(); const double manifoldRunnerVolume = intakeRunnerCrossSection * manifoldRunnerLength; const double totalIntakeRunnerVolume = m_head->getIntakeRunnerVolume() + manifoldRunnerVolume; const double overallIntakeRunnerLength = totalIntakeRunnerVolume / intakeRunnerCrossSection; m_intakeRunnerAndManifold.initialize( units::pressure(1.0, units::atm), totalIntakeRunnerVolume, units::celcius(25.0)); m_intakeRunnerAndManifold.setGeometry( overallIntakeRunnerLength, intakeRunnerWidth, 1.0, 0.0); const double exhaustRunnerCrossSection = m_head->getExhaustRunnerCrossSectionArea(); const double exhaustRunnerWidth = std::sqrt(exhaustRunnerCrossSection); const double exhaustTubeLength = exhaust->getPrimaryTubeLength() + m_head->getHeaderPrimaryLength(m_piston->getCylinderIndex()); const double exhaustTubeVolume = exhaustRunnerCrossSection * exhaustTubeLength; const double totalExhaustRunnerVolume = m_head->getExhaustRunnerVolume() + exhaustTubeVolume; const double overallExhaustRunnerLength = totalExhaustRunnerVolume / exhaustRunnerCrossSection; m_exhaustRunnerAndPrimary.initialize( units::pressure(1.0, units::atm), totalExhaustRunnerVolume, units::celcius(25.0)); m_exhaustRunnerAndPrimary.setGeometry( overallExhaustRunnerLength, exhaustRunnerWidth, 1.0, 0.0); } void CombustionChamber::destroy() { if (m_pistonSpeed != nullptr) delete[] m_pistonSpeed; if (m_pressure != nullptr) delete[] m_pressure; m_pistonSpeed = nullptr; m_pressure = nullptr; } double CombustionChamber::getVolume() const { const double combustionPortVolume = m_head->getCombustionChamberVolume(); const CylinderBank *bank = m_head->getCylinderBank(); const double area = bank->boreSurfaceArea(); const double s = m_piston->relativeX() * bank->getDx() + m_piston->relativeY() * bank->getDy(); const double sweep = area * (bank->getDeckHeight() - s - m_piston->getCompressionHeight()); return sweep + combustionPortVolume - m_piston->getDisplacement(); } double CombustionChamber::pistonSpeed() const { const CylinderBank *bank = m_head->getCylinderBank(); return m_piston->m_body.v_x * bank->getDx() + m_piston->m_body.v_y * bank->getDy(); } double CombustionChamber::calculateMeanPistonSpeed() const { double avg = 0; for (int i = 0; i < StateSamples; ++i) { avg += m_pistonSpeed[i]; } avg /= StateSamples; return avg; } double CombustionChamber::calculateFiringPressure() const { double firingPressure = 0; for (int i = 0; i < StateSamples; ++i) { if (m_pressure[i] > firingPressure) { firingPressure = m_pressure[i]; } } return firingPressure; } bool CombustionChamber::popLitLastFrame() { const bool lit = m_litLastFrame; m_litLastFrame = false; return lit; } void CombustionChamber::ignite() { if (!m_lit) { if (m_system.mix().p_fuel == 0) return; const double afr = m_system.mix().p_o2 / m_system.mix().p_fuel; const double equivalenceRatio = afr / m_fuel->getMolecularAfr(); if (equivalenceRatio < 0.5) return; else if (equivalenceRatio > 1.9) return; const double idealInert = m_system.mix().p_o2 / 0.7; const double dilution = (m_system.mix().p_inert / idealInert) - 1; m_flameEvent.lastVolume = getVolume(); m_flameEvent.travel_x = 0; m_flameEvent.travel_y = 0; m_flameEvent.lit_n = 0; m_flameEvent.total_n = m_system.n(); m_flameEvent.percentageLit = 0; m_flameEvent.globalMix = m_system.mix(); m_lit = true; m_litLastFrame = true; const double randomness = m_fuel->getBurningEfficiencyRandomness(); const double lowEfficiencyAttenuation = m_fuel->getLowEfficiencyAttenuation(); const double maxBurningEfficiency = m_fuel->getMaxBurningEfficiency(); const double maxTurbulenceEffect = m_fuel->getMaxTurbulenceEffect(); const double maxDilutionEffect = m_fuel->getMaxDilutionEffect(); const double turbulence = m_meanPistonSpeedToTurbulence->sampleTriangle( calculateMeanPistonSpeed()); const double mixingFactor = 1.0 - ( clamp(turbulence / maxTurbulenceEffect) * clamp(1 - dilution / maxDilutionEffect)); const double rand_s = lowEfficiencyAttenuation * ((1 - randomness) + randomness * ((double)rand() / RAND_MAX)); const double efficiencyAttenuation = (mixingFactor * rand_s + (1 - mixingFactor)); m_flameEvent.efficiency = efficiencyAttenuation * maxBurningEfficiency; m_flameEvent.flameSpeed = m_fuel->flameSpeed( turbulence, afr, m_system.temperature(), m_system.pressure(), calculateFiringPressure(), units::pressure(160, units::psi)); } } void CombustionChamber::update(double dt) { m_system.setVolume(getVolume()); updateCycleStates(); m_intakeFlowRate = m_head->intakeFlowRate(m_piston->getCylinderIndex()); m_exhaustFlowRate = m_head->exhaustFlowRate(m_piston->getCylinderIndex()); } void CombustionChamber::flow(double dt) { if (m_system.temperature() > m_peakTemperature) { m_peakTemperature = m_system.temperature(); } const double volume = getVolume(); const double cylinderHeight = volume / m_cylinderCrossSectionSurfaceArea; const double cylinderSurfaceArea = cylinderHeight * constants::pi * m_head->getCylinderBank()->getBore() + m_cylinderCrossSectionSurfaceArea * 2; const double dT = units::celcius(90.0) - m_system.temperature(); m_system.changeEnergy(dT * cylinderSurfaceArea * 100 * dt); m_system.flow(m_piston->getBlowbyK(), dt, m_crankcasePressure, units::celcius(25.0)); Intake *intake = m_head->getIntake(m_piston->getCylinderIndex()); ExhaustSystem *exhaust = m_head->getExhaustSystem(m_piston->getCylinderIndex()); const double start_n = m_system.n(); GasSystem::FlowParameters flowParams; flowParams.dt = dt; flowParams.k_flow = m_manifoldToRunnerFlowRate; flowParams.crossSectionArea_0 = intake->getPlenumCrossSectionArea(); flowParams.crossSectionArea_1 = m_head->getIntakeRunnerCrossSectionArea(); flowParams.direction_x = 1.0; flowParams.direction_y = 0.0; flowParams.system_0 = &intake->m_system; flowParams.system_1 = &m_intakeRunnerAndManifold; GasSystem::flow(flowParams); m_intakeRunnerAndManifold.dissipateExcessVelocity(); flowParams.k_flow = m_intakeFlowRate; flowParams.crossSectionArea_0 = m_head->getIntakeRunnerCrossSectionArea(); flowParams.crossSectionArea_1 = volume / cylinderHeight; flowParams.direction_x = 1.0; flowParams.direction_y = 0.0; flowParams.system_0 = &m_intakeRunnerAndManifold; flowParams.system_1 = &m_system; const double intakeFlow = GasSystem::flow(flowParams); m_intakeRunnerAndManifold.dissipateExcessVelocity(); m_system.dissipateExcessVelocity(); flowParams.k_flow = m_exhaustFlowRate; flowParams.crossSectionArea_0 = volume / cylinderHeight; flowParams.crossSectionArea_1 = m_head->getExhaustRunnerCrossSectionArea(); flowParams.direction_x = 1.0; flowParams.direction_y = 0.0; flowParams.system_0 = &m_system; flowParams.system_1 = &m_exhaustRunnerAndPrimary; const double exhaustFlow = GasSystem::flow(flowParams); m_system.dissipateExcessVelocity(); m_exhaustRunnerAndPrimary.dissipateExcessVelocity(); flowParams.k_flow = m_primaryToCollectorFlowRate; flowParams.crossSectionArea_0 = m_head->getExhaustRunnerCrossSectionArea(); flowParams.crossSectionArea_1 = exhaust->getCollectorCrossSectionArea(); flowParams.direction_x = 1.0; flowParams.direction_y = 0.0; flowParams.system_0 = &m_exhaustRunnerAndPrimary; flowParams.system_1 = exhaust->getSystem(); GasSystem::flow(flowParams); m_intakeRunnerAndManifold.updateVelocity(dt, intake->getVelocityDecay()); m_system.updateVelocity(dt, 0.5); m_exhaustRunnerAndPrimary.updateVelocity(dt, exhaust->getVelocityDecay()); if (std::abs(intakeFlow) > 1E-9 && m_lit) { m_lit = false; } m_exhaustFlow = exhaustFlow; m_lastTimestepTotalExhaustFlow += exhaustFlow; m_lastTimestepTotalIntakeFlow += intakeFlow; if (m_lit) { CylinderBank *bank = m_head->getCylinderBank(); const double totalTravel_x = bank->getBore() / 2; const double totalTravel_y = volume / bank->boreSurfaceArea(); const double expansion = volume / m_flameEvent.lastVolume; const double lastTravel_x = m_flameEvent.travel_x; const double lastTravel_y = m_flameEvent.travel_y * expansion; const double flameSpeed = m_flameEvent.flameSpeed; m_flameEvent.travel_x = std::fmin(lastTravel_x + dt * flameSpeed, totalTravel_x); m_flameEvent.travel_y = std::fmin(lastTravel_y + dt * flameSpeed, totalTravel_y); if (lastTravel_x < m_flameEvent.travel_x || lastTravel_y < m_flameEvent.travel_y) { const double burnedVolume = m_flameEvent.travel_x * m_flameEvent.travel_x * constants::pi * m_flameEvent.travel_y; const double prevBurnedVolume = lastTravel_x * lastTravel_x * constants::pi * lastTravel_y; const double litVolume = burnedVolume - prevBurnedVolume; const double n = (litVolume / volume) * m_system.n(); const double fuelBurned = m_system.react(n * m_flameEvent.efficiency, m_flameEvent.globalMix); const double massFuelBurned = fuelBurned * m_fuel->getMolecularMass(); m_system.changeEnergy( massFuelBurned * m_fuel->getEnergyDensity()); m_flameEvent.lit_n += n; m_flameEvent.percentageLit += litVolume / volume; m_nBurntFuel += massFuelBurned; } else { m_lit = false; } m_flameEvent.lastVolume = volume; } } double CombustionChamber::lastEventAfr() const { const double totalFuel = m_flameEvent.globalMix.p_fuel * m_flameEvent.total_n; const double totalOxygen = m_flameEvent.globalMix.p_o2 * m_flameEvent.total_n; const double totalInert = m_flameEvent.globalMix.p_inert * m_flameEvent.total_n; constexpr double octaneMolarMass = units::mass(114.23, units::g); constexpr double oxygenMolarMass = units::mass(31.9988, units::g); constexpr double nitrogenMolarMass = units::mass(28.014, units::g); if (totalFuel == 0) return 0; else { return (oxygenMolarMass * totalOxygen + totalInert * nitrogenMolarMass) / (totalFuel * octaneMolarMass); } } double CombustionChamber::calculateFrictionForce(double v_s) const { const double cylinderWallForce = m_piston->calculateCylinderWallForce(); const double F_coul = m_frictionModel.frictionCoeff * cylinderWallForce; const double v_st = m_frictionModel.breakawayFrictionVelocity * constants::root_2; const double v_coul = m_frictionModel.breakawayFrictionVelocity / 10; const double F_brk = m_frictionModel.breakawayFriction; const double v = std::abs(v_s); const double F_0 = constants::root_2 * constants::e * (F_brk - F_coul); const double F_1 = v / v_st; const double F_2 = std::exp(-F_1 * F_1) * F_1; const double F_3 = F_coul * std::tanh(v / v_coul); const double F_4 = m_frictionModel.viscousFrictionCoefficient * v; return F_0 * F_2 + F_3 + F_4; } void CombustionChamber::updateCycleStates() { double crankAngle = m_engine->getOutputCrankshaft()->getCycleAngle(); if (std::isnan(crankAngle) || std::isinf(crankAngle)) { crankAngle = 0.0; } const int i = (int)std::round((crankAngle / (4 * constants::pi)) * (StateSamples - 1.0)); m_pistonSpeed[i] = std::abs(pistonSpeed()); m_pressure[i] = m_system.pressure(); } void CombustionChamber::apply(atg_scs::SystemState *system) { CylinderBank *bank = m_head->getCylinderBank(); const double area = (bank->getBore() * bank->getBore() / 4.0) * constants::pi; const double v_x = system->v_x[m_piston->m_body.index]; const double v_y = system->v_y[m_piston->m_body.index]; const double v_s = v_x * bank->getDx() + v_y * bank->getDy(); const double pressureDifferential = m_system.pressure() - m_crankcasePressure; const double force = -area * pressureDifferential; if (std::isnan(force) || std::isinf(force)) { assert(false); } constexpr double limit = 1E-3; const double abs_v_s = std::fmin(std::abs(v_s), limit); const double attenuation = abs_v_s / limit; const double F = calculateFrictionForce(v_s) * attenuation; const double F_fric = (v_s > 0) ? -F : F; system->applyForce( 0.0, 0.0, (force + F_fric) * bank->getDx(), (force + F_fric) * bank->getDy(), m_piston->m_body.index); } double CombustionChamber::getFrictionForce() const { CylinderBank *bank = m_head->getCylinderBank(); const double v_x = m_piston->m_body.v_x; const double v_y = m_piston->m_body.v_y; const double v_s = v_x * bank->getDx() + v_y * bank->getDy(); return calculateFrictionForce(v_s); } ================================================ FILE: src/combustion_chamber_object.cpp ================================================ #include "../include/combustion_chamber_object.h" #include "../include/cylinder_bank.h" #include "../include/engine_sim_application.h" #include "../include/constants.h" CombustionChamberObject::CombustionChamberObject() { m_chamber = nullptr; } CombustionChamberObject::~CombustionChamberObject() { /* void */ } void CombustionChamberObject::generateGeometry() { GeometryGenerator *gen = m_app->getGeometryGenerator(); CylinderHead *head = m_chamber->getCylinderHead(); CylinderBank *bank = head->getCylinderBank(); const float lineWidth = (float)m_chamber->m_flameEvent.travel_x * 2; double flameTop_x, flameTop_y; double flameBottom_x, flameBottom_y; double chamberHeight = head->getCombustionChamberVolume() / bank->boreSurfaceArea(); bank->getPositionAboveDeck(chamberHeight, &flameTop_x, &flameTop_y); bank->getPositionAboveDeck(chamberHeight - m_chamber->m_flameEvent.travel_y, &flameBottom_x, &flameBottom_y); GeometryGenerator::Line2dParameters params; params.lineWidth = lineWidth; gen->startShape(); params.x0 = (float)flameTop_x; params.y0 = (float)flameTop_y; params.x1 = (float)flameBottom_x; params.y1 = (float)flameBottom_y; gen->generateLine2d(params); gen->endShape(&m_indices); } void CombustionChamberObject::render(const ViewParameters *view) { resetShader(); CylinderHead *head = m_chamber->getCylinderHead(); CylinderBank *bank = head->getCylinderBank(); Piston *frontmostPiston = getForemostPiston(bank, view->Layer0); if (m_chamber->getPiston() == frontmostPiston) { if (m_chamber->m_lit) { m_app->getShaders()->SetBaseColor( ysMath::Mul( m_app->getOrange(), ysMath::LoadVector(1.0f, 1.0f, 1.0f, 0.6f))); m_app->drawGenerated(m_indices, 0x35); } } } void CombustionChamberObject::process(float dt) { /* void */ } void CombustionChamberObject::destroy() { /* void */ } ================================================ FILE: src/connecting_rod.cpp ================================================ #include "..\include\connecting_rod.h" #include "../include/connecting_rod.h" #include ConnectingRod::ConnectingRod() { m_centerOfMass = 0.0; m_length = 0.0; m_m = 0.0; m_I = 0.0; m_journal = 0; m_master = nullptr; m_crankshaft = nullptr; m_piston = nullptr; m_slaveThrow = 0; m_rodJournalAngles = nullptr; m_rodJournalCount = 0; } ConnectingRod::~ConnectingRod() { /* void */ } void ConnectingRod::initialize(const Parameters ¶ms) { m_centerOfMass = params.centerOfMass; m_length = params.length; m_m = params.mass; m_I = params.momentOfInertia; m_journal = params.journal; m_crankshaft = params.crankshaft; m_piston = params.piston; m_rodJournalAngles = new double[params.rodJournals]; m_rodJournalCount = params.rodJournals; m_slaveThrow = params.slaveThrow; m_master = params.master; } double ConnectingRod::getBigEndLocal() const { return -(m_length / 2) + m_centerOfMass; } double ConnectingRod::getLittleEndLocal() const { return (m_length / 2) - m_centerOfMass; } void ConnectingRod::setRodJournalAngle(int i, double angle) { m_rodJournalAngles[i] = angle; } void ConnectingRod::getRodJournalPositionLocal(int i, double *x, double *y) { const double journalAngle = getRodJournalAngle(i); const double journal_x_local = std::cos(journalAngle) * m_slaveThrow; const double journal_y_local = std::sin(journalAngle) * m_slaveThrow; *x = journal_x_local; *y = journal_y_local + getBigEndLocal(); } void ConnectingRod::getRodJournalPositionGlobal(int i, double *x, double *y) { double lx, ly; getRodJournalPositionLocal(i, &lx, &ly); const double angle = m_body.theta; const double dx = std::cos(angle); const double dy = std::sin(angle); *x = (dx * lx - dy * ly) + m_body.p_x; *y = (dy * lx + dx * ly) + m_body.p_y; } int ConnectingRod::getLayer() const { if (m_master != nullptr) { return m_master->getLayer(); } else { return getJournal(); } } ================================================ FILE: src/connecting_rod_object.cpp ================================================ #include "../include/connecting_rod_object.h" #include "../include/engine_sim_application.h" #include "../include/units.h" #include "../include/ui_utilities.h" ConnectingRodObject::ConnectingRodObject() { m_connectingRod = nullptr; } ConnectingRodObject::~ConnectingRodObject() { /* void */ } void ConnectingRodObject::generateGeometry() { GeometryGenerator *gen = m_app->getGeometryGenerator(); const int rodJournalCount = m_connectingRod->getRodJournalCount(); GeometryGenerator::Line2dParameters params; params.x0 = params.x1 = 0; params.y0 = (float)(m_connectingRod->getBigEndLocal() + m_connectingRod->getCrankshaft()->getThrow() * 0.6); params.y1 = (float)m_connectingRod->getLittleEndLocal(); params.lineWidth = (float)(m_connectingRod->getCrankshaft()->getThrow() * 0.5); gen->startShape(); gen->generateLine2d(params); if (rodJournalCount > 0) { GeometryGenerator::Circle2dParameters circleParams; circleParams.radius = static_cast(m_connectingRod->getSlaveThrow()) * 1.5f; circleParams.center_x = 0.0f; circleParams.center_y = static_cast(m_connectingRod->getBigEndLocal()); gen->generateCircle2d(circleParams); } gen->endShape(&m_connectingRodBody); if (rodJournalCount > 0) { gen->startShape(); GeometryGenerator::Circle2dParameters circleParams; circleParams.radius = static_cast(m_connectingRod->getCrankshaft()->getThrow()) * 0.2f; for (int i = 0; i < rodJournalCount; ++i) { double x, y; m_connectingRod->getRodJournalPositionLocal(i, &x, &y); circleParams.center_x = static_cast(x); circleParams.center_y = static_cast(y); gen->generateCircle2d(circleParams); } gen->endShape(&m_pins); } } void ConnectingRodObject::render(const ViewParameters *view) { if (m_connectingRod->getRodJournalCount() > 0 && view->Sublayer != 1) return; else if (m_connectingRod->getRodJournalCount() == 0 && view->Sublayer != 0) return; const int layer = m_connectingRod->getLayer(); if (layer > view->Layer1 || layer < view->Layer0) return; const ysVector grey0 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.9333f); const ysVector grey1 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.8667f); const ysVector grey2 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.05f); ysVector color = (m_connectingRod->getPiston()->getCylinderBank()->getIndex() % 2 == 0) ? grey0 : grey1; color = tintByLayer(color, layer - view->Layer0); resetShader(); setTransform( &m_connectingRod->m_body, (float)m_connectingRod->getCrankshaft()->getThrow(), 0.0f, (float)m_connectingRod->getBigEndLocal()); m_app->getShaders()->SetBaseColor(color); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("ConnectingRod"), 0x32 - layer); m_app->getShaders()->SetBaseColor(color); setTransform(&m_connectingRod->m_body); m_app->drawGenerated(m_connectingRodBody, 0x32 - layer); if (m_connectingRod->getRodJournalCount() > 0) { const ysVector shadow = tintByLayer(grey2, layer - view->Layer0); m_app->getShaders()->SetBaseColor(shadow); m_app->drawGenerated(m_pins, 0x32 - layer); } } void ConnectingRodObject::process(float dt) { /* void */ } void ConnectingRodObject::destroy() { /* void */ } ================================================ FILE: src/convolution_filter.cpp ================================================ #include "../include/convolution_filter.h" #include #include ConvolutionFilter::ConvolutionFilter() { m_shiftRegister = nullptr; m_impulseResponse = nullptr; m_shiftOffset = 0; m_sampleCount = 0; } ConvolutionFilter::~ConvolutionFilter() { assert(m_shiftRegister == nullptr); assert(m_impulseResponse == nullptr); } void ConvolutionFilter::initialize(int samples) { m_sampleCount = samples; m_shiftOffset = 0; m_shiftRegister = new float[samples]; m_impulseResponse = new float[samples]; memset(m_shiftRegister, 0, sizeof(float) * samples); memset(m_impulseResponse, 0, sizeof(float) * samples); } void ConvolutionFilter::destroy() { delete[] m_shiftRegister; delete[] m_impulseResponse; m_shiftRegister = nullptr; m_impulseResponse = nullptr; } float ConvolutionFilter::f(float sample) { m_shiftRegister[m_shiftOffset] = sample; float result = 0; for (int i = 0; i < m_sampleCount - m_shiftOffset; ++i) { result += m_impulseResponse[i] * m_shiftRegister[i + m_shiftOffset]; } for (int i = m_sampleCount - m_shiftOffset; i < m_sampleCount; ++i) { result += m_impulseResponse[i] * m_shiftRegister[i - (m_sampleCount - m_shiftOffset)]; } m_shiftOffset = (m_shiftOffset - 1 + m_sampleCount) % m_sampleCount; return result; } ================================================ FILE: src/crankshaft.cpp ================================================ #include "../include/crankshaft.h" #include "../include/constants.h" #include #include Crankshaft::Crankshaft() { m_rodJournalAngles = nullptr; m_rodJournalCount = 0; m_throw = 0.0; m_m = 0.0; m_I = 0.0; m_flywheelMass = 0.0; m_p_x = m_p_y = 0.0; m_tdc = 0.0; m_frictionTorque = 0.0; } Crankshaft::~Crankshaft() { assert(m_rodJournalAngles == nullptr); } void Crankshaft::initialize(const Parameters ¶ms) { m_m = params.mass; m_flywheelMass = params.flywheelMass; m_I = params.momentOfInertia; m_throw = params.crankThrow; m_rodJournalCount = params.rodJournals; m_rodJournalAngles = new double[m_rodJournalCount]; m_p_x = params.pos_x; m_p_y = params.pos_y; m_tdc = params.tdc; m_frictionTorque = params.frictionTorque; } void Crankshaft::destroy() { if (m_rodJournalAngles != nullptr) delete[] m_rodJournalAngles; m_rodJournalAngles = nullptr; } void Crankshaft::getRodJournalPositionLocal(int i, double *x, double *y) { const double theta = m_rodJournalAngles[i]; *x = std::cos(theta) * m_throw; *y = std::sin(theta) * m_throw; } void Crankshaft::getRodJournalPositionGlobal(int i, double *x, double *y) { double lx, ly; getRodJournalPositionLocal(i, &lx, &ly); *x = lx + m_body.p_x; *y = ly + m_body.p_y; } void Crankshaft::resetAngle() { m_body.theta = std::fmod(m_body.theta, 4 * constants::pi); } void Crankshaft::setRodJournalAngle(int i, double angle) { assert(i < m_rodJournalCount && i >= 0); m_rodJournalAngles[i] = angle; } double Crankshaft::getAngle() const { return m_body.theta - m_tdc; } double Crankshaft::getCycleAngle(double offset) { const double wrapped = std::fmod(-getAngle() + offset, 4 * constants::pi); return (wrapped < 0) ? wrapped + 4 * constants::pi : wrapped; } ================================================ FILE: src/crankshaft_object.cpp ================================================ #include "../include/crankshaft_object.h" #include "../include/cylinder_bank.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" CrankshaftObject::CrankshaftObject() { m_crankshaft = nullptr; } CrankshaftObject::~CrankshaftObject() { /* void */ } void CrankshaftObject::generateGeometry() { /* void */ } void CrankshaftObject::render(const ViewParameters *view) { if (view->Sublayer != 2) return; const ysVector grey0 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.7f); const ysVector grey1 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.6f); const ysVector grey2 = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.2f); const int journalCount = m_crankshaft->getRodJournalCount(); for (int i = 0; i < journalCount; ++i) { const int layer = i; if (layer > view->Layer1 || layer < view->Layer0) continue; const ysVector col = tintByLayer(grey0, layer - view->Layer0); resetShader(); setTransform( &m_crankshaft->m_body, (float)m_crankshaft->getThrow(), 0.0f, 0.0f, (float)m_crankshaft->getRodJournalAngle(i)); m_app->getShaders()->SetBaseColor(col); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("Crankshaft"), 0x32 - layer); } setTransform( &m_crankshaft->m_body, (float)m_crankshaft->getThrow(), 0.0f, 0.0f, 0.0f); m_app->getShaders()->SetBaseColor(grey1); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("CrankSnout"), 0x32); m_app->getShaders()->SetBaseColor(grey2); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("CrankSnoutThreads"), 0x32); } void CrankshaftObject::process(float dt) { /* void */ } void CrankshaftObject::destroy() { /* void */ } ================================================ FILE: src/cylinder_bank.cpp ================================================ #include "../include/cylinder_bank.h" #include "../include/constants.h" #include CylinderBank::CylinderBank() { m_angle = 0.0; m_bore = 0.0; m_deckHeight = 0.0; m_cylinderCount = 0; m_displayDepth = 0.4; m_index = -1; m_dx = m_dy = 0; m_x = m_y = 0; } CylinderBank::~CylinderBank() { /* void */ } void CylinderBank::initialize(const Parameters ¶ms) { m_angle = params.angle; m_bore = params.bore; m_deckHeight = params.deckHeight; m_cylinderCount = params.cylinderCount; m_dx = std::cos(m_angle + constants::pi / 2); m_dy = std::sin(m_angle + constants::pi / 2); m_x = params.positionX; m_y = params.positionY; m_displayDepth = params.displayDepth; m_index = params.index; } void CylinderBank::destroy() { /* void */ } void CylinderBank::getPositionAboveDeck(double h, double *x, double *y) const { *x = m_dx * (m_deckHeight + h) + m_x; *y = m_dy * (m_deckHeight + h) + m_y; } double CylinderBank::boreSurfaceArea() const { return constants::pi * m_bore * m_bore / 4.0; } ================================================ FILE: src/cylinder_bank_object.cpp ================================================ #include "../include/cylinder_bank_object.h" #include "../include/engine_sim_application.h" CylinderBankObject::CylinderBankObject() { m_bank = nullptr; m_head = nullptr; } CylinderBankObject::~CylinderBankObject() { /* void */ } void CylinderBankObject::generateGeometry() { const double s = m_bank->getBore() / 2.0; const double boreSurfaceArea = constants::pi * m_bank->getBore() * m_bank->getBore() / 4.0; const double chamberHeight = m_head->getCombustionChamberVolume() / boreSurfaceArea; GeometryGenerator *gen = m_app->getGeometryGenerator(); const float displayDepth = 1.0f - static_cast(m_bank->getDisplayDepth()); const float lineWidth = (float)(m_bank->getBore() * 0.1); const float margin = lineWidth * 0.25f; const float dx = -(float)(m_bank->getDy() * (margin + m_bank->getBore() / 2 + lineWidth / 2)); const float dy = (float)(m_bank->getDx() * (margin + m_bank->getBore() / 2 + lineWidth / 2)); const float top_x = (float)(m_bank->getX() + m_bank->getDx() * (m_bank->getDeckHeight() + chamberHeight)); const float top_y = (float)(m_bank->getY() + m_bank->getDy() * (m_bank->getDeckHeight() + chamberHeight)); const float bottom_x = (float)(m_bank->getX() + m_bank->getDx() * (displayDepth * m_bank->getDeckHeight())); const float bottom_y = (float)(m_bank->getY() + m_bank->getDy() * (displayDepth * m_bank->getDeckHeight())); GeometryGenerator::Line2dParameters params; params.lineWidth = lineWidth; GeometryGenerator::Circle2dParameters circleParams; circleParams.radius = lineWidth / 2.0f; circleParams.maxEdgeLength = m_app->pixelsToUnits(5.0f); gen->startShape(); params.x0 = top_x + dx; params.y0 = top_y + dy; params.x1 = bottom_x + dx; params.y1 = bottom_y + dy; gen->generateLine2d(params); circleParams.center_x = params.x1; circleParams.center_y = params.y1; gen->generateCircle2d(circleParams); params.x0 = top_x - dx; params.y0 = top_y - dy; params.x1 = bottom_x - dx; params.y1 = bottom_y - dy; gen->generateLine2d(params); circleParams.center_x = params.x1; circleParams.center_y = params.y1; gen->generateCircle2d(circleParams); gen->endShape(&m_walls); } void CylinderBankObject::render(const ViewParameters *view) { if (view->Sublayer != 0) return; resetShader(); const ysVector col = ysMath::Add( ysMath::Mul(m_app->getForegroundColor(), ysMath::LoadScalar(0.01f)), ysMath::Mul(m_app->getBackgroundColor(), ysMath::LoadScalar(0.99f)) ); m_app->getShaders()->SetBaseColor(m_app->getPink()); m_app->drawGenerated(m_walls, 0x0); } void CylinderBankObject::process(float dt) { /* void */ } void CylinderBankObject::destroy() { /* void */ } ================================================ FILE: src/cylinder_head.cpp ================================================ #include "../include/cylinder_head.h" #include "../include/cylinder_bank.h" #include "../include/valvetrain.h" #include CylinderHead::CylinderHead() { m_cylinders = nullptr; m_flipDisplay = false; m_bank = nullptr; m_valvetrain = nullptr; m_exhaustPortFlow = nullptr; m_intakePortFlow = nullptr; m_intakeRunnerVolume = 0.0; m_intakeRunnerCrossSectionArea = 0.0; m_exhaustRunnerVolume = 0.0; m_exhaustRunnerCrossSectionArea = 0.0; m_combustionChamberVolume = 0.0; } CylinderHead::~CylinderHead() { /* void */ } void CylinderHead::initialize(const Parameters ¶ms) { m_cylinders = new Cylinder[params.Bank->getCylinderCount()]; m_bank = params.Bank; m_valvetrain = params.Valvetrain; m_exhaustPortFlow = params.ExhaustPortFlow; m_intakePortFlow = params.IntakePortFlow; m_combustionChamberVolume = params.CombustionChamberVolume; m_flipDisplay = params.FlipDisplay; m_intakeRunnerVolume = params.IntakeRunnerVolume; m_intakeRunnerCrossSectionArea = params.IntakeRunnerCrossSectionArea; m_exhaustRunnerVolume = params.ExhaustRunnerVolume; m_exhaustRunnerCrossSectionArea = params.ExhaustRunnerCrossSectionArea; } void CylinderHead::destroy() { if (m_cylinders != nullptr) delete[] m_cylinders; m_cylinders = nullptr; } double CylinderHead::intakeFlowRate(int cylinder) const { return m_intakePortFlow->sampleTriangle( intakeValveLift(cylinder)); } double CylinderHead::exhaustFlowRate(int cylinder) const { return m_exhaustPortFlow->sampleTriangle( exhaustValveLift(cylinder)); } double CylinderHead::intakeValveLift(int cylinder) const { return m_valvetrain->intakeValveLift(cylinder); } double CylinderHead::exhaustValveLift(int cylinder) const { return m_valvetrain->exhaustValveLift(cylinder); } void CylinderHead::setAllExhaustSystems(ExhaustSystem *system) { for (int i = 0; i < m_bank->getCylinderCount(); ++i) { m_cylinders[i].exhaustSystem = system; } } void CylinderHead::setExhaustSystem(int i, ExhaustSystem *system) { m_cylinders[i].exhaustSystem = system; } void CylinderHead::setSoundAttenuation(int i, double soundAttenuation) { m_cylinders[i].soundAttenuation = soundAttenuation; } void CylinderHead::setAllIntakes(Intake *intake) { for (int i = 0; i < m_bank->getCylinderCount(); ++i) { m_cylinders[i].intake = intake; } } void CylinderHead::setIntake(int i, Intake *intake) { m_cylinders[i].intake = intake; } void CylinderHead::setAllHeaderPrimaryLengths(double length) { for (int i = 0; i < m_bank->getCylinderCount(); ++i) { m_cylinders[i].headerPrimaryLength = length; } } void CylinderHead::setHeaderPrimaryLength(int i, double length) { m_cylinders[i].headerPrimaryLength = length; } Camshaft *CylinderHead::getExhaustCamshaft() { return m_valvetrain->getActiveExhaustCamshaft(); } Camshaft *CylinderHead::getIntakeCamshaft() { return m_valvetrain->getActiveIntakeCamshaft(); } ================================================ FILE: src/cylinder_head_object.cpp ================================================ #include "../include/cylinder_head_object.h" #include "../include/cylinder_bank.h" #include "../include/engine_sim_application.h" #include "../include/constants.h" CylinderHeadObject::CylinderHeadObject() { m_head = nullptr; m_engine = nullptr; } CylinderHeadObject::~CylinderHeadObject() { /* void */ } void CylinderHeadObject::generateGeometry() { /* void */ } void CylinderHeadObject::render(const ViewParameters *view) { if (view->Sublayer != 0) return; resetShader(); CylinderBank *bank = m_head->getCylinderBank(); const double s = (float)bank->getBore() / 2.0f; const double boreSurfaceArea = constants::pi * bank->getBore() * bank->getBore() / 4.0; const double chamberHeight = m_head->getCombustionChamberVolume() / boreSurfaceArea; Piston *frontmostPiston = getForemostPiston(bank, view->Layer0); if (frontmostPiston == nullptr) return; const double theta = bank->getAngle(); double x, y; bank->getPositionAboveDeck(chamberHeight, &x, &y); const ysMatrix scale = ysMath::ScaleTransform(ysMath::LoadScalar((float)s)); const ysMatrix rotation = ysMath::RotationTransform( ysMath::Constants::ZAxis, (float)theta); const ysMatrix translation = ysMath::TranslationTransform( ysMath::LoadVector((float)x, (float)y)); const ysMatrix T_headObject = ysMath::MatMult(translation, rotation); const ysMatrix T_head = ysMath::MatMult( T_headObject, scale); const ysVector col = m_app->getPink(); ysMath::Add( ysMath::Mul(m_app->getForegroundColor(), ysMath::LoadScalar(0.01f)), ysMath::Mul(m_app->getBackgroundColor(), ysMath::LoadScalar(0.99f)) ); const ysVector moving = m_app->getForegroundColor(); GeometryGenerator::GeometryIndices valveShadow, valveRoller, valveRollerShadow, valveRollerPin, camCenter; GeometryGenerator *gen = m_app->getGeometryGenerator(); GeometryGenerator::Line2dParameters params; gen->startShape(); params.lineWidth = 0.2f; params.x0 = -0.689352f; params.y0 = 0.085f; params.x1 = -0.88632f; params.y1 = -0.077975f; gen->generateLine2d(params); params.x0 = -(params.x0 + 0.5f) - 0.5f; params.x1 = -(params.x1 + 0.5f) - 0.5f; gen->generateLine2d(params); params.x0 = 0.689352f; params.x1 = 0.88632f; gen->generateLine2d(params); params.x0 = -(params.x0 - 0.5f) + 0.5f; params.x1 = -(params.x1 - 0.5f) + 0.5f; gen->generateLine2d(params); params.lineWidth = 0.0917f + m_app->pixelsToUnits(5.0f) / (float)s; params.x0 = -0.5f; params.y0 = 0.2f; params.x1 = -0.5f; params.y1 = 1.5f; gen->generateLine2d(params); params.x0 = 0.5f; params.x1 = 0.5f; gen->generateLine2d(params); gen->endShape(&valveShadow); constexpr float rollerRadius = (float)units::distance(300.0, units::thou); GeometryGenerator::Circle2dParameters circleParams; circleParams.radius = rollerRadius / (float)s; circleParams.center_x = 0.0f; circleParams.center_y = 1.99f; gen->startShape(); gen->generateCircle2d(circleParams); gen->endShape(&valveRoller); circleParams.radius = (rollerRadius + m_app->pixelsToUnits(5.0f) / 2) / (float)s; gen->startShape(); gen->generateCircle2d(circleParams); gen->endShape(&valveRollerShadow); circleParams.radius = (rollerRadius * 0.25f) / (float)s; gen->startShape(); gen->generateCircle2d(circleParams); gen->endShape(&valveRollerPin); circleParams.radius = (rollerRadius * 0.25f); circleParams.center_x = 0.0f; circleParams.center_y = 0.0f; gen->startShape(); gen->generateCircle2d(circleParams); gen->endShape(&camCenter); m_app->getShaders()->SetObjectTransform(T_head); m_app->getShaders()->SetBaseColor(col); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("CylinderHead"), 0x0); m_app->getShaders()->SetObjectTransform(T_head); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(valveShadow, 0x1); const double intakeValvePosition = (m_head->getFlipDisplay()) ? 0.5f : -0.5f; const int layer = frontmostPiston->getCylinderIndex(); const float intakeLift = (float)m_head->intakeValveLift(layer); const ysMatrix T_intakeValve = ysMath::MatMult( T_head, ysMath::TranslationTransform( ysMath::LoadVector( (float)intakeValvePosition, (float)(-intakeLift / s), 0.0f, 0.0f))); m_app->getShaders()->SetObjectTransform(T_intakeValve); m_app->getShaders()->SetBaseColor(m_app->getBlue()); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("Valve"), 0x33); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(valveRollerShadow, 0x33); m_app->getShaders()->SetBaseColor(m_app->getBlue()); m_app->drawGenerated(valveRoller, 0x33); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(valveRollerPin, 0x33); const double exhaustLift = (float)m_head->exhaustValveLift(layer); const ysMatrix T_exhaustValve = ysMath::MatMult( T_head, ysMath::TranslationTransform( ysMath::LoadVector( (float)(-intakeValvePosition), (float)(-exhaustLift / s), 0.0f, 0.0f))); m_app->getShaders()->SetObjectTransform(T_exhaustValve); m_app->getShaders()->SetBaseColor(m_app->getYellow()); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("Valve"), 0x33); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(valveRollerShadow, 0x33); m_app->getShaders()->SetBaseColor(m_app->getYellow()); m_app->drawGenerated(valveRoller, 0x33); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(valveRollerPin, 0x33); Camshaft *intakeCam = m_head->getIntakeCamshaft(); Camshaft *exhaustCam = m_head->getExhaustCamshaft(); GeometryGenerator::GeometryIndices intake, intakeShadow, exhaust; generateCamshaft(intakeCam, 0.0, rollerRadius, &intake); generateCamshaft(intakeCam, m_app->pixelsToUnits(5.0) / 2, rollerRadius, &intakeShadow); generateCamshaft(exhaustCam, 0.0, rollerRadius, &exhaust); ysMatrix T_exhaustCam = ysMath::MatMult( T_headObject, ysMath::TranslationTransform(ysMath::LoadVector( (float)(-intakeValvePosition * s), m_app->pixelsToUnits(5.0f) / 2 + (float)(1.99 * s + exhaustCam->getBaseRadius() + rollerRadius), 0.0f, 0.0f))); T_exhaustCam = ysMath::MatMult( T_exhaustCam, ysMath::RotationTransform( ysMath::Constants::ZAxis, (float)( exhaustCam->getAngle() + exhaustCam->getLobeCenterline(layer)))); m_app->getShaders()->SetObjectTransform(T_exhaustCam); m_app->getShaders()->SetBaseColor(m_app->getYellow()); m_app->drawGenerated(exhaust); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(camCenter); ysMatrix T_intakeCam = ysMath::MatMult( T_headObject, ysMath::TranslationTransform(ysMath::LoadVector( (float)(intakeValvePosition * s), rollerRadius + m_app->pixelsToUnits(5.0f) / 2 + (float)(1.99 * s + intakeCam->getBaseRadius()), 0.0f, 0.0f))); T_intakeCam = ysMath::MatMult( T_intakeCam, ysMath::RotationTransform( ysMath::Constants::ZAxis, (float)( intakeCam->getAngle() + intakeCam->getLobeCenterline(layer)))); m_app->getShaders()->SetObjectTransform(T_intakeCam); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(intakeShadow); m_app->getShaders()->SetObjectTransform(T_intakeCam); m_app->getShaders()->SetBaseColor(m_app->getBlue()); m_app->drawGenerated(intake); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(camCenter); } void CylinderHeadObject::process(float dt) { /* void */ } void CylinderHeadObject::destroy() { /* void */ } void CylinderHeadObject::generateCamshaft( Camshaft *camshaft, double padding, double rollerRadius, GeometryGenerator::GeometryIndices *indices) { GeometryGenerator::Cam2dParameters params; params.baseRadius = (float)(camshaft->getBaseRadius() + padding); params.center_x = 0.0f; params.center_y = 0.0f; params.rollerRadius = (float)rollerRadius; params.lift = camshaft->getLobeProfile(); params.maxEdgeLength = (float)units::distance(50, units::thou); m_app->getGeometryGenerator()->startShape(); m_app->getGeometryGenerator()->generateCam(params); m_app->getGeometryGenerator()->endShape(indices); } ================================================ FILE: src/cylinder_pressure_gauge.cpp ================================================ #include "../include/cylinder_pressure_gauge.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include CylinderPressureGauge::CylinderPressureGauge() { m_engine = nullptr; } CylinderPressureGauge::~CylinderPressureGauge() { /* void */ } void CylinderPressureGauge::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void CylinderPressureGauge::destroy() { UiElement::destroy(); } void CylinderPressureGauge::update(float dt) { UiElement::update(dt); } void CylinderPressureGauge::render() { drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); const Bounds title = m_bounds.verticalSplit(1.0f, 0.9f); const Bounds body = m_bounds.verticalSplit(0.0f, 0.9f); drawCenteredText("Cyl. Press. [PSI]", title.inset(10.0f), 24.0f); const int banks = m_engine->getCylinderBankCount(); Grid grid; grid.h_cells = banks; grid.v_cells = 1; while (m_gauges.size() < m_engine->getCylinderCount()) { m_gauges.push_back(addElement()); } for (int i = 0; i < m_engine->getCylinderCount(); ++i) { Piston *piston = m_engine->getPiston(i); CombustionChamber *chamber = m_engine->getChamber(i); const int bankIndex = piston->getCylinderBank()->getIndex(); const Bounds &b = grid.get(body, bankIndex, 0); Grid bankGrid = { 1, piston->getCylinderBank()->getCylinderCount() }; const Bounds &b_cyl = bankGrid.get( b, 0, piston->getCylinderBank()->getCylinderCount() - piston->getCylinderIndex() - 1).inset(5.0f); const double value = units::convert(chamber->m_system.pressure(), units::psi); std::stringstream ss; ss << std::lround(value); drawCenteredText(ss.str(), b_cyl.verticalSplit(0.0f, 2 / 6.0f), b_cyl.height() / 6); m_gauges[i]->m_bounds = b_cyl; m_gauges[i]->setLocalPosition({ 0, 0 }); m_gauges[i]->m_min = 0; m_gauges[i]->m_max = 1000; m_gauges[i]->m_minorStep = 20; m_gauges[i]->m_majorStep = 100; m_gauges[i]->m_maxMinorTick = 400; m_gauges[i]->m_thetaMin = (float)constants::pi * 1.2f; m_gauges[i]->m_thetaMax = -(float)constants::pi * 0.2f; m_gauges[i]->m_outerRadius = std::fmin(b_cyl.width(), b_cyl.height()) / 2.0f; m_gauges[i]->m_value = (float)units::convert(chamber->m_system.pressure(), units::psi); m_gauges[i]->m_needleOuterRadius = m_gauges[i]->m_outerRadius * 0.7f; m_gauges[i]->m_needleInnerRadius = -m_gauges[i]->m_outerRadius * 0.1f; m_gauges[i]->m_needleWidth = 2.0; m_gauges[i]->m_gamma = 0.5f; m_gauges[i]->setBandCount(2); m_gauges[i]->setBand({ ysMath::Constants::One, 400, 1000, 3.0f }, 0); m_gauges[i]->setBand({ ysColor::srgbiToLinear(0x77CEE0), 0.0f, 14.6959f, 3.0f, 0.0f }, 1); } UiElement::render(); } ================================================ FILE: src/cylinder_temperature_gauge.cpp ================================================ #include "../include/cylinder_temperature_gauge.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" #include #undef min CylinderTemperatureGauge::CylinderTemperatureGauge() { m_engine = nullptr; m_maxTemperature = 2000.0; m_minTemperature = 200.0; } CylinderTemperatureGauge::~CylinderTemperatureGauge() { /* void */ } void CylinderTemperatureGauge::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void CylinderTemperatureGauge::destroy() { UiElement::destroy(); } void CylinderTemperatureGauge::update(float dt) { UiElement::update(dt); const double decay = dt / (0.0001 + dt); double maxTemperature = m_maxTemperature; double minTemperature = m_minTemperature; for (int i = 0; i < m_engine->getCylinderCount(); ++i) { Piston *piston = m_engine->getPiston(i); CombustionChamber *chamber = m_engine->getChamber(i); const double temperature = chamber->m_system.temperature(); double value = temperature - m_minTemperature; m_maxTemperature = std::fmax(m_maxTemperature, value); m_minTemperature = std::fmin(m_minTemperature, temperature); } m_maxTemperature *= decay; m_minTemperature *= decay; } void CylinderTemperatureGauge::render() { drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); const Bounds title = m_bounds.verticalSplit(1.0f, 0.9f); const Bounds body = m_bounds.verticalSplit(0.0f, 0.9f); drawCenteredText("Cyl. Temp.", title.inset(10.0f), 24.0f); const int banks = m_engine->getCylinderBankCount(); Grid grid; grid.h_cells = banks; grid.v_cells = 1; GeometryGenerator *generator = m_app->getGeometryGenerator(); const ysVector background = m_app->getBackgroundColor(); const ysVector hot = mix(background, m_app->getRed(), 0.1f); const ysVector cold = mix(background, m_app->getBlue(), 0.001f); for (int i = 0; i < m_engine->getCylinderCount(); ++i) { Piston *piston = m_engine->getPiston(i); CombustionChamber *chamber = m_engine->getChamber(i); CylinderBank *bank = piston->getCylinderBank(); const int bankIndex = bank->getIndex(); const Bounds &b = grid.get(body, bankIndex, 0); Grid bankGrid = { 1, bank->getCylinderCount() }; const Bounds &b_cyl = bankGrid.get( b, 0, bank->getCylinderCount() - piston->getCylinderIndex() - 1).inset(5.0f); const double temperature = chamber->m_system.temperature(); double value = temperature - m_minTemperature; const Bounds worldBounds = getRenderBounds(b_cyl); const Point position = worldBounds.getPosition(Bounds::center); GeometryGenerator::Circle2dParameters params; params.center_x = position.x; params.center_y = position.y; params.radius = (worldBounds.height() / 2) * 0.9f; params.maxEdgeLength = pixelsToUnits(5.0f); GeometryGenerator::GeometryIndices indices; generator->startShape(); generator->generateCircle2d(params); generator->endShape(&indices); m_app->getShaders()->SetBaseColor(mix(cold, hot, (float)(value / m_maxTemperature))); m_app->drawGenerated(indices, 0x11, m_app->getShaders()->GetUiFlags()); } UiElement::render(); } ================================================ FILE: src/delay_filter.cpp ================================================ #include "../include/delay_filter.h" ================================================ FILE: src/derivative_filter.cpp ================================================ #include "../include/derivative_filter.h" DerivativeFilter::DerivativeFilter() { m_previous = 0; m_dt = 0; } DerivativeFilter::~DerivativeFilter() { /* void */ } float DerivativeFilter::f(float sample) { const float temp = m_previous; m_previous = sample; return (sample - temp) / m_dt; } ================================================ FILE: src/direct_throttle_linkage.cpp ================================================ #include "../include/direct_throttle_linkage.h" #include "../include/engine.h" #include DirectThrottleLinkage::DirectThrottleLinkage() { m_gamma = 1.0; m_throttlePosition = 1.0; } DirectThrottleLinkage::~DirectThrottleLinkage() { /* void */ } void DirectThrottleLinkage::initialize(const Parameters ¶ms) { m_gamma = params.gamma; } void DirectThrottleLinkage::setSpeedControl(double s) { Throttle::setSpeedControl(s); m_throttlePosition = 1 - std::pow(s, m_gamma); } void DirectThrottleLinkage::update(double dt, Engine *engine) { Throttle::update(dt, engine); engine->setThrottle(m_throttlePosition); } ================================================ FILE: src/dynamometer.cpp ================================================ #include "../include/dynamometer.h" #include "../include/units.h" #include Dynamometer::Dynamometer() : atg_scs::Constraint(1, 1) { m_rotationSpeed = 0.0; m_ks = 10.0; m_kd = 1.0; m_maxTorque = units::torque(10000.0, units::ft_lb); m_enabled = false; m_hold = false; } Dynamometer::~Dynamometer() { /* void */ } void Dynamometer::connectCrankshaft(Crankshaft *crankshaft) { m_bodies[0] = &crankshaft->m_body; } void Dynamometer::calculate(Output *output, atg_scs::SystemState *state) { output->J[0][0] = 0; output->J[0][1] = 0; output->J[0][2] = 1; output->J_dot[0][0] = 0; output->J_dot[0][1] = 0; output->J_dot[0][2] = 0; output->ks[0] = m_ks; output->kd[0] = m_kd; output->C[0] = 0; if (m_bodies[0]->v_theta < 0) { output->v_bias[0] = m_rotationSpeed; output->limits[0][0] = (m_hold && m_enabled) ? -m_maxTorque : 0.0; output->limits[0][1] = m_enabled ? m_maxTorque : 0.0; } else { output->v_bias[0] = -m_rotationSpeed; output->limits[0][0] = m_enabled ? -m_maxTorque : 0.0; output->limits[0][1] = (m_hold && m_enabled) ? m_maxTorque : 0.0; } } double Dynamometer::getTorque() const { return (m_bodies[0]->v_theta > 0) ? -atg_scs::Constraint::F_t[0][0] : atg_scs::Constraint::F_t[0][0]; } ================================================ FILE: src/engine.cpp ================================================ #include "..\include\engine.h" #include "../include/engine.h" #include "../include/constants.h" #include "../include/units.h" #include "../include/fuel.h" #include "../include/piston_engine_simulator.h" #include #include Engine::Engine() { m_name = ""; m_crankshafts = nullptr; m_cylinderBanks = nullptr; m_heads = nullptr; m_pistons = nullptr; m_connectingRods = nullptr; m_exhaustSystems = nullptr; m_intakes = nullptr; m_combustionChambers = nullptr; m_crankshaftCount = 0; m_cylinderBankCount = 0; m_cylinderCount = 0; m_intakeCount = 0; m_exhaustSystemCount = 0; m_starterSpeed = 0; m_starterTorque = 0; m_dynoMinSpeed = 0; m_dynoMaxSpeed = 0; m_dynoHoldStep = 0; m_redline = 0; m_throttle = nullptr; m_throttleValue = 0.0; m_initialSimulationFrequency = 10000.0; m_initialHighFrequencyGain = 0.01; m_initialJitter = 0.5; m_initialNoise = 1.0; } Engine::~Engine() { assert(m_crankshafts == nullptr); assert(m_cylinderBanks == nullptr); assert(m_pistons == nullptr); assert(m_connectingRods == nullptr); assert(m_heads == nullptr); assert(m_exhaustSystems == nullptr); assert(m_intakes == nullptr); assert(m_combustionChambers == nullptr); } void Engine::initialize(const Parameters ¶ms) { m_crankshaftCount = params.crankshaftCount; m_cylinderCount = params.cylinderCount; m_cylinderBankCount = params.cylinderBanks; m_exhaustSystemCount = params.exhaustSystemCount; m_intakeCount = params.intakeCount; m_starterTorque = params.starterTorque; m_starterSpeed = params.starterSpeed; m_dynoMinSpeed = params.dynoMinSpeed; m_dynoMaxSpeed = params.dynoMaxSpeed; m_dynoHoldStep = params.dynoHoldStep; m_redline = params.redline; m_name = params.name; m_throttle = params.throttle; m_initialHighFrequencyGain = params.initialHighFrequencyGain; m_initialSimulationFrequency = params.initialSimulationFrequency; m_initialJitter = params.initialJitter; m_initialNoise = params.initialNoise; m_crankshafts = new Crankshaft[m_crankshaftCount]; m_cylinderBanks = new CylinderBank[m_cylinderBankCount]; m_heads = new CylinderHead[m_cylinderBankCount]; m_pistons = new Piston[m_cylinderCount]; m_connectingRods = new ConnectingRod[m_cylinderCount]; m_exhaustSystems = new ExhaustSystem[m_exhaustSystemCount]; m_intakes = new Intake[m_intakeCount]; m_combustionChambers = new CombustionChamber[m_cylinderCount]; for (int i = 0; i < m_exhaustSystemCount; ++i) { m_exhaustSystems[i].m_index = i; } for (int i = 0; i < m_cylinderCount; ++i) { m_combustionChambers[i].setEngine(this); } } void Engine::destroy() { for (int i = 0; i < m_crankshaftCount; ++i) { m_crankshafts[i].destroy(); } for (int i = 0; i < m_cylinderCount; ++i) { m_pistons[i].destroy(); m_connectingRods[i].destroy(); m_combustionChambers[i].destroy(); } for (int i = 0; i < m_exhaustSystemCount; ++i) { m_exhaustSystems[i].destroy(); } for (int i = 0; i < m_intakeCount; ++i) { m_intakes[i].destroy(); } m_ignitionModule.destroy(); if (m_throttle != nullptr) delete m_throttle; if (m_crankshafts != nullptr) delete[] m_crankshafts; if (m_cylinderBanks != nullptr) delete[] m_cylinderBanks; if (m_heads != nullptr) delete[] m_heads; if (m_pistons != nullptr) delete[] m_pistons; if (m_connectingRods != nullptr) delete[] m_connectingRods; if (m_exhaustSystems != nullptr) delete[] m_exhaustSystems; if (m_intakes != nullptr) delete[] m_intakes; if (m_combustionChambers != nullptr) delete[] m_combustionChambers; m_crankshafts = nullptr; m_cylinderBanks = nullptr; m_pistons = nullptr; m_connectingRods = nullptr; m_heads = nullptr; m_exhaustSystems = nullptr; m_intakes = nullptr; m_combustionChambers = nullptr; m_throttle = nullptr; } Crankshaft *Engine::getOutputCrankshaft() const { return &m_crankshafts[0]; } void Engine::setSpeedControl(double s) { m_throttle->setSpeedControl(s); } double Engine::getSpeedControl() { return m_throttle->getSpeedControl(); } void Engine::setThrottle(double throttle) { for (int i = 0; i < m_intakeCount; ++i) { m_intakes[i].m_throttle = throttle; } m_throttleValue = throttle; } double Engine::getThrottle() const { return m_throttleValue; } double Engine::getThrottlePlateAngle() const { return (1 - m_intakes[0].getThrottlePlatePosition()) * (constants::pi / 2); } bool placeRod( const ConnectingRod &rod, const CylinderBank &bank, const Crankshaft &crankshaft, double crankshaftAngle, double *p_x, double *p_y, double *theta, double *s) { double p_x_0, p_y_0, l_x, l_y, theta_0; if (rod.getMasterRod() != nullptr) { double s; const bool succeeded = placeRod( *rod.getMasterRod(), *rod.getMasterRod()->getPiston()->getCylinderBank(), *rod.getCrankshaft(), crankshaftAngle, &p_x_0, &p_y_0, &theta_0, &s); if (!succeeded) { return false; } rod.getMasterRod()->getRodJournalPositionLocal(rod.getPiston()->getCylinderIndex(), &l_x, &l_y); } else { theta_0 = crankshaftAngle; p_x_0 = rod.getCrankshaft()->getPosX(); p_y_0 = rod.getCrankshaft()->getPosY(); rod.getCrankshaft()->getRodJournalPositionLocal(rod.getPiston()->getCylinderIndex(), &l_x, &l_y); } const double dx = std::cos(theta_0); const double dy = std::sin(theta_0); *p_x = p_x_0 + (dx * l_x - dy * l_y); *p_y = p_y_0 + (dy * l_x + dx * l_y); // (bank->m_x + bank->m_dx * s - p_x)^2 + (bank->m_y + bank->m_dy * s - p_y)^2 = (rod->m_length)^2 const double a = bank.getDx() * bank.getDx() + bank.getDy() * bank.getDy(); const double b = -2 * bank.getDx() * ((*p_x) - bank.getX()) - 2 * bank.getDy() * ((*p_y) - bank.getY()); const double c = ((*p_x) - bank.getX()) * ((*p_x) - bank.getX()) + ((*p_y) - bank.getY()) * ((*p_y) - bank.getY()) - rod.getLength() * rod.getLength(); const double det = b * b - 4 * a * c; if (det < 0) return false; const double sqrt_det = std::sqrt(det); const double s0 = (-b + sqrt_det) / (2 * a); const double s1 = (-b - sqrt_det) / (2 * a); *s = std::max(s0, s1); if (*s < 0) return false; if (s != nullptr) { const double dx = (bank.getX() + bank.getDx() * (*s)) - (*p_x); const double dy = (bank.getY() + bank.getDy() * (*s)) - (*p_y); *theta = (dy > 0) ? std::acos(dx) : -std::acos(dx); } return true; } void Engine::calculateDisplacement() { // There is a closed-form/correct way to do this which I really // don't feel like deriving right now, so I'm just going with this // numerical approximation. constexpr int Resolution = 1000; double *min_s = new double[m_cylinderCount]; double *max_s = new double[m_cylinderCount]; for (int i = 0; i < m_cylinderCount; ++i) { min_s[i] = DBL_MAX; max_s[i] = -DBL_MAX; } for (int j = 0; j < Resolution; ++j) { const double crankshaftAngle = 2 * (j / static_cast(Resolution)) * constants::pi; for (int i = 0; i < m_cylinderCount; ++i) { const Piston &piston = m_pistons[i]; const CylinderBank &bank = *piston.getCylinderBank(); const ConnectingRod &rod = *piston.getRod(); const Crankshaft &shaft = *rod.getCrankshaft(); double p_x, p_y; double theta; double s; if (!placeRod( rod, bank, shaft, crankshaftAngle, &p_x, &p_y, &theta, &s)) { continue; } min_s[i] = std::min(min_s[i], s); max_s[i] = std::max(max_s[i], s); } } double displacement = 0; for (int i = 0; i < m_cylinderCount; ++i) { const Piston &piston = m_pistons[i]; const CylinderBank &bank = *piston.getCylinderBank(); if (min_s[i] < max_s[i]) { const double r = bank.getBore() / 2.0; displacement += constants::pi * r * r * (max_s[i] - min_s[i]); } } m_displacement = displacement; } double Engine::getIntakeFlowRate() const { double airIntake = 0; for (int i = 0; i < m_intakeCount; ++i) { airIntake += m_intakes[i].m_flowRate; } return airIntake; } void Engine::update(double dt) { m_throttle->update(dt, this); } double Engine::getManifoldPressure() const { double pressureSum = 0.0; for (int i = 0; i < m_intakeCount; ++i) { pressureSum += m_intakes[i].m_system.pressure(); } return pressureSum / m_intakeCount; } double Engine::getIntakeAfr() const { double totalOxygen = 0.0; double totalFuel = 0.0; for (int i = 0; i < m_intakeCount; ++i) { totalOxygen += m_intakes[i].m_system.n_o2(); totalFuel += m_intakes[i].m_system.n_fuel(); } constexpr double octaneMolarMass = units::mass(114.23, units::g); constexpr double oxygenMolarMass = units::mass(31.9988, units::g); if (totalFuel == 0) return 0; else { return (oxygenMolarMass * totalOxygen / 0.21) / (totalFuel * octaneMolarMass); } } double Engine::getExhaustO2() const { double totalInert = 0.0; double totalOxygen = 0.0; double totalFuel = 0.0; for (int i = 0; i < m_exhaustSystemCount; ++i) { totalInert += m_exhaustSystems[i].m_system.n_inert(); totalOxygen += m_exhaustSystems[i].m_system.n_o2(); totalFuel += m_exhaustSystems[i].m_system.n_fuel(); } constexpr double octaneMolarMass = units::mass(114.23, units::g); constexpr double oxygenMolarMass = units::mass(31.9988, units::g); constexpr double nitrogenMolarMass = units::mass(28.014, units::g); if (totalFuel == 0) return 0; else { return (oxygenMolarMass * totalOxygen) / ( totalFuel * octaneMolarMass + nitrogenMolarMass * totalInert + oxygenMolarMass * totalOxygen); } } void Engine::resetFuelConsumption() { for (int i = 0; i < m_intakeCount; ++i) { m_intakes[i].m_totalFuelInjected = 0; } } double Engine::getTotalFuelMassConsumed() const { double n_fuelConsumed = 0; for (int i = 0; i < m_intakeCount; ++i) { n_fuelConsumed += m_intakes[i].m_totalFuelInjected; } return n_fuelConsumed * m_fuel.getMolecularMass(); } double Engine::getTotalVolumeFuelConsumed() const { return getTotalFuelMassConsumed() / m_fuel.getDensity(); } int Engine::getMaxDepth() const { int maxDepth = 0; for (int i = 0; i < m_crankshaftCount; ++i) { maxDepth = std::max(m_crankshafts[i].getRodJournalCount(), maxDepth); } return maxDepth; } Simulator *Engine::createSimulator(Vehicle *vehicle, Transmission *transmission) { PistonEngineSimulator *simulator = new PistonEngineSimulator; Simulator::Parameters simulatorParams; simulatorParams.systemType = Simulator::SystemType::NsvOptimized; simulator->initialize(simulatorParams); simulator->loadSimulation(this, vehicle, transmission); simulator->setFluidSimulationSteps(8); return static_cast(simulator); } double Engine::getRpm() const { if (m_crankshaftCount == 0) return 0; else return std::abs(units::toRpm(getCrankshaft(0)->m_body.v_theta)); } double Engine::getSpeed() const { if (m_crankshaftCount == 0) return 0; else return std::abs(getCrankshaft(0)->m_body.v_theta); } bool Engine::isSpinningCw() const { return getOutputCrankshaft()->m_body.v_theta <= 0; } ================================================ FILE: src/engine_sim_application.cpp ================================================ #include "../include/engine_sim_application.h" #include "../include/piston_object.h" #include "../include/connecting_rod_object.h" #include "../include/constants.h" #include "../include/units.h" #include "../include/crankshaft_object.h" #include "../include/cylinder_bank_object.h" #include "../include/cylinder_head_object.h" #include "../include/ui_button.h" #include "../include/combustion_chamber_object.h" #include "../include/csv_io.h" #include "../include/exhaust_system.h" #include "../include/feedback_comb_filter.h" #include "../include/utilities.h" #include "../scripting/include/compiler.h" #include #include #include #if ATG_ENGINE_SIM_DISCORD_ENABLED #include "../discord/Discord.h" #endif std::string EngineSimApplication::s_buildVersion = "0.1.12a"; EngineSimApplication::EngineSimApplication() { m_assetPath = ""; m_geometryVertexBuffer = nullptr; m_geometryIndexBuffer = nullptr; m_paused = false; m_recording = false; m_screenResolutionIndex = 0; for (int i = 0; i < ScreenResolutionHistoryLength; ++i) { m_screenResolution[i][0] = m_screenResolution[i][1] = 0; } m_background = ysColor::srgbiToLinear(0x0E1012); m_foreground = ysColor::srgbiToLinear(0xFFFFFF); m_shadow = ysColor::srgbiToLinear(0x0E1012); m_highlight1 = ysColor::srgbiToLinear(0xEF4545); m_highlight2 = ysColor::srgbiToLinear(0xFFFFFF); m_pink = ysColor::srgbiToLinear(0xF394BE); m_red = ysColor::srgbiToLinear(0xEE4445); m_orange = ysColor::srgbiToLinear(0xF4802A); m_yellow = ysColor::srgbiToLinear(0xFDBD2E); m_blue = ysColor::srgbiToLinear(0x77CEE0); m_green = ysColor::srgbiToLinear(0xBDD869); m_displayHeight = (float)units::distance(2.0, units::foot); m_outputAudioBuffer = nullptr; m_audioSource = nullptr; m_torque = 0; m_dynoSpeed = 0; m_simulator = nullptr; m_engineView = nullptr; m_rightGaugeCluster = nullptr; m_temperatureGauge = nullptr; m_oscCluster = nullptr; m_performanceCluster = nullptr; m_loadSimulationCluster = nullptr; m_mixerCluster = nullptr; m_infoCluster = nullptr; m_iceEngine = nullptr; m_mainRenderTarget = nullptr; m_vehicle = nullptr; m_transmission = nullptr; m_oscillatorSampleOffset = 0; m_gameWindowHeight = 256; m_screenWidth = 256; m_screenHeight = 256; m_screen = 0; m_viewParameters.Layer0 = 0; m_viewParameters.Layer1 = 0; m_displayAngle = 0.0f; } EngineSimApplication::~EngineSimApplication() { /* void */ } void EngineSimApplication::initialize(void *instance, ysContextObject::DeviceAPI api) { dbasic::Path modulePath = dbasic::GetModulePath(); dbasic::Path confPath = modulePath.Append("delta.conf"); std::string enginePath = "../dependencies/submodules/delta-studio/engines/basic"; m_assetPath = "../assets"; if (confPath.Exists()) { std::fstream confFile(confPath.ToString(), std::ios::in); std::getline(confFile, enginePath); std::getline(confFile, m_assetPath); enginePath = modulePath.Append(enginePath).ToString(); m_assetPath = modulePath.Append(m_assetPath).ToString(); confFile.close(); } m_engine.GetConsole()->SetDefaultFontDirectory(enginePath + "/fonts/"); const std::string shaderPath = enginePath + "/shaders/"; const std::string winTitle = "Engine Sim | AngeTheGreat | v" + s_buildVersion; dbasic::DeltaEngine::GameEngineSettings settings; settings.API = api; settings.DepthBuffer = false; settings.Instance = instance; settings.ShaderDirectory = shaderPath.c_str(); settings.WindowTitle = winTitle.c_str(); settings.WindowPositionX = 0; settings.WindowPositionY = 0; settings.WindowStyle = ysWindow::WindowStyle::Windowed; settings.WindowWidth = 1920; settings.WindowHeight = 1080; m_engine.CreateGameWindow(settings); m_engine.GetDevice()->CreateSubRenderTarget( &m_mainRenderTarget, m_engine.GetScreenRenderTarget(), 0, 0, 0, 0); m_engine.InitializeShaderSet(&m_shaderSet); m_shaders.Initialize( &m_shaderSet, m_mainRenderTarget, m_engine.GetScreenRenderTarget(), m_engine.GetDefaultShaderProgram(), m_engine.GetDefaultInputLayout()); m_engine.InitializeConsoleShaders(&m_shaderSet); m_engine.SetShaderSet(&m_shaderSet); m_shaders.SetClearColor(ysColor::srgbiToLinear(0x34, 0x98, 0xdb)); m_assetManager.SetEngine(&m_engine); m_engine.GetDevice()->CreateIndexBuffer( &m_geometryIndexBuffer, sizeof(unsigned short) * 200000, nullptr); m_engine.GetDevice()->CreateVertexBuffer( &m_geometryVertexBuffer, sizeof(dbasic::Vertex) * 100000, nullptr); m_geometryGenerator.initialize(100000, 200000); initialize(); } void EngineSimApplication::initialize() { m_shaders.SetClearColor(ysColor::srgbiToLinear(0x34, 0x98, 0xdb)); m_assetManager.CompileInterchangeFile((m_assetPath + "/assets").c_str(), 1.0f, true); m_assetManager.LoadSceneFile((m_assetPath + "/assets").c_str(), true); m_textRenderer.SetEngine(&m_engine); m_textRenderer.SetRenderer(m_engine.GetUiRenderer()); m_textRenderer.SetFont(m_engine.GetConsole()->GetFont()); loadScript(); m_audioBuffer.initialize(44100, 44100); m_audioBuffer.m_writePointer = (int)(44100 * 0.1); ysAudioParameters params; params.m_bitsPerSample = 16; params.m_channelCount = 1; params.m_sampleRate = 44100; m_outputAudioBuffer = m_engine.GetAudioDevice()->CreateBuffer(¶ms, 44100); m_audioSource = m_engine.GetAudioDevice()->CreateSource(m_outputAudioBuffer); m_audioSource->SetMode((m_simulator->getEngine() != nullptr) ? ysAudioSource::Mode::Loop : ysAudioSource::Mode::Stop); m_audioSource->SetPan(0.0f); m_audioSource->SetVolume(1.0f); #ifdef ATG_ENGINE_SIM_DISCORD_ENABLED // Create a global instance of discord-rpc CDiscord::CreateInstance(); // Enable it, this needs to be set via a config file of some sort. GetDiscordManager()->SetUseDiscord(true); DiscordRichPresence passMe = { 0 }; std::string engineName = (m_iceEngine != nullptr) ? m_iceEngine->getName() : "Broken Engine"; GetDiscordManager()->SetStatus(passMe, engineName, s_buildVersion); #endif /* ATG_ENGINE_SIM_DISCORD_ENABLED */ } void EngineSimApplication::process(float frame_dt) { frame_dt = static_cast(clamp(frame_dt, 1 / 200.0f, 1 / 30.0f)); double speed = 1.0 / 1.0; if (m_engine.IsKeyDown(ysKey::Code::N1)) { speed = 1 / 10.0; } else if (m_engine.IsKeyDown(ysKey::Code::N2)) { speed = 1 / 100.0; } else if (m_engine.IsKeyDown(ysKey::Code::N3)) { speed = 1 / 200.0; } else if (m_engine.IsKeyDown(ysKey::Code::N4)) { speed = 1 / 500.0; } else if (m_engine.IsKeyDown(ysKey::Code::N5)) { speed = 1 / 1000.0; } if (m_engine.IsKeyDown(ysKey::Code::F1)) { m_displayAngle += frame_dt * 1.0f; } else if (m_engine.IsKeyDown(ysKey::Code::F2)) { m_displayAngle -= frame_dt * 1.0f; } else if (m_engine.ProcessKeyDown(ysKey::Code::F3)) { m_displayAngle = 0.0f; } m_simulator->setSimulationSpeed(speed); const double avgFramerate = clamp(m_engine.GetAverageFramerate(), 30.0f, 1000.0f); m_simulator->startFrame(1 / avgFramerate); auto proc_t0 = std::chrono::steady_clock::now(); const int iterationCount = m_simulator->getFrameIterationCount(); while (m_simulator->simulateStep()) { m_oscCluster->sample(); } auto proc_t1 = std::chrono::steady_clock::now(); m_simulator->endFrame(); auto duration = proc_t1 - proc_t0; if (iterationCount > 0) { m_performanceCluster->addTimePerTimestepSample( (duration.count() / 1E9) / iterationCount); } const SampleOffset safeWritePosition = m_audioSource->GetCurrentWritePosition(); const SampleOffset writePosition = m_audioBuffer.m_writePointer; SampleOffset targetWritePosition = m_audioBuffer.getBufferIndex(safeWritePosition, (int)(44100 * 0.1)); SampleOffset maxWrite = m_audioBuffer.offsetDelta(writePosition, targetWritePosition); SampleOffset currentLead = m_audioBuffer.offsetDelta(safeWritePosition, writePosition); SampleOffset newLead = m_audioBuffer.offsetDelta(safeWritePosition, targetWritePosition); if (currentLead > 44100 * 0.5) { m_audioBuffer.m_writePointer = m_audioBuffer.getBufferIndex(safeWritePosition, (int)(44100 * 0.05)); currentLead = m_audioBuffer.offsetDelta(safeWritePosition, m_audioBuffer.m_writePointer); maxWrite = m_audioBuffer.offsetDelta(m_audioBuffer.m_writePointer, targetWritePosition); } if (currentLead > newLead) { maxWrite = 0; } int16_t *samples = new int16_t[maxWrite]; const int readSamples = m_simulator->readAudioOutput(maxWrite, samples); for (SampleOffset i = 0; i < (SampleOffset)readSamples && i < maxWrite; ++i) { const int16_t sample = samples[i]; if (m_oscillatorSampleOffset % 4 == 0) { m_oscCluster->getAudioWaveformOscilloscope()->addDataPoint( m_oscillatorSampleOffset, sample / (float)(INT16_MAX)); } m_audioBuffer.writeSample(sample, m_audioBuffer.m_writePointer, (int)i); m_oscillatorSampleOffset = (m_oscillatorSampleOffset + 1) % (44100 / 10); } delete[] samples; if (readSamples > 0) { SampleOffset size0, size1; void *data0, *data1; m_audioSource->LockBufferSegment( m_audioBuffer.m_writePointer, readSamples, &data0, &size0, &data1, &size1); m_audioBuffer.copyBuffer( reinterpret_cast(data0), m_audioBuffer.m_writePointer, size0); m_audioBuffer.copyBuffer( reinterpret_cast(data1), m_audioBuffer.getBufferIndex(m_audioBuffer.m_writePointer, size0), size1); m_audioSource->UnlockBufferSegments(data0, size0, data1, size1); m_audioBuffer.commitBlock(readSamples); } m_performanceCluster->addInputBufferUsageSample( (double)m_simulator->getSynthesizerInputLatency() / m_simulator->getSynthesizerInputLatencyTarget()); m_performanceCluster->addAudioLatencySample( m_audioBuffer.offsetDelta(m_audioSource->GetCurrentWritePosition(), m_audioBuffer.m_writePointer) / (44100 * 0.1)); } void EngineSimApplication::render() { for (SimulationObject *object : m_objects) { object->generateGeometry(); } m_viewParameters.Sublayer = 0; for (SimulationObject *object : m_objects) { object->render(&getViewParameters()); } m_viewParameters.Sublayer = 1; for (SimulationObject *object : m_objects) { object->render(&getViewParameters()); } m_viewParameters.Sublayer = 2; for (SimulationObject *object : m_objects) { object->render(&getViewParameters()); } m_uiManager.render(); } float EngineSimApplication::pixelsToUnits(float pixels) const { const float f = m_displayHeight / m_engineView->m_bounds.height(); return pixels * f; } float EngineSimApplication::unitsToPixels(float units) const { const float f = m_engineView->m_bounds.height() / m_displayHeight; return units * f; } void EngineSimApplication::run() { while (true) { m_engine.StartFrame(); if (!m_engine.IsOpen()) break; if (m_engine.ProcessKeyDown(ysKey::Code::Escape)) { break; } if (m_engine.ProcessKeyDown(ysKey::Code::Return)) { m_audioSource->SetMode(ysAudioSource::Mode::Stop); loadScript(); if (m_simulator->getEngine() != nullptr) { m_audioSource->SetMode(ysAudioSource::Mode::Loop); } } if (m_engine.ProcessKeyDown(ysKey::Code::Tab)) { m_screen++; if (m_screen > 2) m_screen = 0; } if (m_engine.ProcessKeyDown(ysKey::Code::F)) { if (m_engine.GetGameWindow()->GetWindowStyle() != ysWindow::WindowStyle::Fullscreen) { m_engine.GetGameWindow()->SetWindowStyle(ysWindow::WindowStyle::Fullscreen); m_infoCluster->setLogMessage("Entered fullscreen mode"); } else { m_engine.GetGameWindow()->SetWindowStyle(ysWindow::WindowStyle::Windowed); m_infoCluster->setLogMessage("Exited fullscreen mode"); } } m_gameWindowHeight = m_engine.GetGameWindow()->GetGameHeight(); m_screenHeight = m_engine.GetGameWindow()->GetScreenHeight(); m_screenWidth = m_engine.GetGameWindow()->GetScreenWidth(); updateScreenSizeStability(); processEngineInput(); if (m_engine.ProcessKeyDown(ysKey::Code::Insert) && m_engine.GetGameWindow()->IsActive()) { if (!isRecording() && readyToRecord()) { startRecording(); } else if (isRecording()) { stopRecording(); } } if (isRecording() && !readyToRecord()) { stopRecording(); } if (!m_paused || m_engine.ProcessKeyDown(ysKey::Code::Right)) { process(m_engine.GetFrameLength()); } m_uiManager.update(m_engine.GetFrameLength()); renderScene(); m_engine.EndFrame(); if (isRecording()) { recordFrame(); } } if (isRecording()) { stopRecording(); } m_simulator->endAudioRenderingThread(); } void EngineSimApplication::destroy() { m_shaderSet.Destroy(); m_engine.GetDevice()->DestroyGPUBuffer(m_geometryVertexBuffer); m_engine.GetDevice()->DestroyGPUBuffer(m_geometryIndexBuffer); m_assetManager.Destroy(); m_engine.Destroy(); m_simulator->destroy(); m_audioBuffer.destroy(); } void EngineSimApplication::loadEngine( Engine *engine, Vehicle *vehicle, Transmission *transmission) { destroyObjects(); if (m_simulator != nullptr) { m_simulator->releaseSimulation(); delete m_simulator; } if (m_vehicle != nullptr) { delete m_vehicle; m_vehicle = nullptr; } if (m_transmission != nullptr) { delete m_transmission; m_transmission = nullptr; } if (m_iceEngine != nullptr) { m_iceEngine->destroy(); delete m_iceEngine; } m_iceEngine = engine; m_vehicle = vehicle; m_transmission = transmission; m_simulator = engine->createSimulator(vehicle, transmission); if (engine == nullptr || vehicle == nullptr || transmission == nullptr) { m_iceEngine = nullptr; m_viewParameters.Layer1 = 0; return; } createObjects(engine); m_viewParameters.Layer1 = engine->getMaxDepth(); engine->calculateDisplacement(); m_simulator->setSimulationFrequency(engine->getSimulationFrequency()); Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters(); audioParams.inputSampleNoise = static_cast(engine->getInitialJitter()); audioParams.airNoise = static_cast(engine->getInitialNoise()); audioParams.dF_F_mix = static_cast(engine->getInitialHighFrequencyGain()); m_simulator->synthesizer().setAudioParameters(audioParams); for (int i = 0; i < engine->getExhaustSystemCount(); ++i) { ImpulseResponse *response = engine->getExhaustSystem(i)->getImpulseResponse(); ysWindowsAudioWaveFile waveFile; waveFile.OpenFile(response->getFilename().c_str()); waveFile.InitializeInternalBuffer(waveFile.GetSampleCount()); waveFile.FillBuffer(0); waveFile.CloseFile(); m_simulator->synthesizer().initializeImpulseResponse( reinterpret_cast(waveFile.GetBuffer()), waveFile.GetSampleCount(), response->getVolume(), i ); waveFile.DestroyInternalBuffer(); } m_simulator->startAudioRenderingThread(); } void EngineSimApplication::drawGenerated( const GeometryGenerator::GeometryIndices &indices, int layer) { drawGenerated(indices, layer, m_shaders.GetRegularFlags()); } void EngineSimApplication::drawGeneratedUi( const GeometryGenerator::GeometryIndices &indices, int layer) { drawGenerated(indices, layer, m_shaders.GetUiFlags()); } void EngineSimApplication::drawGenerated( const GeometryGenerator::GeometryIndices &indices, int layer, dbasic::StageEnableFlags flags) { m_engine.DrawGeneric( flags, m_geometryIndexBuffer, m_geometryVertexBuffer, sizeof(dbasic::Vertex), indices.BaseIndex, indices.BaseVertex, indices.FaceCount, false, layer); } void EngineSimApplication::configure(const ApplicationSettings &settings) { m_applicationSettings = settings; if (settings.startFullscreen) { m_engine.GetGameWindow()->SetWindowStyle(ysWindow::WindowStyle::Fullscreen); } m_background = ysColor::srgbiToLinear(m_applicationSettings.colorBackground); m_foreground = ysColor::srgbiToLinear(m_applicationSettings.colorForeground); m_shadow = ysColor::srgbiToLinear(m_applicationSettings.colorShadow); m_highlight1 = ysColor::srgbiToLinear(m_applicationSettings.colorHighlight1); m_highlight2 = ysColor::srgbiToLinear(m_applicationSettings.colorHighlight2); m_pink = ysColor::srgbiToLinear(m_applicationSettings.colorPink); m_red = ysColor::srgbiToLinear(m_applicationSettings.colorRed); m_orange = ysColor::srgbiToLinear(m_applicationSettings.colorOrange); m_yellow = ysColor::srgbiToLinear(m_applicationSettings.colorYellow); m_blue = ysColor::srgbiToLinear(m_applicationSettings.colorBlue); m_green = ysColor::srgbiToLinear(m_applicationSettings.colorGreen); } void EngineSimApplication::createObjects(Engine *engine) { for (int i = 0; i < engine->getCylinderCount(); ++i) { ConnectingRodObject *rodObject = new ConnectingRodObject; rodObject->initialize(this); rodObject->m_connectingRod = engine->getConnectingRod(i); m_objects.push_back(rodObject); PistonObject *pistonObject = new PistonObject; pistonObject->initialize(this); pistonObject->m_piston = engine->getPiston(i); m_objects.push_back(pistonObject); CombustionChamberObject *ccObject = new CombustionChamberObject; ccObject->initialize(this); ccObject->m_chamber = m_iceEngine->getChamber(i); m_objects.push_back(ccObject); } for (int i = 0; i < engine->getCrankshaftCount(); ++i) { CrankshaftObject *crankshaftObject = new CrankshaftObject; crankshaftObject->initialize(this); crankshaftObject->m_crankshaft = engine->getCrankshaft(i); m_objects.push_back(crankshaftObject); } for (int i = 0; i < engine->getCylinderBankCount(); ++i) { CylinderBankObject *cbObject = new CylinderBankObject; cbObject->initialize(this); cbObject->m_bank = engine->getCylinderBank(i); cbObject->m_head = engine->getHead(i); m_objects.push_back(cbObject); CylinderHeadObject *chObject = new CylinderHeadObject; chObject->initialize(this); chObject->m_head = engine->getHead(i); chObject->m_engine = engine; m_objects.push_back(chObject); } } void EngineSimApplication::destroyObjects() { for (SimulationObject *object : m_objects) { object->destroy(); delete object; } m_objects.clear(); } const SimulationObject::ViewParameters & EngineSimApplication::getViewParameters() const { return m_viewParameters; } void EngineSimApplication::loadScript() { Engine *engine = nullptr; Vehicle *vehicle = nullptr; Transmission *transmission = nullptr; #ifdef ATG_ENGINE_SIM_PIRANHA_ENABLED es_script::Compiler compiler; compiler.initialize(); const bool compiled = compiler.compile("../assets/main.mr"); if (compiled) { const es_script::Compiler::Output output = compiler.execute(); configure(output.applicationSettings); engine = output.engine; vehicle = output.vehicle; transmission = output.transmission; } else { engine = nullptr; vehicle = nullptr; transmission = nullptr; } compiler.destroy(); #endif /* ATG_ENGINE_SIM_PIRANHA_ENABLED */ if (vehicle == nullptr) { Vehicle::Parameters vehParams; vehParams.mass = units::mass(1597, units::kg); vehParams.diffRatio = 3.42; vehParams.tireRadius = units::distance(10, units::inch); vehParams.dragCoefficient = 0.25; vehParams.crossSectionArea = units::distance(6.0, units::foot) * units::distance(6.0, units::foot); vehParams.rollingResistance = 2000.0; vehicle = new Vehicle; vehicle->initialize(vehParams); } if (transmission == nullptr) { const double gearRatios[] = { 2.97, 2.07, 1.43, 1.00, 0.84, 0.56 }; Transmission::Parameters tParams; tParams.GearCount = 6; tParams.GearRatios = gearRatios; tParams.MaxClutchTorque = units::torque(1000.0, units::ft_lb); transmission = new Transmission; transmission->initialize(tParams); } loadEngine(engine, vehicle, transmission); refreshUserInterface(); } void EngineSimApplication::processEngineInput() { if (m_iceEngine == nullptr) { return; } const float dt = m_engine.GetFrameLength(); const bool fineControlMode = m_engine.IsKeyDown(ysKey::Code::Space); const int mouseWheel = m_engine.GetMouseWheel(); const int mouseWheelDelta = mouseWheel - m_lastMouseWheel; m_lastMouseWheel = mouseWheel; bool fineControlInUse = false; if (m_engine.IsKeyDown(ysKey::Code::Z)) { const double rate = fineControlMode ? 0.001 : 0.01; Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters(); audioParams.volume = clamp(audioParams.volume + mouseWheelDelta * rate * dt); m_simulator->synthesizer().setAudioParameters(audioParams); fineControlInUse = true; m_infoCluster->setLogMessage("[Z] - Set volume to " + std::to_string(audioParams.volume)); } else if (m_engine.IsKeyDown(ysKey::Code::X)) { const double rate = fineControlMode ? 0.001 : 0.01; Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters(); audioParams.convolution = clamp(audioParams.convolution + mouseWheelDelta * rate * dt); m_simulator->synthesizer().setAudioParameters(audioParams); fineControlInUse = true; m_infoCluster->setLogMessage("[X] - Set convolution level to " + std::to_string(audioParams.convolution)); } else if (m_engine.IsKeyDown(ysKey::Code::C)) { const double rate = fineControlMode ? 0.00001 : 0.001; Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters(); audioParams.dF_F_mix = clamp(audioParams.dF_F_mix + mouseWheelDelta * rate * dt); m_simulator->synthesizer().setAudioParameters(audioParams); fineControlInUse = true; m_infoCluster->setLogMessage("[C] - Set high freq. gain to " + std::to_string(audioParams.dF_F_mix)); } else if (m_engine.IsKeyDown(ysKey::Code::V)) { const double rate = fineControlMode ? 0.001 : 0.01; Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters(); audioParams.airNoise = clamp(audioParams.airNoise + mouseWheelDelta * rate * dt); m_simulator->synthesizer().setAudioParameters(audioParams); fineControlInUse = true; m_infoCluster->setLogMessage("[V] - Set low freq. noise to " + std::to_string(audioParams.airNoise)); } else if (m_engine.IsKeyDown(ysKey::Code::B)) { const double rate = fineControlMode ? 0.001 : 0.01; Synthesizer::AudioParameters audioParams = m_simulator->synthesizer().getAudioParameters(); audioParams.inputSampleNoise = clamp(audioParams.inputSampleNoise + mouseWheelDelta * rate * dt); m_simulator->synthesizer().setAudioParameters(audioParams); fineControlInUse = true; m_infoCluster->setLogMessage("[B] - Set high freq. noise to " + std::to_string(audioParams.inputSampleNoise)); } else if (m_engine.IsKeyDown(ysKey::Code::N)) { const double rate = fineControlMode ? 10.0 : 100.0; const double newSimulationFrequency = clamp( m_simulator->getSimulationFrequency() + mouseWheelDelta * rate * dt, 400.0, 400000.0); m_simulator->setSimulationFrequency(newSimulationFrequency); fineControlInUse = true; m_infoCluster->setLogMessage("[N] - Set simulation freq to " + std::to_string(m_simulator->getSimulationFrequency())); } else if (m_engine.IsKeyDown(ysKey::Code::G) && m_simulator->m_dyno.m_hold) { if (mouseWheelDelta > 0) { m_dynoSpeed += m_iceEngine->getDynoHoldStep(); } else if (mouseWheelDelta < 0) { m_dynoSpeed -= m_iceEngine->getDynoHoldStep(); } m_dynoSpeed = clamp(m_dynoSpeed, m_iceEngine->getDynoMinSpeed(), m_iceEngine->getDynoMaxSpeed()); m_infoCluster->setLogMessage("[G] - Set dyno speed to " + std::to_string(units::toRpm(m_dynoSpeed))); fineControlInUse = true; } const double prevTargetThrottle = m_targetSpeedSetting; m_targetSpeedSetting = fineControlMode ? m_targetSpeedSetting : 0.0; if (m_engine.IsKeyDown(ysKey::Code::Q)) { m_targetSpeedSetting = 0.01; } else if (m_engine.IsKeyDown(ysKey::Code::W)) { m_targetSpeedSetting = 0.1; } else if (m_engine.IsKeyDown(ysKey::Code::E)) { m_targetSpeedSetting = 0.2; } else if (m_engine.IsKeyDown(ysKey::Code::R)) { m_targetSpeedSetting = 1.0; } else if (fineControlMode && !fineControlInUse) { m_targetSpeedSetting = clamp(m_targetSpeedSetting + mouseWheelDelta * 0.0001); } if (prevTargetThrottle != m_targetSpeedSetting) { m_infoCluster->setLogMessage("Speed control set to " + std::to_string(m_targetSpeedSetting)); } m_speedSetting = m_targetSpeedSetting * 0.5 + 0.5 * m_speedSetting; m_iceEngine->setSpeedControl(m_speedSetting); if (m_engine.ProcessKeyDown(ysKey::Code::M)) { const int currentLayer = getViewParameters().Layer0; if (currentLayer + 1 < m_iceEngine->getMaxDepth()) { setViewLayer(currentLayer + 1); } m_infoCluster->setLogMessage("[M] - Set render layer to " + std::to_string(getViewParameters().Layer0)); } if (m_engine.ProcessKeyDown(ysKey::Code::OEM_Comma)) { if (getViewParameters().Layer0 - 1 >= 0) setViewLayer(getViewParameters().Layer0 - 1); m_infoCluster->setLogMessage("[,] - Set render layer to " + std::to_string(getViewParameters().Layer0)); } if (m_engine.ProcessKeyDown(ysKey::Code::D)) { m_simulator->m_dyno.m_enabled = !m_simulator->m_dyno.m_enabled; const std::string msg = m_simulator->m_dyno.m_enabled ? "DYNOMOMETER ENABLED" : "DYNOMOMETER DISABLED"; m_infoCluster->setLogMessage(msg); } if (m_engine.ProcessKeyDown(ysKey::Code::H)) { m_simulator->m_dyno.m_hold = !m_simulator->m_dyno.m_hold; const std::string msg = m_simulator->m_dyno.m_hold ? m_simulator->m_dyno.m_enabled ? "HOLD ENABLED" : "HOLD ON STANDBY [ENABLE DYNO. FOR HOLD]" : "HOLD DISABLED"; m_infoCluster->setLogMessage(msg); } if (m_simulator->m_dyno.m_enabled) { if (!m_simulator->m_dyno.m_hold) { if (m_simulator->getFilteredDynoTorque() > units::torque(1.0, units::ft_lb)) { m_dynoSpeed += units::rpm(500) * dt; } else { m_dynoSpeed *= (1 / (1 + dt)); } if (m_dynoSpeed > m_iceEngine->getRedline()) { m_simulator->m_dyno.m_enabled = false; m_dynoSpeed = units::rpm(0); } } } else { if (!m_simulator->m_dyno.m_hold) { m_dynoSpeed = units::rpm(0); } } m_dynoSpeed = clamp(m_dynoSpeed, m_iceEngine->getDynoMinSpeed(), m_iceEngine->getDynoMaxSpeed()); m_simulator->m_dyno.m_rotationSpeed = m_dynoSpeed; const bool prevStarterEnabled = m_simulator->m_starterMotor.m_enabled; if (m_engine.IsKeyDown(ysKey::Code::S)) { m_simulator->m_starterMotor.m_enabled = true; } else { m_simulator->m_starterMotor.m_enabled = false; } if (prevStarterEnabled != m_simulator->m_starterMotor.m_enabled) { const std::string msg = m_simulator->m_starterMotor.m_enabled ? "STARTER ENABLED" : "STARTER DISABLED"; m_infoCluster->setLogMessage(msg); } if (m_engine.ProcessKeyDown(ysKey::Code::A)) { m_simulator->getEngine()->getIgnitionModule()->m_enabled = !m_simulator->getEngine()->getIgnitionModule()->m_enabled; const std::string msg = m_simulator->getEngine()->getIgnitionModule()->m_enabled ? "IGNITION ENABLED" : "IGNITION DISABLED"; m_infoCluster->setLogMessage(msg); } if (m_engine.ProcessKeyDown(ysKey::Code::Up)) { m_simulator->getTransmission()->changeGear(m_simulator->getTransmission()->getGear() + 1); m_infoCluster->setLogMessage( "UPSHIFTED TO " + std::to_string(m_simulator->getTransmission()->getGear() + 1)); } else if (m_engine.ProcessKeyDown(ysKey::Code::Down)) { m_simulator->getTransmission()->changeGear(m_simulator->getTransmission()->getGear() - 1); if (m_simulator->getTransmission()->getGear() != -1) { m_infoCluster->setLogMessage( "DOWNSHIFTED TO " + std::to_string(m_simulator->getTransmission()->getGear() + 1)); } else { m_infoCluster->setLogMessage("SHIFTED TO NEUTRAL"); } } if (m_engine.IsKeyDown(ysKey::Code::T)) { m_targetClutchPressure -= 0.2 * dt; } else if (m_engine.IsKeyDown(ysKey::Code::U)) { m_targetClutchPressure += 0.2 * dt; } else if (m_engine.IsKeyDown(ysKey::Code::Shift)) { m_targetClutchPressure = 0.0; m_infoCluster->setLogMessage("CLUTCH DEPRESSED"); } else if (!m_engine.IsKeyDown(ysKey::Code::Y)) { m_targetClutchPressure = 1.0; } m_targetClutchPressure = clamp(m_targetClutchPressure); double clutchRC = 0.001; if (m_engine.IsKeyDown(ysKey::Code::Space)) { clutchRC = 1.0; } const double clutch_s = dt / (dt + clutchRC); m_clutchPressure = m_clutchPressure * (1 - clutch_s) + m_targetClutchPressure * clutch_s; m_simulator->getTransmission()->setClutchPressure(m_clutchPressure); } void EngineSimApplication::renderScene() { getShaders()->ResetBaseColor(); getShaders()->SetObjectTransform(ysMath::LoadIdentity()); m_textRenderer.SetColor(ysColor::linearToSrgb(m_foreground)); m_shaders.SetClearColor(ysColor::linearToSrgb(m_shadow)); const int screenWidth = m_engine.GetGameWindow()->GetGameWidth(); const int screenHeight = m_engine.GetGameWindow()->GetGameHeight(); const float aspectRatio = screenWidth / (float)screenHeight; const Point cameraPos = m_engineView->getCameraPosition(); m_shaders.m_cameraPosition = ysMath::LoadVector(cameraPos.x, cameraPos.y); m_shaders.CalculateUiCamera(screenWidth, screenHeight); if (m_screen == 0) { Bounds windowBounds((float)screenWidth, (float)screenHeight, { 0, (float)screenHeight }); Grid grid; grid.v_cells = 2; grid.h_cells = 3; Grid grid3x3; grid3x3.v_cells = 3; grid3x3.h_cells = 3; m_engineView->setDrawFrame(true); m_engineView->setBounds(grid.get(windowBounds, 1, 0, 1, 1)); m_engineView->setLocalPosition({ 0, 0 }); m_rightGaugeCluster->m_bounds = grid.get(windowBounds, 2, 0, 1, 2); m_oscCluster->m_bounds = grid.get(windowBounds, 1, 1); m_performanceCluster->m_bounds = grid3x3.get(windowBounds, 0, 1); m_loadSimulationCluster->m_bounds = grid3x3.get(windowBounds, 0, 2); Grid grid1x3; grid1x3.v_cells = 3; grid1x3.h_cells = 1; m_mixerCluster->m_bounds = grid1x3.get(grid3x3.get(windowBounds, 0, 0), 0, 2); m_infoCluster->m_bounds = grid1x3.get(grid3x3.get(windowBounds, 0, 0), 0, 0, 1, 2); m_engineView->setVisible(true); m_rightGaugeCluster->setVisible(true); m_oscCluster->setVisible(true); m_performanceCluster->setVisible(true); m_loadSimulationCluster->setVisible(true); m_mixerCluster->setVisible(true); m_infoCluster->setVisible(true); m_oscCluster->activate(); } else if (m_screen == 1) { Bounds windowBounds((float)screenWidth, (float)screenHeight, { 0, (float)screenHeight }); m_engineView->setDrawFrame(false); m_engineView->setBounds(windowBounds); m_engineView->setLocalPosition({ 0, 0 }); m_engineView->activate(); m_engineView->setVisible(true); m_rightGaugeCluster->setVisible(false); m_oscCluster->setVisible(false); m_performanceCluster->setVisible(false); m_loadSimulationCluster->setVisible(false); m_mixerCluster->setVisible(false); m_infoCluster->setVisible(false); } else if (m_screen == 2) { Bounds windowBounds((float)screenWidth, (float)screenHeight, { 0, (float)screenHeight }); Grid grid; grid.v_cells = 1; grid.h_cells = 3; m_engineView->setDrawFrame(true); m_engineView->setBounds(grid.get(windowBounds, 0, 0, 2, 1)); m_engineView->setLocalPosition({ 0, 0 }); m_engineView->activate(); m_rightGaugeCluster->m_bounds = grid.get(windowBounds, 2, 0, 1, 1); m_engineView->setVisible(true); m_rightGaugeCluster->setVisible(true); m_oscCluster->setVisible(false); m_performanceCluster->setVisible(false); m_loadSimulationCluster->setVisible(false); m_mixerCluster->setVisible(false); m_infoCluster->setVisible(false); } const float cameraAspectRatio = m_engineView->m_bounds.width() / m_engineView->m_bounds.height(); m_engine.GetDevice()->ResizeRenderTarget( m_mainRenderTarget, m_engineView->m_bounds.width(), m_engineView->m_bounds.height(), m_engineView->m_bounds.width(), m_engineView->m_bounds.height() ); m_engine.GetDevice()->RepositionRenderTarget( m_mainRenderTarget, m_engineView->m_bounds.getPosition(Bounds::tl).x, screenHeight - m_engineView->m_bounds.getPosition(Bounds::tl).y ); m_shaders.CalculateCamera( cameraAspectRatio * m_displayHeight / m_engineView->m_zoom, m_displayHeight / m_engineView->m_zoom, m_engineView->m_bounds, m_screenWidth, m_screenHeight, m_displayAngle); m_geometryGenerator.reset(); render(); m_engine.GetDevice()->EditBufferDataRange( m_geometryVertexBuffer, (char *)m_geometryGenerator.getVertexData(), sizeof(dbasic::Vertex) * m_geometryGenerator.getCurrentVertexCount(), 0); m_engine.GetDevice()->EditBufferDataRange( m_geometryIndexBuffer, (char *)m_geometryGenerator.getIndexData(), sizeof(unsigned short) * m_geometryGenerator.getCurrentIndexCount(), 0); } void EngineSimApplication::refreshUserInterface() { m_uiManager.destroy(); m_uiManager.initialize(this); m_engineView = m_uiManager.getRoot()->addElement(); m_rightGaugeCluster = m_uiManager.getRoot()->addElement(); m_oscCluster = m_uiManager.getRoot()->addElement(); m_performanceCluster = m_uiManager.getRoot()->addElement(); m_loadSimulationCluster = m_uiManager.getRoot()->addElement(); m_mixerCluster = m_uiManager.getRoot()->addElement(); m_infoCluster = m_uiManager.getRoot()->addElement(); m_infoCluster->setEngine(m_iceEngine); m_rightGaugeCluster->m_simulator = m_simulator; m_rightGaugeCluster->setEngine(m_iceEngine); m_oscCluster->setSimulator(m_simulator); if (m_iceEngine != nullptr) { m_oscCluster->setDynoMaxRange(units::toRpm(m_iceEngine->getRedline())); } m_performanceCluster->setSimulator(m_simulator); m_loadSimulationCluster->setSimulator(m_simulator); m_mixerCluster->setSimulator(m_simulator); } void EngineSimApplication::startRecording() { m_recording = true; #ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE atg_dtv::Encoder::VideoSettings settings{}; // Output filename settings.fname = "../workspace/video_capture/engine_sim_video_capture.mp4"; settings.inputWidth = m_engine.GetScreenWidth(); settings.inputHeight = m_engine.GetScreenHeight(); settings.width = settings.inputWidth; settings.height = settings.inputHeight; settings.hardwareEncoding = true; settings.inputAlpha = true; settings.bitRate = 40000000; m_encoder.run(settings, 2); #endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */ } void EngineSimApplication::updateScreenSizeStability() { m_screenResolution[m_screenResolutionIndex][0] = m_engine.GetScreenWidth(); m_screenResolution[m_screenResolutionIndex][1] = m_engine.GetScreenHeight(); m_screenResolutionIndex = (m_screenResolutionIndex + 1) % ScreenResolutionHistoryLength; } bool EngineSimApplication::readyToRecord() { const int w = m_screenResolution[0][0]; const int h = m_screenResolution[0][1]; if (w <= 0 && h <= 0) return false; if ((w % 2) != 0 || (h % 2) != 0) return false; for (int i = 1; i < ScreenResolutionHistoryLength; ++i) { if (m_screenResolution[i][0] != w) return false; if (m_screenResolution[i][1] != h) return false; } return true; } void EngineSimApplication::stopRecording() { m_recording = false; #ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE m_encoder.commit(); m_encoder.stop(); #endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */ } void EngineSimApplication::recordFrame() { #ifdef ATG_ENGINE_SIM_VIDEO_CAPTURE atg_dtv::Frame *frame = m_encoder.newFrame(false); if (frame != nullptr && m_encoder.getError() == atg_dtv::Encoder::Error::None) { m_engine.GetDevice()->ReadRenderTarget(m_engine.GetScreenRenderTarget(), frame->m_rgb); } m_encoder.submitFrame(); #endif /* ATG_ENGINE_SIM_VIDEO_CAPTURE */ } ================================================ FILE: src/engine_view.cpp ================================================ #include "../include/engine_view.h" #include "../include/engine_sim_application.h" EngineView::EngineView() { m_pan = { 0, units::distance(-6, units::inch) }; m_checkMouse = true; m_lastScroll = 0; m_zoom = 1.0f; m_drawFrame = true; } EngineView::~EngineView() { /* void */ } void EngineView::update(float dt) { m_mouseBounds = m_bounds; } void EngineView::render() { if (m_drawFrame) { drawFrame(m_bounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor(), false); } } void EngineView::onMouseDown(const Point &mouseLocal) { UiElement::onMouseDown(mouseLocal); m_dragStart = m_pan; } void EngineView::onDrag(const Point &p0, const Point &mouse0, const Point &mouse) { const Point delta = mouse - mouse0; const Point deltaUnits = { m_app->pixelsToUnits(delta.x), m_app->pixelsToUnits(delta.y) }; m_pan = m_dragStart + deltaUnits; } void EngineView::onMouseScroll(int scroll) { const float f = std::powf(2.0, (float)scroll / 500.0f); const Point prevCenter = getCenter(); m_zoom *= f; const Point newCenter = getCenter(); Point diff = newCenter - prevCenter; m_pan += diff * m_zoom; m_dragStart += diff * m_zoom; } void EngineView::setBounds(const Bounds &bounds) { m_bounds = bounds; } Point EngineView::getCenter() const { return getCameraPosition(); } Point EngineView::getCameraPosition() const { return Point(-m_pan / m_zoom); } ================================================ FILE: src/exhaust_system.cpp ================================================ #include "../include/exhaust_system.h" #include "../include/units.h" ExhaustSystem::ExhaustSystem() { m_primaryFlowRate = 0; m_outletFlowRate = 0; m_collectorCrossSectionArea = 0; m_length = 0; m_primaryTubeLength = 0; m_audioVolume = 0; m_velocityDecay = 0; m_flow = 0; m_index = -1; m_impulseResponse = nullptr; } ExhaustSystem::~ExhaustSystem() { /* void */ } void ExhaustSystem::initialize(const Parameters ¶ms) { const double systemWidth = std::sqrt(params.collectorCrossSectionArea); const double volume = params.collectorCrossSectionArea * params.length; const double systemLength = params.length; m_system.initialize( units::pressure(1.0, units::atm), volume, units::celcius(25.0)); m_system.setGeometry( systemLength, systemWidth, 1.0, 0.0); m_atmosphere.initialize( units::pressure(1.0, units::atm), units::volume(1000.0, units::m3), units::celcius(25.0)); m_atmosphere.setGeometry( units::distance(10.0, units::m), units::distance(10.0, units::m), 1.0, 0.0); m_primaryFlowRate = params.primaryFlowRate; m_audioVolume = params.audioVolume; m_outletFlowRate = params.outletFlowRate; m_collectorCrossSectionArea = params.collectorCrossSectionArea; m_velocityDecay = params.velocityDecay; m_impulseResponse = params.impulseResponse; m_length = params.length; m_primaryTubeLength = params.primaryTubeLength; } void ExhaustSystem::destroy() { /* void */ } void ExhaustSystem::process(double dt) { GasSystem::Mix airMix; airMix.p_fuel = 0; airMix.p_inert = 1.0; airMix.p_o2 = 0.0; m_atmosphere.reset(units::pressure(1.0, units::atm), units::celcius(25.0), airMix); GasSystem::FlowParameters flowParams; flowParams.crossSectionArea_0 = m_collectorCrossSectionArea; flowParams.crossSectionArea_1 = units::area(10, units::m2); flowParams.direction_x = 1.0; flowParams.direction_y = 0.0; flowParams.dt = dt; flowParams.system_0 = &m_atmosphere; flowParams.system_1 = &m_system; flowParams.k_flow = m_outletFlowRate; m_flow = m_system.flow(flowParams); m_system.dissipateExcessVelocity(); m_system.updateVelocity(dt, m_velocityDecay); } ================================================ FILE: src/feedback_comb_filter.cpp ================================================ #include "../include/feedback_comb_filter.h" #include FeedbackCombFilter::FeedbackCombFilter() { M = 0; a_M = 1.0; m_y = nullptr; m_offset = 0; } FeedbackCombFilter::~FeedbackCombFilter() { assert(m_y == nullptr); } void FeedbackCombFilter::initialize(int M) { this->M = M; m_y = new float[M]; m_offset = 0; } float FeedbackCombFilter::f(float sample) { const float y_n_min_M = m_y[m_offset]; const float y_n = sample + a_M * y_n_min_M; m_y[m_offset] = y_n; m_offset = (m_offset + 1) % M; return y_n; } void FeedbackCombFilter::destroy() { delete[] m_y; m_y = nullptr; } ================================================ FILE: src/filter.cpp ================================================ #include "../include/filter.h" Filter::Filter() { /* void */ } Filter::~Filter() { /* void */ } float Filter::f(float sample) { return sample; } void Filter::destroy() { /* void */ } ================================================ FILE: src/firing_order_display.cpp ================================================ #include "../include/firing_order_display.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" #include #undef min FiringOrderDisplay::FiringOrderDisplay() { m_engine = nullptr; m_cylinderCount = 0; m_cylinderLit = nullptr; } FiringOrderDisplay::~FiringOrderDisplay() { /* void */ } void FiringOrderDisplay::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void FiringOrderDisplay::destroy() { UiElement::destroy(); } void FiringOrderDisplay::update(float dt) { UiElement::update(dt); if (m_engine != nullptr) { if (m_engine->getCylinderCount() != m_cylinderCount) { if (m_cylinderLit != nullptr) { delete[] m_cylinderLit; } m_cylinderCount = m_engine->getCylinderCount(); m_cylinderLit = new float[m_cylinderCount]; memset(m_cylinderLit, 0, sizeof(float) * m_cylinderCount); } for (int i = 0; i < m_cylinderCount; ++i) { if (m_engine->getChamber(i)->popLitLastFrame() || m_engine->getChamber(i)->isLit()) { m_cylinderLit[i] = 0.05f + 0.95f * m_cylinderLit[i]; } else { m_cylinderLit[i] *= (dt / (dt + 0.01f)); } } } } void FiringOrderDisplay::render() { drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); const Bounds title = m_bounds.verticalSplit(1.0f, 0.9f); const Bounds body = m_bounds.verticalSplit(0.0f, 0.9f); drawCenteredText("Ignition", title.inset(20.0f), 24.0f); const int banks = (m_engine == nullptr) ? 0 : m_engine->getCylinderBankCount(); Grid grid; grid.h_cells = banks; grid.v_cells = 1; GeometryGenerator *generator = m_app->getGeometryGenerator(); const ysVector background = m_app->getBackgroundColor(); const ysVector hot = mix(background, m_app->getForegroundColor(), 1.0f); const ysVector fixed = mix(background, m_app->getForegroundColor(), 0.02f); const ysVector cold = mix(background, m_app->getForegroundColor(), 0.001f); std::vector orderedBanks; std::map bankToIndex; for (int i = 0; i < banks; ++i) { orderedBanks.push_back(m_engine->getCylinderBank(i)); } std::sort( orderedBanks.begin(), orderedBanks.end(), [](CylinderBank *a, CylinderBank *b) { return a->getAngle() < b->getAngle(); }); for (int i = 0; i < banks; ++i) { bankToIndex[orderedBanks[i]] = i; } if (m_engine != nullptr) { for (int i = 0; i < m_engine->getCylinderCount(); ++i) { Piston *piston = m_engine->getPiston(i); CombustionChamber *chamber = m_engine->getChamber(i); CylinderBank *bank = piston->getCylinderBank(); const int bankIndex = bankToIndex[bank]; const double lit = m_cylinderLit[i]; const Bounds &b = grid.get(body, banks - bankIndex - 1, 0); Grid bankGrid = { 1, bank->getCylinderCount() }; const Bounds &b_cyl = bankGrid.get( b, 0, bank->getCylinderCount() - piston->getCylinderIndex() - 1).inset(5.0f); const double temperature = chamber->m_system.temperature(); const Bounds worldBounds = getRenderBounds(b_cyl); const Point position = worldBounds.getPosition(Bounds::center); const float radius = std::min(worldBounds.height() / 2, worldBounds.width() / 2); GeometryGenerator::Circle2dParameters params; params.center_x = position.x; params.center_y = position.y; params.radius = radius * 0.75f; params.maxEdgeLength = pixelsToUnits(5.0f); GeometryGenerator::Ring2dParameters ringParams; ringParams.center_x = position.x; ringParams.center_y = position.y; ringParams.innerRadius = radius * 0.8f; ringParams.outerRadius = radius * 0.85f; ringParams.maxEdgeLength = pixelsToUnits(5.0f); GeometryGenerator::GeometryIndices ring, light; generator->startShape(); generator->generateRing2d(ringParams); generator->endShape(&ring); generator->startShape(); generator->generateCircle2d(params); generator->endShape(&light); m_app->getShaders()->SetBaseColor(fixed); m_app->drawGenerated(ring, 0x11, m_app->getShaders()->GetUiFlags()); m_app->getShaders()->SetBaseColor(mix(cold, hot, (float)lit)); m_app->drawGenerated(light, 0x11, m_app->getShaders()->GetUiFlags()); } } UiElement::render(); } ================================================ FILE: src/fuel.cpp ================================================ #include "../include/fuel.h" #include "../include/units.h" #include Fuel::Fuel() { m_molecularMass = 0.0; m_energyDensity = 0.0; m_density = 0.0; m_turbulenceToFlameSpeedRatio = nullptr; m_molecularAfr = 0.0; m_maxBurningEfficiency = 0.0; m_maxDilutionEffect = 0.0; m_maxTurbulenceEffect = 0.0; m_burningEfficiencyRandomness = 0.0; m_lowEfficiencyAttenuation = 0.0; } Fuel::~Fuel() { /* void */ } void Fuel::initialize(const Parameters ¶ms) { m_molecularMass = params.molecularMass; m_energyDensity = params.energyDensity; m_density = params.density; m_turbulenceToFlameSpeedRatio = params.turbulenceToFlameSpeedRatio; m_molecularAfr = params.molecularAfr; m_burningEfficiencyRandomness = params.burningEfficiencyRandomness; m_maxBurningEfficiency = params.maxBurningEfficiency; m_maxDilutionEffect = params.maxDilutionEffect; m_maxTurbulenceEffect = params.maxTurbulenceEffect; m_lowEfficiencyAttenuation = params.lowEfficiencyAttenuation; } double Fuel::flameSpeed( double turbulence, double molecularAfr, double T, double P, double firingPressure, double motoringPressure) const { const double S_L = laminarBurningVelocity(molecularAfr, T, P); const double p_adjustment = 1.0; return m_turbulenceToFlameSpeedRatio->sampleTriangle((turbulence / S_L) * p_adjustment) * S_L; } double Fuel::laminarBurningVelocity(double molecularAfr, double T, double P) const { // Assuming fuel is gasoline constexpr double er_m = 1.21; constexpr double B_m = units::distance(30.5, units::cm) / units::sec; constexpr double B_er = -units::distance(54.9, units::cm) / units::sec; const double er = molecularAfr / m_molecularAfr; const double alpha = 2.4 - 0.271 * std::pow(er, 3.51); const double beta = -0.357 + 0.14 * std::pow(er, 2.77); const double S_L_0 = B_m + B_er * (er - er_m) * (er - er_m); const double T_ratio = T / units::kelvin(298); const double P_ratio = P / units::pressure(1.0, units::atm); return S_L_0 * std::pow(T_ratio, alpha) * std::pow(P_ratio, beta); } ================================================ FILE: src/fuel_cluster.cpp ================================================ #include "../include/fuel_cluster.h" #include "../include/engine_sim_application.h" #include #include FuelCluster::FuelCluster() { m_engine = nullptr; m_simulator = nullptr; } FuelCluster::~FuelCluster() { /* void */ } void FuelCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void FuelCluster::destroy() { UiElement::destroy(); } void FuelCluster::update(float dt) { UiElement::update(dt); } void FuelCluster::render() { const Bounds bounds = m_bounds.inset(10.0f); const Bounds title = bounds.verticalSplit(1.0f, 0.9f); const Bounds bodyBounds = bounds.verticalSplit(0.0f, 0.9f); drawCenteredText("FUEL", title.inset(10.0f), 24.0f); Grid grid; grid.h_cells = 1; grid.v_cells = 10; std::stringstream ss; ss << std::setprecision(3) << std::fixed; ss << units::convert(getTotalVolumeFuelConsumed(), units::L); ss << " L"; const Bounds totalFuelLiters = grid.get(bodyBounds, 0, 1, 1, 2); drawText(ss.str(), totalFuelLiters, 32.0f, Bounds::lm); ss = std::stringstream(); ss << std::setprecision(3) << std::fixed; ss << units::convert(getTotalVolumeFuelConsumed(), units::gal); ss << " gal"; const Bounds totalFuelGallons = grid.get(bodyBounds, 0, 3, 1, 1); drawText(ss.str(), totalFuelGallons, 16.0f, Bounds::lm); const double fuelConsumed = getTotalVolumeFuelConsumed(); const double fuelConsumed_gallons = units::convert(fuelConsumed, units::gal); ss = std::stringstream(); ss << std::setprecision(2) << std::fixed; ss << "$" << 4.761 * fuelConsumed_gallons << " USD"; const Bounds costUSD = grid.get(bodyBounds, 0, 4); drawText(ss.str(), costUSD, 16.0f, Bounds::lm); const double travelledDistance = (m_simulator->getVehicle() != nullptr) ? m_simulator->getVehicle()->getTravelledDistance() : 0.0; const double mpg = units::convert(travelledDistance, units::mile) / fuelConsumed_gallons; ss = std::stringstream(); ss << std::setprecision(2) << std::fixed; ss << mpg << " MPG"; const Bounds mpgBounds = grid.get(bodyBounds, 0, 6); drawText(ss.str(), mpgBounds, 16.0f, Bounds::lm); const double lp100km = (travelledDistance != 0) ? units::convert(fuelConsumed, units::L) / (units::convert(travelledDistance, units::km) / 100.0) : 0; ss = std::stringstream(); ss << std::setprecision(2) << std::fixed; ss << ((lp100km > 100.0) ? 100.0 : lp100km) << " L/100 KM"; const Bounds lp100kmBounds = grid.get(bodyBounds, 0, 7); drawText(ss.str(), lp100kmBounds, 12.0f, Bounds::lm); UiElement::render(); } double FuelCluster::getTotalVolumeFuelConsumed() const { return (m_engine != nullptr) ? m_engine->getTotalVolumeFuelConsumed() : 0.0; } ================================================ FILE: src/function.cpp ================================================ #include "../include/function.h" #include #include #include #include GaussianFilter *Function::DefaultGaussianFilter = nullptr; Function::Function() { m_x = m_y = nullptr; m_capacity = 0; m_size = 0; m_filterRadius = 0; m_yMin = m_yMax = 0; m_inputScale = 1.0; m_outputScale = 1.0; if (DefaultGaussianFilter == nullptr) { DefaultGaussianFilter = new GaussianFilter; DefaultGaussianFilter->initialize(1.0, 3.0, 1024); } m_gaussianFilter = nullptr; } Function::~Function() { assert(m_x == nullptr); assert(m_y == nullptr); } void Function::initialize(int size, double filterRadius, GaussianFilter *filter) { resize(size); m_size = 0; m_filterRadius = filterRadius; m_gaussianFilter = (filter != nullptr) ? filter : DefaultGaussianFilter; } void Function::resize(int newCapacity) { double *new_x = new double[newCapacity]; double *new_y = new double[newCapacity]; if (m_size > 0) { memcpy(new_x, m_x, sizeof(double) * m_size); memcpy(new_y, m_y, sizeof(double) * m_size); } delete[] m_x; delete[] m_y; m_x = new_x; m_y = new_y; m_capacity = newCapacity; } void Function::destroy() { delete[] m_x; delete[] m_y; m_x = nullptr; m_y = nullptr; m_capacity = 0; m_size = 0; } void Function::addSample(double x, double y) { if (m_size + 1 > m_capacity) { resize(m_capacity * 2 + 1); } m_yMin = std::fmin(m_yMin, y); m_yMax = std::fmax(m_yMax, y); const int closest = closestSample(x); if (closest == -1) { m_size = 1; m_x[0] = x; m_y[0] = y; return; } const int index = x < m_x[closest] ? closest : closest + 1; ++m_size; const size_t sizeToCopy = (size_t)m_size - index - 1; if (sizeToCopy > 0) { memmove(m_x + index + 1, m_x + index, sizeof(double) * sizeToCopy); memmove(m_y + index + 1, m_y + index, sizeof(double) * sizeToCopy); } m_x[index] = x; m_y[index] = y; } double Function::sampleTriangle(double x) const { x *= m_inputScale; const int closest = closestSample(x); if (m_size == 0) return 0; else if (x >= m_x[m_size - 1]) return m_y[m_size - 1] * m_outputScale; else if (x <= m_x[0]) return m_y[0] * m_outputScale; double sum = 0; double totalWeight = 0; for (int i = closest; i >= 0; --i) { if (m_x[i] > x) continue; if (std::abs(x - m_x[i]) > m_filterRadius) break; const double w = triangle(m_x[i] - x); sum += w * m_y[i]; totalWeight += w; } for (int i = closest; i < m_size; ++i) { if (m_x[i] <= x) continue; if (std::abs(m_x[i] - x) > m_filterRadius) break; const double w = triangle(m_x[i] - x); sum += w * m_y[i]; totalWeight += w; } return (totalWeight != 0) ? sum * m_outputScale / totalWeight : 0; } double Function::sampleGaussian(double x) const { x *= m_inputScale; const int closest = closestSample(x); const double filterRadius = m_filterRadius * m_gaussianFilter->getRadius(); double sum = 0; double totalWeight = 0; if (m_size == 0) return 0; else if (x > m_x[m_size - 1]) { const double w = m_gaussianFilter->evaluate(0); sum += w * m_y[m_size - 1]; totalWeight += w; } else if (x < m_x[0]) { const double w = m_gaussianFilter->evaluate(0); sum += w * m_y[0]; totalWeight += w; } for (int i = closest; i >= 0; --i) { if (std::abs(x - m_x[i]) > filterRadius) break; const double w = m_gaussianFilter->evaluate((m_x[i] - x) / m_filterRadius); sum += w * m_y[i]; totalWeight += w; } for (int i = closest + 1; i < m_size; ++i) { if (std::abs(m_x[i] - x) > filterRadius) break; const double w = m_gaussianFilter->evaluate((m_x[i] - x) / m_filterRadius); sum += w * m_y[i]; totalWeight += w; } return (totalWeight != 0) ? sum * m_outputScale / totalWeight : 0; } bool Function::isOrdered() const { for (int i = 0; i < m_size - 1; ++i) { if (m_x[i] > m_x[i + 1]) return false; } return true; } void Function::getDomain(double *x0, double *x1) { if (m_size == 0) { *x0 = *x1 = 0; } else { *x0 = m_x[0]; *x1 = m_x[m_size - 1]; } } void Function::getRange(double *y0, double *y1) { *y0 = m_yMin; *y1 = m_yMax; } double Function::triangle(double x) const { return (m_filterRadius - std::abs(x)) / m_filterRadius; } int Function::closestSample(double x) const { if (std::isnan(x)) { return 0; } int l = 0; int r = m_size - 1; if (m_size == 0) return -1; else if (x <= m_x[l]) return l; else if (x >= m_x[r]) return r; while (l + 1 < r) { const int m = (l + r) / 2; if (x > m_x[m]) { l = m; } else if (x < m_x[m]) { r = m; } else if (x == m_x[m]) { return m; } } return (x - m_x[l] < m_x[r] - x) ? l : r; } ================================================ FILE: src/gas_system.cpp ================================================ #include "../include/gas_system.h" #include "../include/units.h" #include "../include/utilities.h" #include #include void GasSystem::setGeometry(double width, double height, double dx, double dy) { m_width = width; m_height = height; m_dx = dx; m_dy = dy; } void GasSystem::initialize(double P, double V, double T, const Mix &mix, int degreesOfFreedom) { m_degreesOfFreedom = degreesOfFreedom; m_state.n_mol = P * V / (constants::R * T); m_state.V = V; m_state.E_k = T * (0.5 * degreesOfFreedom * m_state.n_mol * constants::R); m_state.mix = mix; m_state.momentum[0] = m_state.momentum[1] = 0; const double hcr = heatCapacityRatio(); m_chokedFlowLimit = chokedFlowLimit(degreesOfFreedom); m_chokedFlowFactorCached = chokedFlowRate(degreesOfFreedom); } void GasSystem::reset(double P, double T, const Mix &mix) { m_state.n_mol = P * volume() / (constants::R * T); m_state.E_k = T * (0.5 * m_degreesOfFreedom * m_state.n_mol * constants::R); m_state.mix = mix; m_state.momentum[0] = m_state.momentum[1] = 0; } void GasSystem::setVolume(double V) { return changeVolume(V - m_state.V); } void GasSystem::setN(double n) { m_state.E_k = kineticEnergy(n); m_state.n_mol = n; } void GasSystem::changeVolume(double dV) { const double V = this->volume(); const double L = std::pow(V + dV, 1 / 3.0); const double surfaceArea = (L * L); const double dL = -dV / surfaceArea; const double W = dL * pressure() * surfaceArea; m_state.V += dV; m_state.E_k += W; } void GasSystem::changePressure(double dP) { m_state.E_k += dP * volume() * m_degreesOfFreedom * 0.5; } void GasSystem::changeTemperature(double dT) { m_state.E_k += dT * 0.5 * m_degreesOfFreedom * n() * constants::R; } void GasSystem::changeEnergy(double dE) { m_state.E_k += dE; } void GasSystem::changeMix(const Mix &mix) { m_state.mix = mix; } void GasSystem::injectFuel(double n) { const double n_fuel = this->n_fuel() + n; const double p_fuel = n_fuel / this->n(); m_state.mix.p_fuel = p_fuel; } void GasSystem::changeTemperature(double dT, double n) { m_state.E_k += dT * 0.5 * m_degreesOfFreedom * n * constants::R; } double GasSystem::react(double n, const Mix &mix) { const double l_n_fuel = mix.p_fuel * n; const double l_n_o2 = mix.p_o2 * n; const double system_n_fuel = n_fuel(); const double system_n_o2 = n_o2(); const double system_n_inert = n_inert(); const double system_n = this->n(); // Assuming the following reaction: // 25[O2] + 2[C8H16] -> 16[CO2] + 18[H2O] constexpr double ideal_o2_ratio = 25.0 / 2; constexpr double ideal_fuel_ratio = 2.0 / 25; constexpr double output_input_ratio = (16.0 + 18.0) / (25 + 2); const double ideal_fuel_n = ideal_fuel_ratio * l_n_o2; const double ideal_o2_n = ideal_o2_ratio * l_n_fuel; const double a_n_fuel = std::fmin( std::fmin(system_n_fuel, l_n_fuel), ideal_fuel_n); const double a_n_o2 = std::fmin( std::fmin(system_n_o2, l_n_o2), ideal_o2_n); const double reactants_n = a_n_fuel + a_n_o2; const double products_n = output_input_ratio * reactants_n; const double dn = products_n - reactants_n; m_state.n_mol += dn; // Adjust mix const double new_system_n_fuel = system_n_fuel - a_n_fuel; const double new_system_n_o2 = system_n_o2 - a_n_o2; const double new_system_n_inert = system_n_inert + products_n; const double new_system_n = system_n + dn; if (new_system_n != 0) { m_state.mix.p_fuel = new_system_n_fuel / new_system_n; m_state.mix.p_inert = new_system_n_inert / new_system_n; m_state.mix.p_o2 = new_system_n_o2 / new_system_n; } else { m_state.mix.p_fuel = m_state.mix.p_inert = m_state.mix.p_o2 = 0; } return a_n_fuel; } double GasSystem::flowConstant( double targetFlowRate, double P, double pressureDrop, double T, double hcr) { const double T_0 = T; const double p_0 = P, p_T = P - pressureDrop; // p_0 = upstream pressure const double chokedFlowLimit = std::pow((2.0 / (hcr + 1)), hcr / (hcr - 1)); const double p_ratio = p_T / p_0; double flowRate = 0; if (p_ratio <= chokedFlowLimit) { // Choked flow flowRate = std::sqrt(hcr); flowRate *= std::pow(2 / (hcr + 1), (hcr + 1) / (2 * (hcr - 1))); } else { flowRate = (2 * hcr) / (hcr - 1); flowRate *= (1 - std::pow(p_ratio, (hcr - 1) / hcr)); flowRate = std::sqrt(flowRate); flowRate *= std::pow(p_ratio, 1 / hcr); } flowRate *= p_0 / std::sqrt(constants::R * T_0); return targetFlowRate / flowRate; } double GasSystem::k_28inH2O(double flowRateScfm) { return flowConstant( units::flow(flowRateScfm, units::scfm), units::pressure(1.0, units::atm), units::pressure(28.0, units::inH2O), units::celcius(25), heatCapacityRatio(5) ); } double GasSystem::k_carb(double flowRateScfm) { return flowConstant( units::flow(flowRateScfm, units::scfm), units::pressure(1.0, units::atm), units::pressure(1.5, units::inHg), units::celcius(25), heatCapacityRatio(5) ); } double GasSystem::flowRate( double k_flow, double P0, double P1, double T0, double T1, double hcr, double chokedFlowLimit, double chokedFlowRateCached) { if (k_flow == 0) return 0; double direction; double T_0; double p_0, p_T; // p_0 = upstream pressure if (P0 > P1) { direction = 1.0; T_0 = T0; p_0 = P0; p_T = P1; } else { direction = -1.0; T_0 = T1; p_0 = P1; p_T = P0; } const double p_ratio = p_T / p_0; double flowRate = 0; if (p_ratio <= chokedFlowLimit) { // Choked flow flowRate = chokedFlowRateCached; flowRate /= std::sqrt(constants::R * T_0); } else { const double s = std::pow(p_ratio, 1 / hcr); flowRate = (2 * hcr) / (hcr - 1); flowRate *= s * (s - p_ratio); flowRate = std::sqrt(std::fmax(flowRate, 0.0) / (constants::R * T_0)); } flowRate *= direction * p_0; return flowRate * k_flow; } double GasSystem::loseN(double dn, double E_k_per_mol) { m_state.E_k -= E_k_per_mol * dn; m_state.n_mol -= dn; if (m_state.n_mol < 0) { m_state.n_mol = 0; } return dn; } double GasSystem::gainN(double dn, double E_k_per_mol, const Mix &mix) { const double next_n = m_state.n_mol + dn; const double current_n = m_state.n_mol; m_state.E_k += dn * E_k_per_mol; m_state.n_mol = next_n; if (next_n != 0) { m_state.mix.p_fuel = (m_state.mix.p_fuel * current_n + dn * mix.p_fuel) / next_n; m_state.mix.p_inert = (m_state.mix.p_inert * current_n + dn * mix.p_inert) / next_n; m_state.mix.p_o2 = (m_state.mix.p_o2 * current_n + dn * mix.p_o2) / next_n; } else { m_state.mix.p_fuel = m_state.mix.p_inert = m_state.mix.p_o2 = 0; } return -dn; } void GasSystem::dissipateExcessVelocity() { const double v_x = velocity_x(); const double v_y = velocity_y(); const double v_squared = v_x * v_x + v_y * v_y; const double c = this->c(); const double c_squared = c * c; if (c_squared >= v_squared || v_squared == 0) { return; } const double k_squared = c_squared / v_squared; const double k = std::sqrt(k_squared); m_state.momentum[0] *= k; m_state.momentum[1] *= k; m_state.E_k += 0.5 * mass() * (v_squared - c_squared); if (m_state.E_k < 0) m_state.E_k = 0; } void GasSystem::updateVelocity(double dt, double beta) { if (n() == 0) return; const double depth = volume() / (m_width * m_height); double d_momentum_x = 0; double d_momentum_y = 0; const double p0 = dynamicPressure(m_dx, m_dy); const double p1 = dynamicPressure(-m_dx, -m_dy); const double p2 = dynamicPressure(m_dy, m_dx); const double p3 = dynamicPressure(-m_dy, -m_dx); const double p_sa_0 = p0 * (m_height * depth); const double p_sa_1 = p1 * (m_height * depth); const double p_sa_2 = p2 * (m_width * depth); const double p_sa_3 = p3 * (m_width * depth); d_momentum_x += p_sa_0 * m_dx; d_momentum_y += p_sa_0 * m_dy; d_momentum_x -= p_sa_1 * m_dx; d_momentum_y -= p_sa_1 * m_dy; d_momentum_x += p_sa_2 * m_dy; d_momentum_y += p_sa_2 * m_dx; d_momentum_x -= p_sa_3 * m_dy; d_momentum_y -= p_sa_3 * m_dx; const double m = mass(); const double inv_m = 1 / m; const double v0_x = m_state.momentum[0] * inv_m; const double v0_y = m_state.momentum[1] * inv_m; m_state.momentum[0] -= d_momentum_x * dt * beta; m_state.momentum[1] -= d_momentum_y * dt * beta; const double v1_x = m_state.momentum[0] * inv_m; const double v1_y = m_state.momentum[1] * inv_m; m_state.E_k -= 0.5 * m * (v1_x * v1_x - v0_x * v0_x); m_state.E_k -= 0.5 * m * (v1_y * v1_y - v0_y * v0_y); if (m_state.E_k < 0) m_state.E_k = 0; } void GasSystem::dissipateVelocity(double dt, double timeConstant) { if (n() == 0) return; const double invMass = 1.0 / mass(); const double velocity_x = m_state.momentum[0] * invMass; const double velocity_y = m_state.momentum[1] * invMass; const double velocity_squared = velocity_x * velocity_x + velocity_y * velocity_y; const double s = dt / (dt + timeConstant); m_state.momentum[0] = m_state.momentum[0] * (1 - s); m_state.momentum[1] = m_state.momentum[1] * (1 - s); const double newVelocity_x = m_state.momentum[0] * invMass; const double newVelocity_y = m_state.momentum[1] * invMass; const double newVelocity_squared = newVelocity_x * newVelocity_x + newVelocity_y * newVelocity_y; const double dE_k = 0.5 * mass() * (velocity_squared - newVelocity_squared); m_state.E_k += dE_k; } double GasSystem::flow(const FlowParameters ¶ms) { GasSystem *source = nullptr, *sink = nullptr; double sourcePressure = 0, sinkPressure = 0; double dx, dy; double sourceCrossSection = 0, sinkCrossSection = 0; double direction = 0; const double P_0 = params.system_0->pressure() + params.system_0->dynamicPressure(params.direction_x, params.direction_y); const double P_1 = params.system_1->pressure() + params.system_1->dynamicPressure(-params.direction_x, -params.direction_y); if (P_0 > P_1) { dx = params.direction_x; dy = params.direction_y; source = params.system_0; sink = params.system_1; sourcePressure = P_0; sinkPressure = P_1; sourceCrossSection = params.crossSectionArea_0; sinkCrossSection = params.crossSectionArea_1; direction = 1.0; } else { dx = -params.direction_x; dy = -params.direction_y; source = params.system_1; sink = params.system_0; sourcePressure = P_1; sinkPressure = P_0; sourceCrossSection = params.crossSectionArea_1; sinkCrossSection = params.crossSectionArea_0; direction = -1.0; } double flow = params.dt * flowRate( params.k_flow, sourcePressure, sinkPressure, source->temperature(), sink->temperature(), source->heatCapacityRatio(), source->m_chokedFlowLimit, source->m_chokedFlowFactorCached); const double maxFlow = source->pressureEquilibriumMaxFlow(sink); flow = clamp(flow, 0.0, 0.9 * source->n()); const double fraction = flow / source->n(); const double fractionVolume = fraction * source->volume(); const double fractionMass = fraction * source->mass(); const double remainingMass = (1 - fraction) * source->mass(); if (flow != 0) { // - Stage 1 // Fraction flows from source to sink. const double E_k_bulk_src0 = source->bulkKineticEnergy(); const double E_k_bulk_sink0 = sink->bulkKineticEnergy(); const double s0 = source->totalEnergy() + sink->totalEnergy(); const double E_k_per_mol = source->kineticEnergyPerMol(); sink->gainN(flow, E_k_per_mol, source->mix()); source->loseN(flow, E_k_per_mol); const double s1 = source->totalEnergy() + sink->totalEnergy(); const double dp_x = source->m_state.momentum[0] * fraction; const double dp_y = source->m_state.momentum[1] * fraction; source->m_state.momentum[0] -= dp_x; source->m_state.momentum[1] -= dp_y; sink->m_state.momentum[0] += dp_x; sink->m_state.momentum[1] += dp_y; const double E_k_bulk_src1 = source->bulkKineticEnergy(); const double E_k_bulk_sink1 = sink->bulkKineticEnergy(); sink->m_state.E_k -= ((E_k_bulk_src1 + E_k_bulk_sink1) - (E_k_bulk_src0 + E_k_bulk_sink0)); } const double sourceMass = source->mass(); const double invSourceMass = 1 / sourceMass; const double sinkMass = sink->mass(); const double invSinkMass = 1 / sinkMass; const double c_source = source->c(); const double c_sink = sink->c(); const double sourceInitialMomentum_x = source->m_state.momentum[0]; const double sourceInitialMomentum_y = source->m_state.momentum[1]; const double sinkInitialMomentum_x = sink->m_state.momentum[0]; const double sinkInitialMomentum_y = sink->m_state.momentum[1]; // Momentum in fraction if (sinkCrossSection != 0) { const double sinkFractionVelocity = clamp((fractionVolume / sinkCrossSection) / params.dt, 0.0, c_sink); const double sinkFractionVelocity_squared = sinkFractionVelocity * sinkFractionVelocity; const double sinkFractionVelocity_x = sinkFractionVelocity * dx; const double sinkFractionVelocity_y = sinkFractionVelocity * dy; const double sinkFractionMomentum_x = sinkFractionVelocity_x * fractionMass; const double sinkFractionMomentum_y = sinkFractionVelocity_y * fractionMass; sink->m_state.momentum[0] += sinkFractionMomentum_x; sink->m_state.momentum[1] += sinkFractionMomentum_y; } if (sourceCrossSection != 0 && sourceMass != 0) { const double sourceFractionVelocity = clamp((fractionVolume / sourceCrossSection) / params.dt, 0.0, c_source); const double sourceFractionVelocity_squared = sourceFractionVelocity * sourceFractionVelocity; const double sourceFractionVelocity_x = sourceFractionVelocity * dx; const double sourceFractionVelocity_y = sourceFractionVelocity * dy; const double sourceFractionMomentum_x = sourceFractionVelocity_x * fractionMass; const double sourceFractionMomentum_y = sourceFractionVelocity_y * fractionMass; source->m_state.momentum[0] += sourceFractionMomentum_x; source->m_state.momentum[1] += sourceFractionMomentum_y; } if (sourceMass != 0) { // Energy conservation const double sourceVelocity0_x = sourceInitialMomentum_x * invSourceMass; const double sourceVelocity0_y = sourceInitialMomentum_y * invSourceMass; const double sourceVelocity1_x = source->m_state.momentum[0] * invSourceMass; const double sourceVelocity1_y = source->m_state.momentum[1] * invSourceMass; source->m_state.E_k -= 0.5 * sourceMass * (sourceVelocity1_x * sourceVelocity1_x - sourceVelocity0_x * sourceVelocity0_x); source->m_state.E_k -= 0.5 * sourceMass * (sourceVelocity1_y * sourceVelocity1_y - sourceVelocity0_y * sourceVelocity0_y); } if (sinkMass > 0) { const double sinkVelocity0_x = sinkInitialMomentum_x * invSinkMass; const double sinkVelocity0_y = sinkInitialMomentum_y * invSinkMass; const double sinkVelocity1_x = sink->m_state.momentum[0] * invSinkMass; const double sinkVelocity1_y = sink->m_state.momentum[1] * invSinkMass; sink->m_state.E_k -= 0.5 * sinkMass * (sinkVelocity1_x * sinkVelocity1_x - sinkVelocity0_x * sinkVelocity0_x); sink->m_state.E_k -= 0.5 * sinkMass * (sinkVelocity1_y * sinkVelocity1_y - sinkVelocity0_y * sinkVelocity0_y); } if (sink->m_state.E_k < 0) { sink->m_state.E_k = 0; } if (source->m_state.E_k < 0) { source->m_state.E_k = 0; } return flow * direction; } double GasSystem::flow(double k_flow, double dt, double P_env, double T_env, const Mix &mix) { const double maxFlow = pressureEquilibriumMaxFlow(P_env, T_env); double flow = dt * flowRate( k_flow, pressure(), P_env, temperature(), T_env, heatCapacityRatio(), m_chokedFlowLimit, m_chokedFlowFactorCached); if (std::abs(flow) > std::abs(maxFlow)) { flow = maxFlow; } if (flow < 0) { const double bulk_E_k_0 = bulkKineticEnergy(); gainN(-flow, kineticEnergyPerMol(T_env, m_degreesOfFreedom), mix); const double bulk_E_k_1 = bulkKineticEnergy(); m_state.E_k += (bulk_E_k_1 - bulk_E_k_0); } else { const double starting_n = n(); loseN(flow, kineticEnergyPerMol()); m_state.momentum[0] -= (flow / starting_n) * m_state.momentum[0]; m_state.momentum[1] -= (flow / starting_n) * m_state.momentum[1]; } return flow; } double GasSystem::pressureEquilibriumMaxFlow(const GasSystem *b) const { // pressure_a = (kineticEnergy() + n * b->kineticEnergyPerMol()) / (0.5 * degreesOfFreedom * volume()) // pressure_b = (b->kineticEnergy() - n * / (0.5 * b->degreesOfFreedom * b->volume()) // pressure_a = pressure_b // E_a = kineticEnergy() // E_b = b->kineticEnergy() // D_a = E_a / n() // D_b = E_b / b->n() // Q_a = 1 / (0.5 * degreesOfFreedom * volume()) // Q_b = 1 / (0.5 * b->degreesOfFreedom * b->volume()) // pressure_a = Q_a * (E_a + dn * D_b) // pressure_b = Q_b * (E_b - dn * D_b) if (pressure() > b->pressure()) { const double maxFlow = (b->volume() * kineticEnergy() - volume() * b->kineticEnergy()) / (b->volume() * kineticEnergyPerMol() + volume() * kineticEnergyPerMol()); return std::fmax(0.0, std::fmin(maxFlow, n())); } else { const double maxFlow = (b->volume() * kineticEnergy() - volume() * b->kineticEnergy()) / (b->volume() * b->kineticEnergyPerMol() + volume() * b->kineticEnergyPerMol()); return std::fmin(0.0, std::fmax(maxFlow, -b->n())); } } double GasSystem::pressureEquilibriumMaxFlow(double P_env, double T_env) const { if (pressure() > P_env) { return -(P_env * (0.5 * m_degreesOfFreedom * volume()) - kineticEnergy()) / kineticEnergyPerMol(); } else { const double E_k_per_mol_env = 0.5 * T_env * constants::R * m_degreesOfFreedom; return -(P_env * (0.5 * m_degreesOfFreedom * volume()) - kineticEnergy()) / E_k_per_mol_env; } } ================================================ FILE: src/gauge.cpp ================================================ #include "../include/gauge.h" #include "../include/engine_sim_application.h" #include "../include/constants.h" Gauge::Gauge() { m_thetaMin = (float)constants::pi; m_thetaMax = 0.0f; m_min = m_max = 0; m_maxMinorTick = INT_MAX; m_value = 0; m_minorStep = 1; m_majorStep = 10; m_minorTickWidth = 1.0f; m_majorTickWidth = 2.0f; m_minorTickLength = 5.0f; m_majorTickLength = 10.0f; m_outerRadius = 0.0f; m_renderText = false; m_needleInnerRadius = 0.0f; m_needleOuterRadius = 0.0f; m_needleWidth = 1.0f; m_needlePosition = 0.0f; m_needleVelocity = 0.0f; m_needleMaxVelocity = 2.0f; m_needleKs = 1000.0f; m_needleKd = 25.0f; m_gamma = 1.0f; } Gauge::~Gauge() { /* void */ } void Gauge::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void Gauge::destroy() { /* void */ } void Gauge::update(float dt) { const float value = std::fmaxf((float)m_min, std::fmin((float)m_max, (float)m_value)); const float needle_s = std::powf((value - m_min) / std::abs(m_max - m_min), m_gamma); const float F = m_needleKs * (needle_s - m_needlePosition) - m_needleKd * m_needleVelocity; m_needleVelocity = std::fminf( m_needleMaxVelocity, std::fmaxf(m_needleVelocity + F * dt, -m_needleMaxVelocity)); m_needlePosition += m_needleVelocity * dt; m_needlePosition = std::fmax(0.0f, std::fmin(1.0f, m_needlePosition)); } void Gauge::render() { GeometryGenerator *generator = m_app->getGeometryGenerator(); const Point origin = getRenderPoint(m_bounds.getPosition(Bounds::center) + m_center); const float outerRadius = pixelsToUnits(m_outerRadius); const float minorTickWidth = pixelsToUnits(m_minorTickWidth); const float majorTickWidth = pixelsToUnits(m_majorTickWidth); const float minorTickLength = pixelsToUnits(m_minorTickLength); const float majorTickLength = pixelsToUnits(m_majorTickLength); GeometryGenerator::GeometryIndices ticks, needle; GeometryGenerator::Line2dParameters lineParams; generator->startShape(); for (int i = 0; i <= std::abs(m_max - m_min); i += m_minorStep) { const float s = std::powf((float)i / std::abs(m_max - m_min), m_gamma); const float theta = s * m_thetaMax + (1 - s) * m_thetaMin; const float tickLength = (i % m_majorStep) == 0 ? majorTickLength : minorTickLength; const float tickWidth = (i % m_majorStep) == 0 ? majorTickWidth : minorTickWidth; const Point dir(std::cos(theta), std::sin(theta)); const Point inner = dir * (outerRadius - tickLength) + origin; const Point outer = dir * outerRadius + origin; const Point text = dir * (outerRadius - majorTickLength * 2) + origin; lineParams.lineWidth = tickWidth; lineParams.x0 = inner.x; lineParams.x1 = outer.x; lineParams.y0 = inner.y; lineParams.y1 = outer.y; if ((i % m_majorStep) == 0 || (i + m_minorStep) <= m_maxMinorTick) { generator->generateLine2d(lineParams); } if ((i % m_majorStep) == 0 && m_renderText) { drawCenteredText( "n", Bounds(0.0f, 0.0f, unitsToPixels(text - origin) + m_bounds.getPosition(Bounds::center) + m_center, Bounds::center), 12); } } generator->endShape(&ticks); generator->startShape(); const float needleAngle = m_needlePosition * m_thetaMax + (1 - m_needlePosition) * m_thetaMin; const Point needleDir(std::cos(needleAngle), std::sin(needleAngle)); const Point needleOuter = needleDir * pixelsToUnits(m_needleOuterRadius) + origin; const Point needleInner = needleDir * pixelsToUnits(m_needleInnerRadius) + origin; lineParams.lineWidth = pixelsToUnits(m_needleWidth); lineParams.x0 = needleInner.x; lineParams.x1 = needleOuter.x; lineParams.y0 = needleInner.y; lineParams.y1 = needleOuter.y; generator->generateLine2d(lineParams); generator->endShape(&needle); resetShader(); GeometryGenerator::Ring2dParameters ringParams; ringParams.arrowOnEnd = false; ringParams.drawArrow = false; ringParams.center_x = origin.x; ringParams.center_y = origin.y; ringParams.maxEdgeLength = pixelsToUnits(5); for (const Band &band : m_bands) { ringParams.outerRadius = outerRadius + pixelsToUnits(band.radial_offset); ringParams.innerRadius = outerRadius + pixelsToUnits(band.radial_offset - band.width); const float s0 = std::powf((float)(band.start - m_min) / std::abs(m_max - m_min), m_gamma); const float angle0 = s0 * m_thetaMax + (1 - s0) * m_thetaMin; const float s1 = std::powf((float)(band.end - m_min) / std::abs(m_max - m_min), m_gamma); const float angle1 = s1 * m_thetaMax + (1 - s1) * m_thetaMin; ringParams.startAngle = std::fminf(angle0, angle1) + band.shorten_end; ringParams.endAngle = std::fmaxf(angle0, angle1) - band.shorten_start; GeometryGenerator::GeometryIndices bandIndices; generator->startShape(); generator->generateRing2d(ringParams); generator->endShape(&bandIndices); m_app->getShaders()->SetBaseColor(band.color); m_app->drawGenerated(bandIndices, 0x11, m_app->getShaders()->GetUiFlags()); } m_app->getShaders()->SetBaseColor(m_app->getForegroundColor()); m_app->drawGenerated(ticks, 0x11, m_app->getShaders()->GetUiFlags()); m_app->getShaders()->SetBaseColor(m_app->getHightlight1Color()); m_app->drawGenerated(needle, 0x11, m_app->getShaders()->GetUiFlags()); } ================================================ FILE: src/gaussian_filter.cpp ================================================ #include "../include/gaussian_filter.h" #include GaussianFilter::GaussianFilter() { m_cache = nullptr; m_cacheSteps = 0; m_radius = 0.0; m_alpha = 0.0; m_exp_s = 0.0; m_inv_r = 0.0; } GaussianFilter::~GaussianFilter() { if (m_cache != nullptr) delete[] m_cache; } void GaussianFilter::initialize(double alpha, double radius, int cacheSteps) { m_cacheSteps = cacheSteps; m_alpha = alpha; m_radius = radius; m_exp_s = std::exp(-m_alpha * m_radius * m_radius); m_inv_r = 1 / m_radius; generateCache(); } double GaussianFilter::evaluate(double s) const { const int actualSteps = m_cacheSteps - 32; const double s_sample = actualSteps * std::abs(s) * m_inv_r; const double s0 = std::floor(s_sample); const double s1 = std::ceil(s_sample); const double d = s_sample - s0; return (1 - d) * m_cache[(int)s0] + d * m_cache[(int)s1]; } double GaussianFilter::calculate(double s) const { return std::max( 0.0, std::exp(-m_alpha * s * s) - m_exp_s); } void GaussianFilter::generateCache() { const int actualSteps = m_cacheSteps - 32; const double step = 1.0 / actualSteps; m_cache = new double[m_cacheSteps]; for (int i = 0; i <= actualSteps; ++i) { const double s = i * step * m_radius; m_cache[i] = calculate(s); } for (int i = actualSteps + 1; i < m_cacheSteps; ++i) { m_cache[i] = 0.0; } } ================================================ FILE: src/geometry_generator.cpp ================================================ #include "../include/geometry_generator.h" GeometryGenerator::GeometryGenerator() { m_vertexData = nullptr; m_indexData = nullptr; m_state.vertexPointer = 0; m_state.indexPointer = 0; m_indexBufferSize = 0; m_vertexBufferSize = 0; m_state.subshapeVertexPointer = 0; } GeometryGenerator::~GeometryGenerator() { /* void */ } void GeometryGenerator::initialize(int vertexBufferSize, int indexBufferSize) { m_vertexData = new dbasic::Vertex[vertexBufferSize]; m_indexData = new unsigned short[indexBufferSize]; m_vertexBufferSize = vertexBufferSize; m_indexBufferSize = indexBufferSize; } void GeometryGenerator::destroy() { delete[] m_vertexData; delete[] m_indexData; } void GeometryGenerator::reset() { m_state.vertexPointer = 0; m_state.indexPointer = 0; m_state.subshapeVertexPointer = 0; } bool GeometryGenerator::generateFilledCircle( const ysVector &normal, const ysVector ¢er, float radius, float maxEdgeLength) { // edge_length = (sin(theta) * radius) * 2 // theta = arcsin(edge_length / (2 * radius)) const float angle = std::asinf(maxEdgeLength / (2 * radius)); const float steps = ysMath::Constants::TWO_PI / angle; int wholeSteps = (int)std::ceilf(steps); wholeSteps = (wholeSteps < 3) ? 3 : wholeSteps; return generateFilledFanPolygon( normal, findOrthogonal(normal), center, radius, 0.0f, wholeSteps); } bool GeometryGenerator::generateFilledFanPolygon( const ysVector &normal, const ysVector &up, const ysVector ¢er, float radius, float rotation, int segmentCount) { startSubshape(); const int vertexCount = 1 + segmentCount; const int faceCount = segmentCount; const int indexCount = faceCount * 3; if (vertexCount + m_state.vertexPointer > m_vertexBufferSize || indexCount + m_state.indexPointer > m_indexBufferSize) { return false; } // Generate center vertex dbasic::Vertex *centerVertex = writeVertex(); centerVertex->Normal = ysMath::GetVector4(normal); centerVertex->Pos = ysMath::GetVector4(center); centerVertex->TexCoord = ysVector2(0.5f, 0.5f); const float angleStep = ysMath::Constants::TWO_PI / segmentCount; const ysVector right = ysMath::Cross(up, normal); ysMatrix T = ysMath::LoadMatrix( right, up, normal, ysMath::ExtendVector(center) ); T = ysMath::Transpose(T); for (int i = 0; i < segmentCount; ++i) { const float angle0 = angleStep * i + rotation; const float x0 = std::cosf(angle0); const float y0 = std::sinf(angle0); const ysVector pos = ysMath::LoadVector(x0 * radius, y0 * radius, 0.0f, 1.0f); dbasic::Vertex *newVertex = writeVertex(); newVertex->Normal = normal; newVertex->Pos = ysMath::MatMult(T, pos); newVertex->TexCoord = ysVector2(0.5f * x0 + 0.5f, 0.5f * y0 + 0.5f); } for (int i = 0; i < segmentCount; ++i) { writeFace(0, i + 1, 1 + ((i + 1) % segmentCount)); } return true; } bool GeometryGenerator::generateLineRing( const LineRingParameters ¶ms) { // edge_length = (sin(theta) * radius) * 2 // theta = arcsin(edge_length / (2 * radius)) startSubshape(); const float actualStartAngle = params.startAngle - params.taperTail; const float actualEndAngle = params.endAngle + params.taperTail; const float maxOuterRadius = params.radius + (params.patternHeight / 2); const float angle = std::asinf(params.maxEdgeLength / (2 * maxOuterRadius)); const float steps = (actualEndAngle - actualStartAngle) / angle; int segmentCount = (int)std::ceilf(steps); segmentCount = (segmentCount < 3) ? 3 : segmentCount; const int vertexCount = (segmentCount + 1) * 2; const int faceCount = segmentCount * 2; const int indexCount = faceCount * 3; const ysVector up = findOrthogonal(params.normal); if (vertexCount + m_state.vertexPointer > m_vertexBufferSize || indexCount + m_state.indexPointer > m_indexBufferSize) { return false; } // Generate center vertex const float angleStep = (actualEndAngle - actualStartAngle) / segmentCount; const ysVector right = ysMath::Cross(up, params.normal); ysMatrix T = ysMath::LoadMatrix( right, up, params.normal, ysMath::ExtendVector(params.center) ); T = ysMath::Transpose(T); for (int i = 0; i <= segmentCount; ++i) { float angle0 = angleStep * i + actualStartAngle; const float x0 = std::cosf(angle0); const float y0 = std::sinf(angle0); if (angle0 >= actualEndAngle) angle0 = actualEndAngle; else if (angle0 <= actualStartAngle) angle0 = actualStartAngle; float taper = 1.0f; if (params.taperTail != 0) { if (angle0 >= actualStartAngle && angle0 < params.startAngle) { taper = (angle0 - actualStartAngle) / params.taperTail; } else if (angle0 > params.endAngle && angle0 <= actualEndAngle) { taper = 1.0f - (angle0 - params.endAngle) / params.taperTail; } } const float innerRadius = params.radius - (params.patternHeight / 2) * taper; const float outerRadius = params.radius + (params.patternHeight / 2) * taper; const ysVector outerPos = ysMath::LoadVector(x0 * outerRadius, y0 * outerRadius, 0.0f, 1.0f); const ysVector innerPos = ysMath::LoadVector(x0 * innerRadius, y0 * innerRadius, 0.0f, 1.0f); const float s = params.textureOffset + angle0 * params.radius / (params.patternHeight * params.textureWidthHeightRatio); dbasic::Vertex *outerVertex = writeVertex(); outerVertex->Normal = params.normal; outerVertex->Pos = ysMath::MatMult(T, outerPos); outerVertex->TexCoord = ysVector2(s, 1.0f); dbasic::Vertex *innerVertex = writeVertex(); innerVertex->Normal = params.normal; innerVertex->Pos = ysMath::MatMult(T, innerPos); innerVertex->TexCoord = ysVector2(s, 0.0f); } #define OUTER(i) (((i)) * 2) #define INNER(i) (((i)) * 2 + 1) for (int i = 0; i < segmentCount; ++i) { writeFace(INNER(i), OUTER(i + 1), INNER(i + 1)); writeFace(INNER(i), OUTER(i), OUTER(i + 1)); } return true; } bool GeometryGenerator::generateLineRingBalanced( const LineRingParameters ¶ms) { const float midpoint = (params.startAngle + params.endAngle) / 2.0f; const float offset = params.textureOffset - (midpoint * params.radius) / (params.patternHeight * params.textureWidthHeightRatio); LineRingParameters augmentedParams = params; augmentedParams.textureOffset = offset; generateLineRing(augmentedParams); return true; } bool GeometryGenerator::generateLine( const LineParameters ¶ms) { startSubshape(); const ysVector tangent = ysMath::Normalize(ysMath::Sub(params.end, params.start)); const ysVector right = ysMath::Cross(params.normal, tangent); const float length = ysMath::GetScalar(ysMath::Magnitude(ysMath::Sub(params.end, params.start))); ysMatrix T0 = ysMath::LoadMatrix( right, tangent, params.normal, ysMath::ExtendVector(params.start) ); T0 = ysMath::Transpose(T0); ysMatrix T1 = ysMath::LoadMatrix( right, tangent, params.normal, ysMath::ExtendVector(params.end) ); T1 = ysMath::Transpose(T1); const ysVector v0 = { params.patternHeight / 2.0f, 0.0f, 0.0f, 1.0f }; const ysVector v1 = { -params.patternHeight / 2.0f, 0.0f, 0.0f, 1.0f }; const float ds = 1 / (params.patternHeight * params.textureWidthHeightRatio); dbasic::Vertex *vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult(T0, v0); vertex->TexCoord = ysVector2(0.0f, 1.0f); vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult(T0, v1); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult(T1, v0); vertex->TexCoord = ysVector2(ds * length + params.textureOffset, 1.0f); vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult(T1, v1); vertex->TexCoord = ysVector2(ds * length + params.textureOffset, 0.0f); writeFace(0, 1, 2); writeFace(1, 3, 2); if (params.taperTail > 0) { const int steps = 10; const float step = params.taperTail / steps; for (int i = 0; i < steps; ++i) { const float scale = (steps - i - 1) / (float)steps; vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult( T0, ysMath::LoadVector( scale * params.patternHeight / 2.0f, -step * (i + 1), 0.0f, 1.0f)); vertex->TexCoord = ysVector2(-ds * step * (i + 1) + params.textureOffset, 1.0f); vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult( T0, ysMath::LoadVector( -scale * params.patternHeight / 2.0f, -step * (i + 1), 0.0f, 1.0f)); vertex->TexCoord = ysVector2(-ds * step * (i + 1) + params.textureOffset, 0.0f); vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult( T1, ysMath::LoadVector( scale * params.patternHeight / 2.0f, step * (i + 1), 0.0f, 1.0f)); vertex->TexCoord = ysVector2( ds * (length + step * (i + 1)) + params.textureOffset, 1.0f); vertex = writeVertex(); vertex->Normal = params.normal; vertex->Pos = ysMath::MatMult( T1, ysMath::LoadVector( -scale * params.patternHeight / 2.0f, step * (i + 1), 0.0f, 1.0f)); vertex->TexCoord = ysVector2( ds * (length + step * (i + 1)) + params.textureOffset, 0.0f); writeFace((i + 1) * 4, (i + 1) * 4 + 1, i * 4); writeFace((i + 1) * 4 + 1, i * 4 + 1, i * 4); writeFace(i * 4 + 2, i * 4 + 3, (i + 1) * 4 + 2); writeFace(i * 4 + 3, (i + 1) * 4 + 3, (i + 1) * 4 + 2); } } return true; } bool GeometryGenerator::generateLine2d( const Line2dParameters ¶ms) { startSubshape(); const float dx = params.x1 - params.x0; const float dy = params.y1 - params.y0; const float length = std::sqrt(dx * dx + dy * dy); const float dir_x = dx / length; const float dir_y = dy / length; const float perp_x = -dir_y; const float perp_y = dir_x; const int vertexCount = 4; const int indexCount = 6; if (!checkCapacity(vertexCount, indexCount)) { return false; } dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.x0 + perp_x * params.lineWidth / 2, params.y0 + perp_y * params.lineWidth / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.x0 - perp_x * params.lineWidth / 2, params.y0 - perp_y * params.lineWidth / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.x1 + perp_x * params.lineWidth / 2, params.y1 + perp_y * params.lineWidth / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.x1 - perp_x * params.lineWidth / 2, params.y1 - perp_y * params.lineWidth / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); writeFace(0, 1, 2); writeFace(1, 3, 2); return true; } bool GeometryGenerator::generateFrame(const FrameParameters ¶ms) { State prevState = m_state; Line2dParameters lineParams; lineParams.lineWidth = params.lineWidth; lineParams.x0 = params.x - params.frameWidth / 2 - params.lineWidth / 2; lineParams.x1 = params.x + params.frameWidth / 2 + params.lineWidth / 2; lineParams.y0 = lineParams.y1 = params.y + params.frameHeight / 2; if (!generateLine2d(lineParams)) goto fail; lineParams.y0 = lineParams.y1 = params.y - params.frameHeight / 2; if (!generateLine2d(lineParams)) goto fail; lineParams.y0 = params.y - params.frameHeight / 2 - params.lineWidth / 2; lineParams.y1 = params.y + params.frameHeight / 2 + params.lineWidth / 2; lineParams.x0 = lineParams.x1 = params.x + params.frameWidth / 2; if (!generateLine2d(lineParams)) goto fail; lineParams.x0 = lineParams.x1 = params.x - params.frameWidth / 2; if (!generateLine2d(lineParams)) goto fail; return true; fail: m_state = prevState; return false; } bool GeometryGenerator::generateGrid(const GridParameters ¶ms) { const State prevState = m_state; Line2dParameters lineParams; lineParams.lineWidth = params.lineWidth; lineParams.x0 = params.x - params.width / 2; lineParams.x1 = params.x + params.width / 2; for (float offset = 0.0f; offset <= params.height / 2.0f; offset += params.div_y) { lineParams.y0 = lineParams.y1 = params.y + offset; if (!generateLine2d(lineParams)) goto fail; lineParams.y0 = lineParams.y1 = params.y - offset; if (!generateLine2d(lineParams)) goto fail; } lineParams.y0 = params.y - params.height / 2; lineParams.y1 = params.y + params.height / 2; for (float offset = 0.0f; offset <= params.width / 2.0f; offset += params.div_x) { lineParams.x0 = lineParams.x1 = params.x + offset; if (!generateLine2d(lineParams)) goto fail; lineParams.x0 = lineParams.x1 = params.x - offset; if (!generateLine2d(lineParams)) goto fail; } return true; fail: m_state = prevState; return false; } bool GeometryGenerator::generateRing2d(const Ring2dParameters ¶ms) { // edge_length = (sin(theta) * radius) * 2 // theta = arcsin(edge_length / (2 * radius)) startSubshape(); const float angle = std::asinf(params.maxEdgeLength / (2 * params.outerRadius)); const float steps = (params.endAngle - params.startAngle) / angle; int segmentCount = (int)std::ceilf(steps); segmentCount = (segmentCount < 3) ? 3 : segmentCount; const int vertexCount = (segmentCount + 1) * 2; const int faceCount = segmentCount * 2; const int indexCount = faceCount * 3; if (!checkCapacity(vertexCount, indexCount)) { return false; } const float angleStep = (params.endAngle - params.startAngle) / segmentCount; float arrowStart = 0.0f; float arrowEnd = 0.0f; if (params.drawArrow) { if (params.arrowOnEnd) { arrowStart = params.endAngle - params.arrowLength; arrowEnd = params.endAngle; } else { arrowStart = params.startAngle + params.arrowLength; arrowEnd = params.startAngle; } } const float midRadius = (params.outerRadius + params.innerRadius) / 2; const float fullWidth = params.outerRadius - params.innerRadius; for (int i = 0; i <= segmentCount; ++i) { float angle0 = angleStep * i + params.startAngle; const float x0 = std::cosf(angle0); const float y0 = std::sinf(angle0); const float s = (params.drawArrow) ? (angle0 - arrowStart) / (arrowEnd - arrowStart) : 0.0f; const float width = fullWidth * (1 - std::fminf(1.0, std::fmaxf(s, 0.0))); const float innerRadius = midRadius - width / 2; const float outerRadius = midRadius + width / 2; if (angle0 >= params.endAngle) angle0 = params.endAngle; else if (angle0 <= params.startAngle) angle0 = params.startAngle; const ysVector outerPos = ysMath::LoadVector( x0 * outerRadius + params.center_x, y0 * outerRadius + params.center_y, 0.0f, 1.0f); const ysVector innerPos = ysMath::LoadVector( x0 * innerRadius + params.center_x, y0 * innerRadius + params.center_y, 0.0f, 1.0f); dbasic::Vertex *outerVertex = writeVertex(); outerVertex->Normal = ysMath::Constants::ZAxis; outerVertex->Pos = outerPos; outerVertex->TexCoord = ysVector2(0.0f, 0.0f); dbasic::Vertex *innerVertex = writeVertex(); innerVertex->Normal = ysMath::Constants::ZAxis; innerVertex->Pos = innerPos; innerVertex->TexCoord = ysVector2(0.0f, 0.0f); } #define OUTER(i) (((i)) * 2) #define INNER(i) (((i)) * 2 + 1) for (int i = 0; i < segmentCount; ++i) { writeFace(INNER(i), OUTER(i + 1), INNER(i + 1)); writeFace(INNER(i), OUTER(i), OUTER(i + 1)); } return true; } bool GeometryGenerator::generateCircle2d(const Circle2dParameters ¶ms) { // edge_length = (sin(theta) * radius) * 2 // theta = arcsin(edge_length / (2 * radius)) // theta2 = PI - theta startSubshape(); float angle = std::asinf(params.maxEdgeLength / (2 * params.radius)) * 2; angle = std::fminf(angle, ysMath::Constants::PI - params.smallestAngle); const float steps = ysMath::Constants::TWO_PI / angle; int segmentCount = (int)std::ceilf(steps); segmentCount = (segmentCount < 3) ? 3 : segmentCount; const int vertexCount = 1 + segmentCount; const int faceCount = segmentCount; const int indexCount = faceCount * 3; if (!checkCapacity(vertexCount, indexCount)) { return false; } // Generate center vertex dbasic::Vertex *centerVertex = writeVertex(); centerVertex->Normal = ysMath::Constants::ZAxis; centerVertex->Pos = ysMath::LoadVector(params.center_x, params.center_y); centerVertex->TexCoord = ysVector2(0.5f, 0.5f); const float angleStep = ysMath::Constants::TWO_PI / segmentCount; for (int i = 0; i < segmentCount; ++i) { const float angle0 = angleStep * i; const float x = std::cosf(angle0); const float y = std::sinf(angle0); const float pos_x = params.center_x + x * params.radius; const float pos_y = params.center_y + y * params.radius; dbasic::Vertex *newVertex = writeVertex(); newVertex->Normal.Set(0, 0, 1, 0); newVertex->Pos.Set(pos_x, pos_y, 0.0f, 1.0f); newVertex->TexCoord.x = 0.5f * x + 0.5f; newVertex->TexCoord.y = 0.5f * y + 0.5f; } for (int i = 0; i < segmentCount; ++i) { writeFace(0, i + 1, 1 + ((i + 1) % segmentCount)); } return true; } bool GeometryGenerator::generateCam(const Cam2dParameters ¶ms) { // edge_length = (sin(theta) * radius) * 2 // theta = arcsin(edge_length / (2 * radius)) // theta2 = PI - theta startSubshape(); float angle = std::asinf(params.maxEdgeLength / (2 * params.baseRadius)) * 2; angle = std::fminf(angle, ysMath::Constants::PI - params.smallestAngle); const float steps = ysMath::Constants::TWO_PI / angle; int segmentCount = (int)std::ceilf(steps); segmentCount = (segmentCount < 3) ? 3 : segmentCount; const int vertexCount = 1 + segmentCount; const int faceCount = segmentCount; const int indexCount = faceCount * 3; if (!checkCapacity(vertexCount, indexCount)) { return false; } // Generate center vertex dbasic::Vertex *centerVertex = writeVertex(); centerVertex->Normal = ysMath::Constants::ZAxis; centerVertex->Pos = ysMath::LoadVector(params.center_x, params.center_y); centerVertex->TexCoord = ysVector2(0.5f, 0.5f); const float angleStep = ysMath::Constants::TWO_PI / segmentCount; const float baseRollerPosition = params.baseRadius + params.rollerRadius; float lastRoller_x = 0; float lastRoller_y = 0; float *positions_x = new float[segmentCount]; float *positions_y = new float[segmentCount]; for (int i = 0; i < segmentCount; ++i) { const float angle0 = angleStep * i; const float x = std::cosf(angle0 + ysMath::Constants::PI / 2); const float y = std::sinf(angle0 + ysMath::Constants::PI / 2); const float lift = (params.lift == nullptr) ? 0.0f : (float)params.lift->sampleTriangle((double)angle0 - ysMath::Constants::TWO_PI / 2); const float rollerPosition = baseRollerPosition + lift; const float rollerPos_x = params.center_x + x * rollerPosition; const float rollerPos_y = params.center_y + y * rollerPosition; float dx = -1, dy = 0; if (i != 0) { dx = rollerPos_x - lastRoller_x; dy = rollerPos_y - lastRoller_y; const float mag = std::sqrt(dx * dx + dy * dy); dx /= mag; dy /= mag; } lastRoller_x = rollerPos_x; lastRoller_y = rollerPos_y; const float t_dx = -dy; const float t_dy = dx; const float tangentPos_x = rollerPos_x + t_dx * params.rollerRadius; const float tangentPos_y = rollerPos_y + t_dy * params.rollerRadius; positions_x[i] = tangentPos_x; positions_y[i] = tangentPos_y; } for (int i = 0; i < segmentCount; ++i) { const int prev_i = (i - 1 + segmentCount) % segmentCount; const int next_i = (i + 1) % segmentCount; const float prev_x = positions_x[prev_i], prev_y = positions_y[prev_i]; const float next_x = positions_x[next_i], next_y = positions_y[next_i]; dbasic::Vertex *newVertex = writeVertex(); newVertex->Normal.Set(0, 0, 1, 0); newVertex->Pos.Set((prev_x + next_x) / 2, (prev_y + next_y) / 2, 0.0f, 1.0f); newVertex->TexCoord.x = 0.0f; newVertex->TexCoord.y = 0.5f; } for (int i = 0; i < segmentCount; ++i) { writeFace(0, i + 1, 1 + ((i + 1) % segmentCount)); } delete[] positions_x; delete[] positions_y; return true; } bool GeometryGenerator::generateRhombus(const Rhombus2dParameters ¶ms) { startSubshape(); if (!checkCapacity(4, 6)) { return false; } dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x + params.shear + params.width / 2, params.center_y + params.height / 2); vertex->TexCoord = ysVector2(1.0f, 1.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x + params.shear - params.width / 2, params.center_y + params.height / 2); vertex->TexCoord = ysVector2(0.0f, 1.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x - params.shear + params.width / 2, params.center_y - params.height / 2); vertex->TexCoord = ysVector2(1.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x - params.shear - params.width / 2, params.center_y - params.height / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); writeFace(0, 1, 2); writeFace(1, 3, 2); return true; } bool GeometryGenerator::generateTrapezoid2d(const Trapezoid2dParameters ¶ms) { startSubshape(); if (!checkCapacity(3, 3)) { return false; } dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x - params.base / 2, params.center_y - params.height / 2); vertex->TexCoord = ysVector2(1.0f, 1.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x + params.base / 2, params.center_y - params.height / 2); vertex->TexCoord = ysVector2(0.0f, 1.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x - params.top / 2, params.center_y + params.height / 2); vertex->TexCoord = ysVector2(1.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.center_x + params.top / 2, params.center_y + params.height / 2); vertex->TexCoord = ysVector2(1.0f, 0.0f); writeFace(0, 1, 2); writeFace(1, 3, 2); return true; } bool GeometryGenerator::generateIsoscelesTriangle( float x, float y, float width, float height) { startSubshape(); if (!checkCapacity(3, 3)) { return false; } dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector(x - width / 2, y); vertex->TexCoord = ysVector2(1.0f, 1.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector(x + width / 2, y); vertex->TexCoord = ysVector2(0.0f, 1.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector(x, y + height); vertex->TexCoord = ysVector2(1.0f, 0.0f); writeFace(0, 1, 2); return true; } bool GeometryGenerator::startPath(PathParameters ¶ms) { startSubshape(); const int n = params.n0 + params.n1; if (n < 2) return true; Point *p2 = (1 >= params.n0) ? params.p1 : params.p0; const int i1 = (1 >= params.n0) ? 0 : 1; const float dx = p2[i1].x - params.p0[0].x; const float dy = p2[i1].y - params.p0[0].y; const float length = std::sqrt(dx * dx + dy * dy); const float dir_x = dx / length; const float dir_y = dy / length; const float perp_x = -dir_y; const float perp_y = dir_x; if (!checkCapacity(n * 2, (n - 1) * 6)) { return false; } dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.p0[0].x + perp_x * params.width / 2, params.p0[0].y + perp_y * params.width / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( params.p0[0].x - perp_x * params.width / 2, params.p0[0].y - perp_y * params.width / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); params.v0 = 0; params.v1 = 1; params.pdir_x = dir_x; params.pdir_y = dir_y; params.perp_x = perp_x; params.perp_y = perp_y; return true; } bool GeometryGenerator::generatePathSegment(PathParameters ¶ms, bool detached) { const int n = params.n0 + params.n1; if (params.i > n - 1) return true; Point *p0 = (params.i >= params.n0) ? params.p1 : params.p0; const int i0 = (params.i >= params.n0) ? (params.i - params.n0) : params.i; if (params.i == n - 1) { dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( p0[i0].x + params.perp_x * params.width / 2, p0[i0].y + params.perp_y * params.width / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( p0[i0].x - params.perp_x * params.width / 2, p0[i0].y - params.perp_y * params.width / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); if (!detached) { writeFace(params.v0, params.v1, params.v1 + 1); writeFace(params.v1, params.v1 + 2, params.v1 + 1); } return true; } Point *p1 = (params.i + 1 >= params.n0) ? params.p1 : params.p0; const int i1 = (params.i + 1 >= params.n0) ? (params.i + 1 - params.n0) : params.i + 1; const float dx1 = p1[i1].x - p0[i0].x; const float dy1 = p1[i1].y - p0[i0].y; const float length = std::sqrt(dx1 * dx1 + dy1 * dy1); const float dir_x = dx1 / length; const float dir_y = dy1 / length; float perp_x = -dir_y - params.pdir_y; float perp_y = dir_x + params.pdir_x; float perp_l = std::sqrt(perp_x * perp_x + perp_y * perp_y); if (perp_l == 0) { perp_x = -dir_y; perp_y = dir_x; } else { perp_x /= perp_l; perp_y /= perp_l; } dbasic::Vertex *vertex; vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( p0[i0].x + perp_x * params.width / 2, p0[i0].y + perp_y * params.width / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); vertex = writeVertex(); vertex->Normal = ysMath::Constants::ZAxis; vertex->Pos = ysMath::LoadVector( p0[i0].x - perp_x * params.width / 2, p0[i0].y - perp_y * params.width / 2); vertex->TexCoord = ysVector2(0.0f, 0.0f); if (!detached) { writeFace(params.v0, params.v1, params.v1 + 1); writeFace(params.v1, params.v1 + 2, params.v1 + 1); } params.v0 = params.v1 + 1; params.v1 = params.v1 + 2; params.pdir_x = dir_x; params.pdir_y = dir_y; params.perp_x = perp_x; params.perp_y = perp_y; return true; } dbasic::Vertex *GeometryGenerator::writeVertex() { return &m_vertexData[m_state.vertexPointer++]; } void GeometryGenerator::startShape() { m_state.currentShape.BaseIndex = m_state.indexPointer; m_state.currentShape.BaseVertex = m_state.vertexPointer; m_state.currentShape.FaceCount = 0; m_state.currentShape.VertexData = &m_vertexData[m_state.vertexPointer]; } void GeometryGenerator::endShape(GeometryIndices *indices) { *indices = m_state.currentShape; } void GeometryGenerator::startSubshape() { m_state.subshapeVertexPointer = m_state.vertexPointer - m_state.currentShape.BaseVertex; } void GeometryGenerator::writeFace(unsigned short i0, unsigned short i1, unsigned short i2) { m_indexData[m_state.indexPointer + 0] = i0 + m_state.subshapeVertexPointer; m_indexData[m_state.indexPointer + 1] = i1 + m_state.subshapeVertexPointer; m_indexData[m_state.indexPointer + 2] = i2 + m_state.subshapeVertexPointer; ++m_state.currentShape.FaceCount; m_state.indexPointer += 3; } bool GeometryGenerator::checkCapacity(int vertexCount, int indexCount) { return (vertexCount + m_state.vertexPointer) <= m_vertexBufferSize && (indexCount + m_state.indexPointer) <= m_indexBufferSize; } ysVector GeometryGenerator::findOrthogonal(const ysVector &v) { ysVector base = ysMath::Constants::XAxis; const float s = ysMath::GetScalar(ysMath::Dot(v, base)); if (abs(s) > 0.99f) { base = ysMath::Constants::YAxis; } return ysMath::Normalize(ysMath::Cross(v, base)); } ================================================ FILE: src/governor.cpp ================================================ #include "../include/governor.h" #include "../include/engine.h" #include "../include/utilities.h" Governor::Governor() { m_minSpeed = m_maxSpeed = 0; m_targetSpeed = 0; m_currentThrottle = 1.0; m_velocity = 0.0; m_minVelocity = m_maxVelocity = 0.0; m_k_s = m_k_d = 0.0; m_gamma = 1.0; } Governor::~Governor() { /* void */ } void Governor::initialize(const Parameters ¶ms) { m_minSpeed = params.minSpeed; m_maxSpeed = params.maxSpeed; m_minVelocity = params.minVelocity; m_maxVelocity = params.maxVelocity; m_k_s = params.k_s; m_k_d = params.k_d; m_gamma = params.gamma; } void Governor::setSpeedControl(double s) { Throttle::setSpeedControl(s); m_targetSpeed = (1 - s) * m_minSpeed + s * m_maxSpeed; } void Governor::update(double dt, Engine *engine) { const double currentSpeed = engine->getSpeed(); const double ds = m_targetSpeed * m_targetSpeed - currentSpeed * currentSpeed; m_velocity += (dt * -ds * m_k_s - m_velocity * dt * m_k_d); m_velocity = clamp(m_velocity, m_minVelocity, m_maxVelocity); if (std::abs(currentSpeed) < std::abs(0.5 * m_minSpeed)) { m_velocity = 0; m_currentThrottle = 1.0; } m_currentThrottle += m_velocity * dt; m_currentThrottle = clamp(m_currentThrottle); engine->setThrottle(1 - std::pow(1 - m_currentThrottle, m_gamma)); } ================================================ FILE: src/ignition_module.cpp ================================================ #include "../include/ignition_module.h" #include "../include/utilities.h" #include "../include/constants.h" #include "../include/units.h" #include IgnitionModule::IgnitionModule() { m_plugs = nullptr; m_crankshaft = nullptr; m_timingCurve = nullptr; m_cylinderCount = 0; m_lastCrankshaftAngle = 0.0; m_enabled = false; m_revLimitTimer = 0.0; m_revLimit = 0; m_limiterDuration = 0; } IgnitionModule::~IgnitionModule() { assert(m_plugs == nullptr); } void IgnitionModule::destroy() { delete[] m_plugs; m_plugs = nullptr; m_cylinderCount = 0; } void IgnitionModule::initialize(const Parameters ¶ms) { m_cylinderCount = params.cylinderCount; m_plugs = new SparkPlug[m_cylinderCount]; m_crankshaft = params.crankshaft; m_timingCurve = params.timingCurve; m_revLimit = params.revLimit; m_limiterDuration = params.limiterDuration; } void IgnitionModule::setFiringOrder(int cylinderIndex, double angle) { assert(cylinderIndex < m_cylinderCount); m_plugs[cylinderIndex].angle = angle; m_plugs[cylinderIndex].enabled = true; } void IgnitionModule::reset() { m_lastCrankshaftAngle = m_crankshaft->getCycleAngle(); resetIgnitionEvents(); } void IgnitionModule::update(double dt) { const double cycleAngle = m_crankshaft->getCycleAngle(); if (m_enabled && m_revLimitTimer == 0) { const double fourPi = 4 * constants::pi; const double advance = getTimingAdvance(); for (int i = 0; i < m_cylinderCount; ++i) { double adjustedAngle = positiveMod(m_plugs[i].angle - advance, fourPi); const double r0 = m_lastCrankshaftAngle; double r1 = cycleAngle; if (m_crankshaft->m_body.v_theta < 0) { if (r1 < r0) { r1 += fourPi; adjustedAngle += fourPi; } if (adjustedAngle >= r0 && adjustedAngle < r1) { m_plugs[i].ignitionEvent = m_plugs[i].enabled; } } else { if (r1 > r0) { r1 -= fourPi; adjustedAngle -= fourPi; } if (adjustedAngle >= r1 && adjustedAngle < r0) { m_plugs[i].ignitionEvent = m_plugs[i].enabled; } } } } m_revLimitTimer -= dt; if (std::fabs(m_crankshaft->m_body.v_theta) > m_revLimit) { m_revLimitTimer = m_limiterDuration; } if (m_revLimitTimer < 0) { m_revLimitTimer = 0; } m_lastCrankshaftAngle = cycleAngle; } bool IgnitionModule::getIgnitionEvent(int index) const { return m_plugs[index].ignitionEvent; } void IgnitionModule::resetIgnitionEvents() { for (int i = 0; i < m_cylinderCount; ++i) { m_plugs[i].ignitionEvent = false; } } double IgnitionModule::getTimingAdvance() { return m_timingCurve->sampleTriangle(-m_crankshaft->m_body.v_theta); } IgnitionModule::SparkPlug *IgnitionModule::getPlug(int i) { return &m_plugs[((i % m_cylinderCount) + m_cylinderCount) % m_cylinderCount]; } ================================================ FILE: src/impulse_response.cpp ================================================ #include "../include/impulse_response.h" ImpulseResponse::ImpulseResponse() { m_volume = 1.0; } ImpulseResponse::~ImpulseResponse() { /* void */ } void ImpulseResponse::initialize( const std::string &filename, double volume) { m_filename = filename; m_volume = volume; } ================================================ FILE: src/info_cluster.cpp ================================================ #include "../include/info_cluster.h" #include "../include/engine_sim_application.h" #include #include InfoCluster::InfoCluster() { m_engine = nullptr; m_logMessage = "Started"; } InfoCluster::~InfoCluster() { /* void */ } void InfoCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void InfoCluster::destroy() { UiElement::destroy(); } void InfoCluster::update(float dt) { UiElement::update(dt); } void InfoCluster::render() { Grid grid; grid.h_cells = 6; grid.v_cells = 4; const Bounds logoBounds = grid.get(m_bounds, 0, 0, 1, 2); drawFrame(logoBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor()); drawModel( m_app->getAssetManager()->GetModelAsset("Logo"), m_app->getForegroundColor(), logoBounds.getPosition(Bounds::center), Point(logoBounds.height(), logoBounds.height()) * 0.75f); const Bounds titleBounds = grid.get(m_bounds, 1, 0, 5, 2); drawFrame(titleBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor()); Grid titleSplit; titleSplit.h_cells = 1; titleSplit.v_cells = 3; drawAlignedText( "ENGINE SIMULATOR", titleSplit.get(titleBounds, 0, 0).inset(10.0f).move({ 0.0f, -21.0f }), 42.0f, Bounds::bl, Bounds::bl); drawAlignedText( "YOUTUBE/ANGETHEGREAT", titleSplit.get(titleBounds, 0, 1).inset(10.0f).move({ 0.0f, 5.0f }), 24.0f, Bounds::tl, Bounds::tl); drawAlignedText( "BUILD: v" + EngineSimApplication::getBuildVersion() + " // " __DATE__, titleSplit.get(titleBounds, 0, 2).inset(10.0f).move({ 0.0f, 10.0f }), 16.0f, Bounds::tl, Bounds::tl); const Bounds engineInfoBounds = grid.get(m_bounds, 0, 2, 6, 1); drawFrame(engineInfoBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor()); drawAlignedText( (m_engine != nullptr) ? m_engine->getName() : "", engineInfoBounds.inset(10.0f), 24.0f, Bounds::lm, Bounds::lm); std::stringstream ss; if (m_engine != nullptr) { ss << std::fixed; if (m_engine->getDisplacement() < units::volume(1.0, units::L)) { ss << std::setprecision(0) << units::convert(m_engine->getDisplacement(), units::cc) << " cc -- "; } else { ss << std::setprecision(1) << units::convert(m_engine->getDisplacement(), units::L) << " L -- "; } ss << std::setprecision(0) << units::convert(m_engine->getDisplacement(), units::cubic_inches) << " CI"; } else { ss << "N/A"; } drawAlignedText( ss.str(), engineInfoBounds.inset(10.0f), 24.0f, Bounds::rm, Bounds::rm); const Bounds infoMessagesBounds = grid.get(m_bounds, 0, 3, 6, 1); drawFrame(infoMessagesBounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor()); drawAlignedText( m_logMessage, infoMessagesBounds.inset(10.0f), 24.0f, Bounds::lm, Bounds::lm); } ================================================ FILE: src/intake.cpp ================================================ #include "../include/intake.h" #include "../include/units.h" #include Intake::Intake() { m_inputFlowK = 0; m_idleFlowK = 0; m_flow = 0; m_throttle = 1.0; m_idleThrottlePlatePosition = 0.0; m_crossSectionArea = 0.0; m_flowRate = 0; m_totalFuelInjected = 0; m_molecularAfr = 0; m_runnerLength = 0; } Intake::~Intake() { /* void */ } void Intake::initialize(Parameters ¶ms) { const double width = std::sqrt(params.CrossSectionArea); m_system.initialize( units::pressure(1.0, units::atm), params.volume, units::celcius(25.0)); m_system.setGeometry( width, params.volume / params.CrossSectionArea, 1.0, 0.0); m_atmosphere.initialize( units::pressure(1.0, units::atm), units::volume(1000.0, units::m3), units::celcius(25.0)); m_atmosphere.setGeometry( units::distance(100.0, units::m), units::distance(100.0, units::m), 1.0, 0.0); m_inputFlowK = params.InputFlowK; m_molecularAfr = params.MolecularAfr; m_idleFlowK = params.IdleFlowK; m_idleThrottlePlatePosition = params.IdleThrottlePlatePosition; m_runnerLength = params.RunnerLength; m_crossSectionArea = params.CrossSectionArea; m_velocityDecay = params.VelocityDecay; m_runnerFlowRate = params.RunnerFlowRate; } void Intake::destroy() { /* void */ } void Intake::process(double dt) { const double ideal_afr = 0.8 * m_molecularAfr * 4; const double current_afr = (m_system.mix().p_o2 + m_system.mix().p_inert) / m_system.mix().p_fuel; const double p_air = ideal_afr / (1 + ideal_afr); GasSystem::Mix fuelAirMix; fuelAirMix.p_fuel = 1 - p_air; fuelAirMix.p_inert = p_air * 0.75; fuelAirMix.p_o2 = p_air * 0.25; const double idle_afr = 2.0; const double p_idle_air = idle_afr / (1 + idle_afr); GasSystem::Mix fuelMix; fuelMix.p_fuel = (1.0 - p_idle_air); fuelMix.p_inert = p_idle_air * 0.75; fuelMix.p_o2 = p_idle_air * 0.25; const double throttle = getThrottlePlatePosition(); const double flowAttenuation = std::cos(throttle * constants::pi / 2); GasSystem::FlowParameters flowParams; flowParams.crossSectionArea_0 = units::area(10, units::m2); flowParams.crossSectionArea_1 = m_crossSectionArea; flowParams.direction_x = 0.0; flowParams.direction_y = -1.0; flowParams.dt = dt; m_atmosphere.reset(units::pressure(1.0, units::atm), units::celcius(25.0), fuelAirMix); flowParams.system_0 = &m_atmosphere; flowParams.system_1 = &m_system; flowParams.k_flow = flowAttenuation * m_inputFlowK; m_flow = m_system.flow(flowParams); m_atmosphere.reset(units::pressure(1.0, units::atm), units::celcius(25.0), fuelMix); flowParams.system_0 = &m_atmosphere; flowParams.system_1 = &m_system; flowParams.k_flow = m_idleFlowK; const double idleCircuitFlow = m_system.flow(flowParams); m_system.dissipateExcessVelocity(); m_system.updateVelocity(dt, m_velocityDecay); if (m_flow > 0) { m_totalFuelInjected += fuelAirMix.p_fuel * m_flow; } if (idleCircuitFlow > 0) { m_totalFuelInjected += fuelMix.p_fuel * idleCircuitFlow; } } ================================================ FILE: src/jitter_filter.cpp ================================================ #include "../include/jitter_filter.h" JitterFilter::JitterFilter() { m_history = nullptr; m_maxJitter = 0; m_offset = 0; m_jitterScale = 0.0f; } JitterFilter::~JitterFilter() { /* void */ } void JitterFilter::initialize( int maxJitter, float cutoffFrequency, float audioFrequency) { m_maxJitter = maxJitter; m_history = new float[maxJitter]; m_offset = 0; memset(m_history, 0, sizeof(float) * maxJitter); m_noiseFilter.setCutoffFrequency(cutoffFrequency, audioFrequency); } float JitterFilter::f(float sample) { return fast_f(sample); } ================================================ FILE: src/labeled_gauge.cpp ================================================ #include "../include/labeled_gauge.h" #include "../include/engine_sim_application.h" #include #include LabeledGauge::LabeledGauge() { m_gauge = nullptr; m_title = ""; m_precision = 2; m_unit = ""; m_spaceBeforeUnit = true; } LabeledGauge::~LabeledGauge() { /* void */ } void LabeledGauge::initialize(EngineSimApplication *app) { UiElement::initialize(app); m_gauge = addElement(); m_gauge->m_center = { 0.0f, -20.0f }; } void LabeledGauge::destroy() { UiElement::destroy(); } void LabeledGauge::update(float dt) { UiElement::update(dt); } void LabeledGauge::render() { drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); const Bounds bounds = m_bounds.inset(m_margin); const Bounds title = bounds.verticalSplit(1.0f, 0.9f); const Bounds gaugeBounds = bounds.verticalSplit(0.0f, 0.9f); drawCenteredText(m_title, title.inset(10.0f), 24.0f); const double value = m_gauge->m_value; std::stringstream ss; ss << std::fixed << std::setprecision(m_precision); ss << value << ((m_spaceBeforeUnit && m_unit.length() > 0) ? " " : "") << m_unit; drawAlignedText( ss.str(), gaugeBounds.verticalSplit(0.0f, 2 / 8.0f), gaugeBounds.height() / 8, Bounds::bm, Bounds::bm); m_gauge->m_bounds = gaugeBounds; m_gauge->setLocalPosition({ 0, 0 }); m_gauge->m_outerRadius = std::fmin(gaugeBounds.width(), gaugeBounds.height()) / 2.0f; m_gauge->m_needleOuterRadius = m_gauge->m_outerRadius * m_needleOuterRadius; m_gauge->m_needleInnerRadius = -m_gauge->m_outerRadius * m_needleInnerRadius; UiElement::render(); } ================================================ FILE: src/leveling_filter.cpp ================================================ #include "../include/leveling_filter.h" #include LevelingFilter::LevelingFilter() { m_peak = 30000.0; m_attenuation = 1.0; p_target = 30000.0; p_minLevel = 0.0; p_maxLevel = 1.0; } LevelingFilter::~LevelingFilter() { /* void */ } float LevelingFilter::f(float sample) { m_peak = 0.999f * m_peak; if (std::abs(sample) > m_peak) { m_peak = std::abs(sample); } if (m_peak == 0) return 0; const float raw_attenuation = p_target / m_peak; float attenuation = raw_attenuation; if (attenuation < p_minLevel) attenuation = p_minLevel; else if (attenuation > p_maxLevel) attenuation = p_maxLevel; m_attenuation = 0.9 * m_attenuation + 0.1 * attenuation; return sample * m_attenuation; } ================================================ FILE: src/load_simulation_cluster.cpp ================================================ #include "../include/load_simulation_cluster.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" #include #include LoadSimulationCluster::LoadSimulationCluster() { m_torqueGauge = nullptr; m_hpGauge = nullptr; m_simulator = nullptr; m_clutchPressureGauge = nullptr; m_filteredHorsepower = 0.0; m_filteredTorque = 0.0; m_peakHorsepower = 0.0; m_peakTorque = 0.0; m_peakHorsepowerRpm = 0.0; m_peakTorqueRpm = 0.0; memset(m_systemStatusLights, 0, sizeof(double) * 4); } LoadSimulationCluster::~LoadSimulationCluster() { /* void */ } void LoadSimulationCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); constexpr float shortenAngle = (float)units::angle(1.0, units::deg); m_dynoSpeedGauge = addElement(); m_dynoSpeedGauge->m_title = "DYNO. SPEED"; m_dynoSpeedGauge->m_unit = "RPM"; m_dynoSpeedGauge->m_precision = 0; m_dynoSpeedGauge->setLocalPosition({ 0, 0 }); m_dynoSpeedGauge->m_gauge->m_min = 0; m_dynoSpeedGauge->m_gauge->m_max = 100; m_dynoSpeedGauge->m_gauge->m_minorStep = 500; m_dynoSpeedGauge->m_gauge->m_majorStep = 1000; m_dynoSpeedGauge->m_gauge->m_maxMinorTick = INT_MAX; m_dynoSpeedGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f; m_dynoSpeedGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f; m_dynoSpeedGauge->m_gauge->m_needleWidth = 4.0f; m_dynoSpeedGauge->m_gauge->m_gamma = 1.0f; m_dynoSpeedGauge->m_gauge->m_needleKs = 1000.0f; m_dynoSpeedGauge->m_gauge->m_needleKd = 5.0f; m_dynoSpeedGauge->m_gauge->setBandCount(0); m_torqueGauge = addElement(); m_torqueGauge->m_title = "TORQUE"; m_torqueGauge->m_unit = "LB-FT"; m_torqueGauge->m_precision = 0; m_torqueGauge->setLocalPosition({ 0, 0 }); m_torqueGauge->m_gauge->m_min = 0; m_torqueGauge->m_gauge->m_max = 1000; m_torqueGauge->m_gauge->m_minorStep = 50; m_torqueGauge->m_gauge->m_majorStep = 100; m_torqueGauge->m_gauge->m_maxMinorTick = INT_MAX; m_torqueGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f; m_torqueGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f; m_torqueGauge->m_gauge->m_needleWidth = 4.0f; m_torqueGauge->m_gauge->m_gamma = 1.0f; m_torqueGauge->m_gauge->m_needleKs = 1000.0f; m_torqueGauge->m_gauge->m_needleKd = 5.0f; m_torqueGauge->m_gauge->setBandCount(0); m_hpGauge = addElement(); m_hpGauge->m_title = "POWER"; m_hpGauge->m_unit = "HP"; m_hpGauge->m_precision = 0; m_hpGauge->setLocalPosition({ 0, 0 }); m_hpGauge->m_gauge->m_min = 0; m_hpGauge->m_gauge->m_max = 1000; m_hpGauge->m_gauge->m_minorStep = 50; m_hpGauge->m_gauge->m_majorStep = 100; m_hpGauge->m_gauge->m_maxMinorTick = INT_MAX; m_hpGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f; m_hpGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f; m_hpGauge->m_gauge->m_needleWidth = 4.0f; m_hpGauge->m_gauge->m_gamma = 1.0f; m_hpGauge->m_gauge->m_needleKs = 1000.0f; m_hpGauge->m_gauge->m_needleKd = 5.0f; m_hpGauge->m_gauge->setBandCount(0); m_clutchPressureGauge = addElement(); m_clutchPressureGauge->m_title = "CLUTCH"; m_clutchPressureGauge->m_unit = ""; m_clutchPressureGauge->m_spaceBeforeUnit = false; m_clutchPressureGauge->m_precision = 0; m_clutchPressureGauge->setLocalPosition({ 0, 0 }); m_clutchPressureGauge->m_gauge->m_min = 0; m_clutchPressureGauge->m_gauge->m_max = 100; m_clutchPressureGauge->m_gauge->m_minorStep = 10; m_clutchPressureGauge->m_gauge->m_majorStep = 50; m_clutchPressureGauge->m_gauge->m_maxMinorTick = 200; m_clutchPressureGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f; m_clutchPressureGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f; m_clutchPressureGauge->m_gauge->m_needleWidth = 4.0f; m_clutchPressureGauge->m_gauge->m_gamma = 1.0f; m_clutchPressureGauge->m_gauge->m_needleKs = 1000.0f; m_clutchPressureGauge->m_gauge->m_needleKd = 5.0f; m_clutchPressureGauge->m_gauge->m_needleMaxVelocity = 10.0f; m_clutchPressureGauge->m_gauge->setBandCount(0); m_torqueUnits = app->getAppSettings()->torqueUnits; m_powerUnits = app->getAppSettings()->powerUnits; setUnits(); } void LoadSimulationCluster::destroy() { /* void */ } void LoadSimulationCluster::update(float dt) { UiElement::update(dt); const float systemStatuses[] = { isIgnitionOn() ? 1.0f : 0.01f, m_simulator->m_starterMotor.m_enabled ? 1.0f : 0.01f, m_simulator->m_dyno.m_enabled ? 1.0f : 0.01f, m_simulator->m_dyno.m_hold ? (m_simulator->m_dyno.m_enabled ? 1.0f : 0.25f) : 0.01f }; constexpr float RC = 0.08f; const float alpha = dt / (dt + RC); for (int i = 0; i < 4; ++i) { const float next = systemStatuses[i]; m_systemStatusLights[i] = (1 - alpha) * m_systemStatusLights[i] + alpha * next; } if (m_app->getEngine()->ProcessKeyDown(ysKey::Code::I)) { std::stringstream ss; ss << std::setprecision(0) << std::fixed; if (m_powerUnits == "hp") { ss << m_peakHorsepower << "hp @ " << m_peakHorsepowerRpm << "rpm"; } else { ss << m_peakHorsepower << "kW @ " << m_peakHorsepowerRpm << "rpm"; } ss << " | "; if (m_torqueUnits == "lb-ft") { ss << m_peakTorque << "lb-ft @ " << m_peakTorqueRpm << "rpm"; } else { ss << m_peakTorque << "Nm @ " << m_peakTorqueRpm << "rpm"; } m_app->getInfoCluster()->setLogMessage(ss.str()); } updateHpAndTorque(dt); } void LoadSimulationCluster::render() { Grid grid; grid.h_cells = 3; grid.v_cells = 2; const Bounds gearBounds = grid.get(m_bounds, 2, 0); drawCurrentGear(gearBounds); const Bounds clutchBounds = grid.get(m_bounds, 1, 0); drawClutchPressureGauge(clutchBounds); const Bounds systemStatusBounds = grid.get(m_bounds, 0, 0); drawSystemStatus(systemStatusBounds); const Bounds dynoSpeedBounds = grid.get(m_bounds, 0, 1); m_dynoSpeedGauge->m_gauge->m_value = (float)units::toRpm(std::abs(m_simulator->m_dyno.m_rotationSpeed)); m_dynoSpeedGauge->m_bounds = dynoSpeedBounds; Engine *engine = m_simulator->getEngine(); constexpr float shortenAngle = (float)units::angle(1.0, units::deg); const double redline = units::toRpm((engine != nullptr) ? engine->getRedline() : 0); const double maxRpm = std::floor(redline / 500.0) * 500.0; m_dynoSpeedGauge->m_gauge->m_max = (int)(maxRpm); m_dynoSpeedGauge->m_gauge->setBandCount(1); m_dynoSpeedGauge->m_gauge->setBand( { m_app->getRed(), (float)redline, (float)maxRpm, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 0); const Bounds torqueBounds = grid.get(m_bounds, 1, 1); m_torqueGauge->m_gauge->m_value = m_simulator->m_dyno.m_enabled ? (float)m_filteredTorque : (float)m_peakTorque; m_torqueGauge->m_bounds = torqueBounds; const Bounds horsepowerBounds = grid.get(m_bounds, 2, 1); m_hpGauge->m_gauge->m_value = m_simulator->m_dyno.m_enabled ? (float)m_filteredHorsepower : (float)m_peakHorsepower; m_hpGauge->m_bounds = horsepowerBounds; UiElement::render(); } void LoadSimulationCluster::drawCurrentGear(const Bounds &bounds) { const Bounds insetBounds = bounds.inset(10.0f); const Bounds title = insetBounds.verticalSplit(0.9f, 1.0f); const Bounds body = insetBounds.verticalSplit(0.0f, 0.9f); drawFrame(bounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor()); drawCenteredText("Gear", title.inset(10.0f), 24.0f); const int gear = (getTransmission() != nullptr) ? getTransmission()->getGear() : -1; std::stringstream ss; if (gear != -1) ss << (gear + 1); else ss << "N"; drawCenteredText(ss.str(), body, 64.0f, Bounds::center); } void LoadSimulationCluster::drawClutchPressureGauge(const Bounds &bounds) { m_clutchPressureGauge->m_bounds = bounds; m_clutchPressureGauge->m_gauge->m_value = (getTransmission() != nullptr) ? (float)getTransmission()->getClutchPressure() * 100.0f : 0.0f; } void LoadSimulationCluster::drawSystemStatus(const Bounds &bounds) { const Bounds left = bounds.horizontalSplit(0.0f, 0.6f); const Bounds right = bounds.horizontalSplit(0.6f, 1.0f); drawFrame(bounds, 1.0f, m_app->getForegroundColor(), m_app->getBackgroundColor()); Grid grid; grid.v_cells = 4; grid.h_cells = 1; drawText( "Ignition", grid.get(left, 0, 0).inset(10.0f), 20.0, Bounds::lm); drawText( "Starter", grid.get(left, 0, 1).inset(10.0f), 20.0, Bounds::lm); drawText( "Dyno.", grid.get(left, 0, 2).inset(10.0f), 20.0, Bounds::lm); drawText( "Hold", grid.get(left, 0, 3).inset(10.0f), 20.0, Bounds::lm); for (int i = 0; i < 4; ++i) { const Bounds rawBounds = grid.get(right, 0, i); const float width = std::fmax(rawBounds.width(), rawBounds.height()); const Bounds squareBounds(width - 20.0f, 5.0f, rawBounds.getPosition(Bounds::center), Bounds::center); drawFrame( squareBounds, 1.0f, mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.001f), mix(m_app->getBackgroundColor(), m_app->getRed(), m_systemStatusLights[i]) ); } } void LoadSimulationCluster::updateHpAndTorque(float dt) { constexpr double RC = 0.1; const double alpha = dt / (dt + RC); const double torque = m_simulator->getFilteredDynoTorque(); const double power = m_simulator->getDynoPower(); const double torqueWithUnits = (m_torqueUnits == "Nm") ? (units::convert(torque, units::Nm)) : (units::convert(torque, units::ft_lb)); const double powerWithUnits = (m_powerUnits == "kW") ? (units::convert(power, units::kW)) : (units::convert(power, units::hp)); m_filteredTorque = (1 - alpha) * m_filteredTorque + alpha * torqueWithUnits; m_filteredHorsepower = (1 - alpha) * m_filteredHorsepower + alpha * powerWithUnits; if (m_simulator->getEngine() != nullptr) { if (m_filteredTorque > m_peakTorque) { m_peakTorque = m_filteredTorque; m_peakTorqueRpm = m_simulator->getEngine()->getRpm(); } if (m_filteredHorsepower > m_peakHorsepower) { m_peakHorsepower = std::fmax(m_peakHorsepower, m_filteredHorsepower); m_peakHorsepowerRpm = m_simulator->getEngine()->getRpm(); } } } bool LoadSimulationCluster::isIgnitionOn() const { Engine *engine = m_simulator->getEngine(); return (engine != nullptr) ? engine->getIgnitionModule()->m_enabled : false; } void LoadSimulationCluster::setUnits(){ if (m_torqueUnits == "lb-ft") { m_torqueGauge->m_unit = "lb-ft"; m_torqueGauge->m_precision = 0; m_torqueGauge->m_gauge->m_min = 0; m_torqueGauge->m_gauge->m_max = 1000; m_torqueGauge->m_gauge->m_minorStep = 50; m_torqueGauge->m_gauge->m_majorStep = 100; } else if (m_torqueUnits == "Nm") { m_torqueGauge->m_unit = "Nm"; m_torqueGauge->m_precision = 1; m_torqueGauge->m_gauge->m_min = 0; m_torqueGauge->m_gauge->m_max = 1000; m_torqueGauge->m_gauge->m_minorStep = 50; m_torqueGauge->m_gauge->m_majorStep = 100; } if (m_powerUnits == "hp") { m_hpGauge->m_unit = "hp"; m_hpGauge->m_precision = 0; m_hpGauge->m_gauge->m_min = 0; m_hpGauge->m_gauge->m_max = 1000; m_hpGauge->m_gauge->m_minorStep = 50; m_hpGauge->m_gauge->m_majorStep = 100; } else if (m_powerUnits == "kW") { m_hpGauge->m_unit = "kW"; m_hpGauge->m_precision = 1; m_hpGauge->m_gauge->m_min = 0; m_hpGauge->m_gauge->m_max = 1000; m_hpGauge->m_gauge->m_minorStep = 50; m_hpGauge->m_gauge->m_majorStep = 100; } } ================================================ FILE: src/low_pass_filter.cpp ================================================ #include "../include/low_pass_filter.h" LowPassFilter::LowPassFilter() { m_y = 0; m_rc = 0; m_dt = 1 / 44100.0f; } LowPassFilter::~LowPassFilter() { /* void */ } float LowPassFilter::f(float sample) { return fast_f(sample); } ================================================ FILE: src/main.cpp ================================================ #include "../include/engine_sim_application.h" #include int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { (void)nCmdShow; (void)lpCmdLine; (void)hPrevInstance; EngineSimApplication application; application.initialize((void *)&hInstance, ysContextObject::DeviceAPI::DirectX11); application.run(); application.destroy(); return 0; } ================================================ FILE: src/mixer_cluster.cpp ================================================ #include "../include/mixer_cluster.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include MixerCluster::MixerCluster() { m_volumeGauge = nullptr, m_convolutionGauge = nullptr, m_highFreqFilterGauge = nullptr, m_levelerGauge = nullptr; m_noise0Gauge = nullptr; m_noise1Gauge = nullptr; m_simulator = nullptr; } MixerCluster::~MixerCluster() { /* void */ } void MixerCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); constexpr float shortenAngle = (float)units::angle(1.0, units::deg); m_volumeGauge = addElement(); m_volumeGauge->m_title = "Vol."; m_volumeGauge->m_unit = ""; m_volumeGauge->m_precision = 1; m_volumeGauge->setLocalPosition({ 0, 0 }); m_volumeGauge->m_gauge->m_min = 0; m_volumeGauge->m_gauge->m_max = 100; m_volumeGauge->m_gauge->m_minorStep = 5; m_volumeGauge->m_gauge->m_majorStep = 10; m_volumeGauge->m_gauge->m_maxMinorTick = 1000000; m_volumeGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_volumeGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_volumeGauge->m_gauge->m_needleWidth = 4.0f; m_volumeGauge->m_gauge->m_gamma = 1.0f; m_volumeGauge->m_gauge->m_needleKs = 1000.0f; m_volumeGauge->m_gauge->m_needleKd = 20.0f; m_volumeGauge->m_gauge->setBandCount(0); m_levelerGauge = addElement(); m_levelerGauge->m_title = "Lvl."; m_levelerGauge->m_unit = ""; m_levelerGauge->m_precision = 1; m_levelerGauge->setLocalPosition({ 0, 0 }); m_levelerGauge->m_gauge->m_min = 0; m_levelerGauge->m_gauge->m_max = 100; m_levelerGauge->m_gauge->m_minorStep = 5; m_levelerGauge->m_gauge->m_majorStep = 10; m_levelerGauge->m_gauge->m_maxMinorTick = 1000000; m_levelerGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_levelerGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_levelerGauge->m_gauge->m_needleWidth = 4.0f; m_levelerGauge->m_gauge->m_gamma = 1.0f; m_levelerGauge->m_gauge->m_needleKs = 1000.0f; m_levelerGauge->m_gauge->m_needleKd = 20.0f; m_levelerGauge->m_gauge->setBandCount(0); m_convolutionGauge = addElement(); m_convolutionGauge->m_title = "Conv."; m_convolutionGauge->m_unit = ""; m_convolutionGauge->m_precision = 1; m_convolutionGauge->setLocalPosition({ 0, 0 }); m_convolutionGauge->m_gauge->m_min = 0; m_convolutionGauge->m_gauge->m_max = 100; m_convolutionGauge->m_gauge->m_minorStep = 5; m_convolutionGauge->m_gauge->m_majorStep = 10; m_convolutionGauge->m_gauge->m_maxMinorTick = 1000000; m_convolutionGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_convolutionGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_convolutionGauge->m_gauge->m_needleWidth = 4.0f; m_convolutionGauge->m_gauge->m_gamma = 1.0f; m_convolutionGauge->m_gauge->m_needleKs = 1000.0f; m_convolutionGauge->m_gauge->m_needleKd = 20.0f; m_convolutionGauge->m_gauge->setBandCount(0); m_highFreqFilterGauge = addElement(); m_highFreqFilterGauge->m_title = "+HF"; m_highFreqFilterGauge->m_unit = ""; m_highFreqFilterGauge->m_precision = 2; m_highFreqFilterGauge->setLocalPosition({ 0, 0 }); m_highFreqFilterGauge->m_gauge->m_min = 0; m_highFreqFilterGauge->m_gauge->m_max = 10; m_highFreqFilterGauge->m_gauge->m_minorStep = 1; m_highFreqFilterGauge->m_gauge->m_majorStep = 2; m_highFreqFilterGauge->m_gauge->m_maxMinorTick = 10; m_highFreqFilterGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_highFreqFilterGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_highFreqFilterGauge->m_gauge->m_needleWidth = 4.0f; m_highFreqFilterGauge->m_gauge->m_gamma = 1.0f; m_highFreqFilterGauge->m_gauge->m_needleKs = 1000.0f; m_highFreqFilterGauge->m_gauge->m_needleKd = 20.0f; m_highFreqFilterGauge->m_gauge->setBandCount(0); m_noise0Gauge = addElement(); m_noise0Gauge->m_title = "~ LF"; m_noise0Gauge->m_unit = ""; m_noise0Gauge->m_precision = 1; m_noise0Gauge->setLocalPosition({ 0, 0 }); m_noise0Gauge->m_gauge->m_min = 0; m_noise0Gauge->m_gauge->m_max = 100; m_noise0Gauge->m_gauge->m_minorStep = 5; m_noise0Gauge->m_gauge->m_majorStep = 10; m_noise0Gauge->m_gauge->m_maxMinorTick = 1000000; m_noise0Gauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_noise0Gauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_noise0Gauge->m_gauge->m_needleWidth = 4.0f; m_noise0Gauge->m_gauge->m_gamma = 1.0f; m_noise0Gauge->m_gauge->m_needleKs = 1000.0f; m_noise0Gauge->m_gauge->m_needleKd = 20.0f; m_noise0Gauge->m_gauge->setBandCount(0); m_noise1Gauge = addElement(); m_noise1Gauge->m_title = "~ HF"; m_noise1Gauge->m_unit = ""; m_noise1Gauge->m_precision = 1; m_noise1Gauge->setLocalPosition({ 0, 0 }); m_noise1Gauge->m_gauge->m_min = 0; m_noise1Gauge->m_gauge->m_max = 100; m_noise1Gauge->m_gauge->m_minorStep = 5; m_noise1Gauge->m_gauge->m_majorStep = 10; m_noise1Gauge->m_gauge->m_maxMinorTick = 1000000; m_noise1Gauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_noise1Gauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_noise1Gauge->m_gauge->m_needleWidth = 4.0f; m_noise1Gauge->m_gauge->m_gamma = 1.0f; m_noise1Gauge->m_gauge->m_needleKs = 1000.0f; m_noise1Gauge->m_gauge->m_needleKd = 20.0f; m_noise1Gauge->m_gauge->setBandCount(0); } void MixerCluster::destroy() { UiElement::destroy(); } void MixerCluster::update(float dt) { UiElement::update(dt); } void MixerCluster::render() { Grid grid; grid.h_cells = 6; grid.v_cells = 1; Synthesizer::AudioParameters parameters = m_simulator->synthesizer().getAudioParameters(); m_volumeGauge->m_bounds = grid.get(m_bounds, 0, 0); m_volumeGauge->m_gauge->m_value = (float)parameters.volume * 100.0f; m_convolutionGauge->m_bounds = grid.get(m_bounds, 1, 0); m_convolutionGauge->m_gauge->m_value = (float)parameters.convolution * 100.0f; m_highFreqFilterGauge->m_bounds = grid.get(m_bounds, 2, 0); m_highFreqFilterGauge->m_gauge->m_value = (float)parameters.dF_F_mix * 1000.0f; m_noise0Gauge->m_bounds = grid.get(m_bounds, 3, 0); m_noise0Gauge->m_gauge->m_value = (float)parameters.airNoise * 100.0f; m_noise1Gauge->m_bounds = grid.get(m_bounds, 4, 0); m_noise1Gauge->m_gauge->m_value = (float)parameters.inputSampleNoise * 100.0f; const double gain = m_simulator->synthesizer().getLevelerGain(); m_levelerGauge->m_bounds = grid.get(m_bounds, 5, 0); m_levelerGauge->m_gauge->m_value = 100.0f * (float)((gain - parameters.levelerMinGain) / parameters.levelerMaxGain); UiElement::render(); } ================================================ FILE: src/oscilloscope.cpp ================================================ #include "../include/oscilloscope.h" #include "../include/geometry_generator.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" Oscilloscope::Oscilloscope() { m_xMin = m_xMax = 0; m_yMin = m_yMax = 0; m_lineWidth = 1; m_points = nullptr; m_renderBuffer = nullptr; m_writeIndex = 0; m_bufferSize = 0; m_pointCount = 0; m_drawReverse = true; m_checkMouse = true; m_drawZero = true; m_dynamicallyResizeX = false; m_dynamicallyResizeY = true; i_color = ysMath::Constants::One; } Oscilloscope::~Oscilloscope() { assert(m_points == nullptr); assert(m_renderBuffer == nullptr); } void Oscilloscope::initialize(EngineSimApplication *app) { UiElement::initialize(app); i_color = m_app->getRed(); } void Oscilloscope::destroy() { delete[] m_points; delete[] m_renderBuffer; m_points = nullptr; m_renderBuffer = nullptr; m_writeIndex = 0; m_bufferSize = 0; m_pointCount = 0; } void Oscilloscope::update(float dt) { UiElement::update(dt); m_mouseBounds = m_bounds; } void Oscilloscope::render() { render(m_bounds); } void Oscilloscope::render(const Bounds &bounds) { for (int i = 0; i < m_pointCount; ++i) { const int index = (m_writeIndex - m_pointCount + i + m_bufferSize) % m_bufferSize; m_renderBuffer[index] = dataPointToRenderPosition(m_points[index], bounds); } const int start = (m_writeIndex - m_pointCount + m_bufferSize) % m_bufferSize; const int n0 = (start + m_pointCount) > m_bufferSize ? m_bufferSize - start : m_pointCount; const int n1 = m_pointCount - n0; GeometryGenerator::GeometryIndices lines; GeometryGenerator::PathParameters params; params.p0 = m_renderBuffer + start; params.p1 = m_renderBuffer; params.n0 = n0; params.n1 = n1; m_app->getGeometryGenerator()->startShape(); params.i = 0; params.width = pixelsToUnits(0.5f) * (float)m_lineWidth; if (!m_app->getGeometryGenerator()->startPath(params)) { return; } Point prev = params.p0[0]; bool lastDetached = false; for (int i = 1; i < n0 + n1; ++i) { Point *p = (i >= n0) ? params.p1 : params.p0; const int index = (i >= n0) ? i - n0 : i; const float s = (float)(i) / (n0 + n1); const Point p_i = p[index]; params.i = i; params.width = (float)m_lineWidth * std::fmaxf( pixelsToUnits(1.0f) * s, pixelsToUnits(0.5f)); if (s > 0.95f) { params.width += pixelsToUnits(((s - 0.95f) / 0.05f) * 2); } const bool detached = prev.x > p_i.x || std::abs(p_i.x - prev.x) > pixelsToUnits(100.0f); m_app->getGeometryGenerator()->generatePathSegment( params, (detached || lastDetached) && !m_drawReverse); lastDetached = detached; prev = p_i; } m_app->getGeometryGenerator()->endShape(&lines); resetShader(); if (m_drawZero) { GeometryGenerator::GeometryIndices zeroLine; const Point zeroA = dataPointToRenderPosition({ (float)m_xMin, 0.0f }, bounds); const Point zeroB = dataPointToRenderPosition({ (float)m_xMax, 0.0f }, bounds); GeometryGenerator::Line2dParameters params; params.x0 = zeroA.x; params.x1 = zeroB.x; params.y0 = zeroA.y; params.y1 = zeroB.y; params.lineWidth = pixelsToUnits(0.5f); m_app->getGeometryGenerator()->startShape(); m_app->getGeometryGenerator()->generateLine2d(params); m_app->getGeometryGenerator()->endShape(&zeroLine); m_app->getShaders()->SetBaseColor(mix(m_app->getForegroundColor(), m_app->getBackgroundColor(), 0.95f)); m_app->drawGenerated(zeroLine, 0x11, m_app->getShaders()->GetUiFlags()); } m_app->getShaders()->SetBaseColor(i_color); m_app->drawGenerated(lines, 0x11, m_app->getShaders()->GetUiFlags()); } Point Oscilloscope::dataPointToRenderPosition( const DataPoint &p, const Bounds &bounds) const { const float width = bounds.width(); const float height = bounds.height(); const float s_x = (float)((p.x - m_xMin) / (m_xMax - m_xMin)); const float s_y = (float)((p.y - m_yMin) / (m_yMax - m_yMin)); const Point local = { s_x * width, s_y * height }; return getRenderPoint(bounds.getPosition(Bounds::bl) + local); } void Oscilloscope::addDataPoint(double x, double y) { m_points[m_writeIndex] = { x, y }; m_writeIndex = (m_writeIndex + 1) % m_bufferSize; m_pointCount = (m_pointCount >= m_bufferSize) ? m_bufferSize : m_pointCount + 1; if (m_dynamicallyResizeY) { if (y + std::abs(0.1 * y) >= m_yMax) { m_yMax = y + std::abs(0.1 * y); } else if (y - std::abs(0.1 * y) <= m_yMin) { m_yMin = y - std::abs(0.1 * y); } } if (m_dynamicallyResizeX) { if (x + std::abs(0.1 * x) >= m_xMax) { m_xMax = x + std::abs(0.1 * x); } else if (x - std::abs(0.1 * x) <= m_xMin) { m_xMin = x - std::abs(0.1 * x); } } } void Oscilloscope::setBufferSize(int n) { m_points = new DataPoint[n]; m_renderBuffer = new Point[n]; m_bufferSize = n; reset(); } void Oscilloscope::reset() { m_writeIndex = 0; m_pointCount = 0; } ================================================ FILE: src/oscilloscope_cluster.cpp ================================================ #include "../include/oscilloscope_cluster.h" #include "../include/engine_sim_application.h" #include OscilloscopeCluster::OscilloscopeCluster() { m_simulator = nullptr; m_torqueScope = nullptr; m_powerScope = nullptr; m_totalExhaustFlowScope = nullptr; m_intakeFlowScope = nullptr; m_exhaustFlowScope = nullptr; m_exhaustValveLiftScope = nullptr; m_intakeValveLiftScope = nullptr; m_audioWaveformScope = nullptr; m_cylinderPressureScope = nullptr; m_sparkAdvanceScope = nullptr; m_cylinderMoleculesScope = nullptr; m_pvScope = nullptr; for (int i = 0; i < MaxLayeredScopes; ++i) { m_currentFocusScopes[i] = nullptr; } m_torque = 0; m_power = 0; m_updatePeriod = 0.25f; m_updateTimer = 0.0f; } OscilloscopeCluster::~OscilloscopeCluster() { /* void */ } void OscilloscopeCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); m_torqueScope = addElement(this); m_powerScope = addElement(this); m_exhaustFlowScope = addElement(this); m_totalExhaustFlowScope = addElement(this); m_intakeFlowScope = addElement(this); m_audioWaveformScope = addElement(this); m_intakeValveLiftScope = addElement(this); m_exhaustValveLiftScope = addElement(this); m_cylinderPressureScope = addElement(this); m_sparkAdvanceScope = addElement(this); m_cylinderMoleculesScope = addElement(this); m_pvScope = addElement(this); // Torque m_torqueScope->setBufferSize(100); m_torqueScope->m_xMin = 0.0f; m_torqueScope->m_yMin = 0.0f; m_torqueScope->m_yMax = 0.0f; m_torqueScope->m_lineWidth = 2.0f; m_torqueScope->m_drawReverse = false; m_torqueScope->m_dynamicallyResizeX = true; m_torqueScope->i_color = m_app->getOrange(); // Power m_powerScope->setBufferSize(100); m_powerScope->m_xMin = 0.0f; m_powerScope->m_yMin = 0.0f; m_powerScope->m_yMax = 0.0f; m_powerScope->m_lineWidth = 2.0f; m_powerScope->m_drawReverse = false; m_powerScope->m_dynamicallyResizeX = true; m_powerScope->i_color = m_app->getPink(); // Total exhaust flow m_totalExhaustFlowScope->setBufferSize(1024); m_totalExhaustFlowScope->m_xMin = 0.0f; m_totalExhaustFlowScope->m_xMax = constants::pi * 4; m_totalExhaustFlowScope->m_yMin = -units::flow(10, units::scfm); m_totalExhaustFlowScope->m_yMax = units::flow(10, units::scfm); m_totalExhaustFlowScope->m_lineWidth = 2.0f; m_totalExhaustFlowScope->m_drawReverse = false; m_totalExhaustFlowScope->i_color = m_app->getOrange(); // Exhaust flow m_exhaustFlowScope->setBufferSize(1024); m_exhaustFlowScope->m_xMin = 0.0f; m_exhaustFlowScope->m_xMax = constants::pi * 4; m_exhaustFlowScope->m_yMin = -units::flow(10.0, units::scfm); m_exhaustFlowScope->m_yMax = units::flow(10.0, units::scfm); m_exhaustFlowScope->m_lineWidth = 2.0f; m_exhaustFlowScope->m_drawReverse = false; m_exhaustFlowScope->i_color = m_app->getOrange(); // Intake flow m_intakeFlowScope->setBufferSize(1024); m_intakeFlowScope->m_xMin = 0.0f; m_intakeFlowScope->m_xMax = constants::pi * 4; m_intakeFlowScope->m_yMin = -units::flow(10.0, units::scfm); m_intakeFlowScope->m_yMax = units::flow(10.0, units::scfm); m_intakeFlowScope->m_lineWidth = 2.0f; m_intakeFlowScope->m_drawReverse = false; m_intakeFlowScope->i_color = m_app->getBlue(); // Cylinder molcules m_cylinderMoleculesScope->setBufferSize(1024); m_cylinderMoleculesScope->m_xMin = 0.0f; m_cylinderMoleculesScope->m_xMax = constants::pi * 4; m_cylinderMoleculesScope->m_yMin = -0.05; m_cylinderMoleculesScope->m_yMax = 0.2; m_cylinderMoleculesScope->m_lineWidth = 4.0f; m_cylinderMoleculesScope->m_drawReverse = false; m_cylinderMoleculesScope->i_color = m_app->getForegroundColor(); // Audio waveform scope m_audioWaveformScope->setBufferSize(44100 / 50); m_audioWaveformScope->m_xMin = 0.0f; m_audioWaveformScope->m_xMax = 44100 / 10; m_audioWaveformScope->m_yMin = -1.5f; m_audioWaveformScope->m_yMax = 1.5f; m_audioWaveformScope->m_lineWidth = 2.0f; m_audioWaveformScope->m_drawReverse = false; m_audioWaveformScope->i_color = m_app->getBlue(); // Valve lift scopes m_exhaustValveLiftScope->setBufferSize(1024); m_exhaustValveLiftScope->m_xMin = 0.0f; m_exhaustValveLiftScope->m_xMax = constants::pi * 4; m_exhaustValveLiftScope->m_yMin = (float)units::distance(-10, units::thou); m_exhaustValveLiftScope->m_yMax = (float)units::distance(10, units::thou); m_exhaustValveLiftScope->m_lineWidth = 2.0f; m_exhaustValveLiftScope->m_drawReverse = false; m_exhaustValveLiftScope->i_color = m_app->getOrange(); m_intakeValveLiftScope->setBufferSize(1024); m_intakeValveLiftScope->m_xMin = 0.0f; m_intakeValveLiftScope->m_xMax = constants::pi * 4; m_intakeValveLiftScope->m_yMin = (float)units::distance(-10, units::thou); m_intakeValveLiftScope->m_yMax = (float)units::distance(10, units::thou); m_intakeValveLiftScope->m_lineWidth = 2.0f; m_intakeValveLiftScope->m_drawReverse = false; m_intakeValveLiftScope->i_color = m_app->getBlue(); // Cylinder pressure scope m_cylinderPressureScope->setBufferSize(1024); m_cylinderPressureScope->m_xMin = 0.0f; m_cylinderPressureScope->m_xMax = constants::pi * 4; m_cylinderPressureScope->m_yMin = -(float)std::sqrt(units::pressure(1, units::psi)); m_cylinderPressureScope->m_yMax = (float)std::sqrt(units::pressure(1, units::psi)); m_cylinderPressureScope->m_lineWidth = 2.0f; m_cylinderPressureScope->m_drawReverse = false; m_cylinderPressureScope->i_color = m_app->getOrange(); // Pressure volume scope m_pvScope->setBufferSize(1024); m_pvScope->m_xMin = 0.0f; m_pvScope->m_xMax = units::volume(0.1, units::L); m_pvScope->m_yMin = -(float)std::sqrt(units::pressure(1, units::psi)); m_pvScope->m_yMax = (float)std::sqrt(units::pressure(1, units::psi)); m_pvScope->m_lineWidth = 2.0f; m_pvScope->m_drawReverse = true; m_pvScope->i_color = m_app->getOrange(); m_pvScope->m_dynamicallyResizeX = true; // Spark advance scope m_sparkAdvanceScope->setBufferSize(1024); m_sparkAdvanceScope->m_xMin = 0.0f; m_sparkAdvanceScope->m_xMax = units::rpm(10000); m_sparkAdvanceScope->m_yMin = -units::angle(30, units::deg); m_sparkAdvanceScope->m_yMax = units::angle(60, units::deg); m_sparkAdvanceScope->m_lineWidth = 2.0f; m_sparkAdvanceScope->m_drawReverse = true; m_sparkAdvanceScope->i_color = m_app->getOrange(); m_currentFocusScopes[0] = m_totalExhaustFlowScope; m_currentFocusScopes[1] = nullptr; m_torqueUnits = app->getAppSettings()->torqueUnits; m_powerUnits = app->getAppSettings()->powerUnits; } void OscilloscopeCluster::destroy() { UiElement::destroy(); } void OscilloscopeCluster::signal(UiElement *element, Event event) { if (event == Event::Clicked) { if (element == m_audioWaveformScope) { m_currentFocusScopes[0] = m_audioWaveformScope; m_currentFocusScopes[1] = nullptr; } else if (element == m_powerScope || element == m_torqueScope) { m_currentFocusScopes[0] = m_torqueScope; m_currentFocusScopes[1] = m_powerScope; m_currentFocusScopes[2] = nullptr; } else if (element == m_totalExhaustFlowScope) { m_currentFocusScopes[0] = m_totalExhaustFlowScope; m_currentFocusScopes[1] = nullptr; } else if ( element == m_intakeValveLiftScope || element == m_exhaustValveLiftScope) { m_currentFocusScopes[0] = m_intakeValveLiftScope; m_currentFocusScopes[1] = m_exhaustValveLiftScope; m_currentFocusScopes[2] = nullptr; } else if (element == m_pvScope) { m_currentFocusScopes[0] = m_pvScope; m_currentFocusScopes[1] = nullptr; } else if ( element == m_intakeFlowScope || element == m_exhaustFlowScope || element == m_cylinderMoleculesScope) { m_currentFocusScopes[0] = m_intakeFlowScope; m_currentFocusScopes[1] = m_exhaustFlowScope; m_currentFocusScopes[2] = m_cylinderMoleculesScope; m_currentFocusScopes[3] = nullptr; } else if (element == m_cylinderPressureScope) { m_currentFocusScopes[0] = m_cylinderPressureScope; m_currentFocusScopes[1] = nullptr; } else if (element == m_sparkAdvanceScope) { m_currentFocusScopes[0] = m_sparkAdvanceScope; m_currentFocusScopes[1] = nullptr; } } } void OscilloscopeCluster::update(float dt) { const double torque = (m_torqueUnits == "Nm") ? (units::convert(m_simulator->getFilteredDynoTorque(), units::Nm)) : (units::convert(m_simulator->getFilteredDynoTorque(), units::ft_lb)); const double power = (m_powerUnits == "kW") ? (units::convert(m_simulator->getDynoPower(), units::kW)) : (units::convert(m_simulator->getDynoPower(), units::hp)); m_torque = m_torque * 0.95 + 0.05 * torque; m_power = m_power * 0.95 + 0.05 * power; Engine *engine = m_simulator->getEngine(); if (engine != nullptr) { if (m_updateTimer <= 0 && m_simulator->m_dyno.m_enabled) { m_updateTimer = m_updatePeriod; m_torqueScope->addDataPoint(engine->getRpm(), m_torque); m_powerScope->addDataPoint(engine->getRpm(), m_power); } m_sparkAdvanceScope->addDataPoint( -engine->getCrankshaft(0)->m_body.v_theta, engine->getIgnitionModule()->getTimingAdvance()); } m_updateTimer -= dt; UiElement::update(dt); } void OscilloscopeCluster::render() { Grid grid; grid.h_cells = 3; grid.v_cells = 4; const Bounds &hpTorqueBounds = grid.get(m_bounds, 0, 3); renderScope(m_torqueScope, hpTorqueBounds, "Torque/Power"); renderScope(m_powerScope, hpTorqueBounds, "", true); const Bounds &valveLiftBounds = grid.get(m_bounds, 2, 2); renderScope(m_intakeValveLiftScope, valveLiftBounds, "Valve Lift"); renderScope(m_exhaustValveLiftScope, valveLiftBounds, "", true); const Bounds &flowBounds = grid.get(m_bounds, 2, 3); renderScope(m_intakeFlowScope, flowBounds, "Flow"); renderScope(m_exhaustFlowScope, flowBounds, "", true); renderScope(m_cylinderMoleculesScope, flowBounds, "", true); const Bounds &audioWaveformBounds = grid.get(m_bounds, 0, 2); renderScope(m_audioWaveformScope, audioWaveformBounds, "Waveform"); const Bounds &cylinderPressureBounds = grid.get(m_bounds, 1, 3); renderScope(m_pvScope, cylinderPressureBounds, "pressure-volume"); const Bounds &totalExhaustPressureBounds = grid.get(m_bounds, 1, 2); renderScope(m_totalExhaustFlowScope, totalExhaustPressureBounds, "Total Exhaust Flow"); const Bounds &focusBounds = grid.get(m_bounds, 0, 0, 3, 2); Bounds focusTitle = focusBounds; focusTitle.m0.y = focusTitle.m1.y - (24.0f + 15.0f); Bounds focusBody = focusBounds; focusBody.m1 = focusBody.m1 - Point(0.0f, 24.0f + 15.0f); drawFrame(focusTitle, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); drawFrame(focusBody, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); for (int i = 0; i < MaxLayeredScopes; ++i) { if (m_currentFocusScopes[i] != nullptr) { m_currentFocusScopes[i]->render(focusBody); } else break; } UiElement::render(); } void OscilloscopeCluster::sample() { Engine *engine = m_simulator->getEngine(); if (engine == nullptr) return; const double cylinderPressure = engine->getChamber(0)->m_system.pressure() + engine->getChamber(0)->m_system.dynamicPressure(-1.0, 0.0); if (m_simulator->getCurrentIteration() % 2 == 0) { double cycleAngle = engine->getCrankshaft(0)->getCycleAngle(); if (!engine->isSpinningCw()) { cycleAngle = 4 * constants::pi - cycleAngle; } getTotalExhaustFlowOscilloscope()->addDataPoint( cycleAngle, m_simulator->getTotalExhaustFlow() / m_simulator->getTimestep()); getCylinderPressureScope()->addDataPoint( engine->getCrankshaft(0)->getCycleAngle(constants::pi), std::sqrt(cylinderPressure)); getExhaustFlowOscilloscope()->addDataPoint( cycleAngle, engine->getChamber(0)->getLastTimestepExhaustFlow() / m_simulator->getTimestep()); getIntakeFlowOscilloscope()->addDataPoint( cycleAngle, engine->getChamber(0)->getLastTimestepIntakeFlow() / m_simulator->getTimestep()); getCylinderMoleculesScope()->addDataPoint( cycleAngle, engine->getChamber(0)->m_system.n()); getExhaustValveLiftOscilloscope()->addDataPoint( cycleAngle, engine->getChamber(0)->getCylinderHead()->exhaustValveLift( engine->getChamber(0)->getPiston()->getCylinderIndex())); getIntakeValveLiftOscilloscope()->addDataPoint( cycleAngle, engine->getChamber(0)->getCylinderHead()->intakeValveLift( engine->getChamber(0)->getPiston()->getCylinderIndex())); getPvScope()->addDataPoint( engine->getChamber(0)->getVolume(), std::sqrt(engine->getChamber(0)->m_system.pressure())); } m_exhaustFlowScope->m_yMin = m_intakeFlowScope->m_yMin = std::fmin(m_intakeFlowScope->m_yMin, m_exhaustFlowScope->m_yMin); m_exhaustFlowScope->m_yMax = m_intakeFlowScope->m_yMax = std::fmax(m_intakeFlowScope->m_yMax, m_exhaustFlowScope->m_yMax); m_torqueScope->m_yMin = m_powerScope->m_yMin = std::fmin(m_torqueScope->m_yMin, m_powerScope->m_yMin); m_torqueScope->m_yMax = m_powerScope->m_yMax = std::fmax(m_torqueScope->m_yMax, m_powerScope->m_yMax); m_powerScope->m_xMax = m_torqueScope->m_xMax = std::fmax(m_powerScope->m_xMax, units::toRpm(engine->getSpeed())); } void OscilloscopeCluster::setSimulator(Simulator *simulator) { m_simulator = simulator; } void OscilloscopeCluster::renderScope( Oscilloscope *osc, const Bounds &bounds, const std::string &title, bool overlay) { if (!overlay) { drawFrame(bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); } if (osc == m_currentFocusScopes[0]) { Grid grid; grid.h_cells = 3; grid.v_cells = 4; const Bounds &focusBounds = grid.get(m_bounds, 0, 0, 3, 2); Bounds focusTitle = focusBounds; focusTitle.m1 -= Point(0.0f, 24.0f + 15.0f); Bounds focusBody = focusBounds; focusTitle.m1 += Point(0.0f, 24.0f + 15.0f); drawText(title, focusTitle.inset(20.0f), 24.0f, Bounds::tl); } osc->m_bounds = bounds; } ================================================ FILE: src/part.cpp ================================================ #include "../include/part.h" Part::Part() { /* void */ } Part::~Part() { /* void */ } void Part::destroy() { /* void */ } ================================================ FILE: src/performance_cluster.cpp ================================================ #include "../include/performance_cluster.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include PerformanceCluster::PerformanceCluster() { m_simulator = nullptr; m_timePerTimestepGauge = nullptr; m_timePerTimestepGauge = nullptr; m_fpsGauge = nullptr; m_simSpeedGauge = nullptr; m_simulationFrequencyGauge = nullptr; m_inputSamplesGauge = nullptr; m_audioLagGauge = nullptr; m_timePerTimestep = 0.0; m_filteredSimulationFrequency = 0.0; m_inputBufferUsage = 0.0; m_audioLatency = 0.0; } PerformanceCluster::~PerformanceCluster() { /* void */ } void PerformanceCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); constexpr float shortenAngle = (float)units::angle(1.0, units::deg); m_timePerTimestepGauge = addElement(); m_timePerTimestepGauge->m_title = "RT/dT"; m_timePerTimestepGauge->m_unit = ""; m_timePerTimestepGauge->m_precision = 1; m_timePerTimestepGauge->setLocalPosition({ 0, 0 }); m_timePerTimestepGauge->m_gauge->m_min = 0; m_timePerTimestepGauge->m_gauge->m_max = 200; m_timePerTimestepGauge->m_gauge->m_minorStep = 5; m_timePerTimestepGauge->m_gauge->m_majorStep = 10; m_timePerTimestepGauge->m_gauge->m_maxMinorTick = 1000000; m_timePerTimestepGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_timePerTimestepGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_timePerTimestepGauge->m_gauge->m_needleWidth = 4.0f; m_timePerTimestepGauge->m_gauge->m_gamma = 1.0f; m_timePerTimestepGauge->m_gauge->m_needleKs = 1000.0f; m_timePerTimestepGauge->m_gauge->m_needleKd = 20.0f; m_timePerTimestepGauge->m_gauge->setBandCount(3); m_timePerTimestepGauge->m_gauge->setBand( { m_app->getOrange(), 50.0f, 100.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_timePerTimestepGauge->m_gauge->setBand( { m_app->getRed(), 100.0f, 200.0f, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 1); m_timePerTimestepGauge->m_gauge->setBand( { m_app->getBlue(), 0.0f, 50.0f, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 2); m_fpsGauge = addElement(); m_fpsGauge->m_title = "FPS"; m_fpsGauge->m_unit = ""; m_fpsGauge->m_precision = 1; m_fpsGauge->setLocalPosition({ 0, 0 }); m_fpsGauge->m_gauge->m_min = 0; m_fpsGauge->m_gauge->m_max = 120; m_fpsGauge->m_gauge->m_minorStep = 1; m_fpsGauge->m_gauge->m_majorStep = 15; m_fpsGauge->m_gauge->m_maxMinorTick = 60; m_fpsGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_fpsGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_fpsGauge->m_gauge->m_needleWidth = 4.0f; m_fpsGauge->m_gauge->m_gamma = 0.6f; m_fpsGauge->m_gauge->m_needleKs = 1000.0f; m_fpsGauge->m_gauge->m_needleKd = 20.0f; m_fpsGauge->m_gauge->setBandCount(5); m_fpsGauge->m_gauge->setBand( { m_app->getGreen(), 58, 62, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_fpsGauge->m_gauge->setBand( { m_app->getBlue(), 62, 120, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 1); m_fpsGauge->m_gauge->setBand( { m_app->getOrange(), 30, 58, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_fpsGauge->m_gauge->setBand( { m_app->getRed(), 0, 30, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 3); m_fpsGauge->m_gauge->setBand( { m_app->getForegroundColor(), 60, 120, 3.0f, 0.0f, -shortenAngle, shortenAngle }, 4); m_simSpeedGauge = addElement(); m_simSpeedGauge->m_title = "1 / SPEED"; m_simSpeedGauge->m_unit = ""; m_simSpeedGauge->m_spaceBeforeUnit = false; m_simSpeedGauge->m_precision = 1; m_simSpeedGauge->setLocalPosition({ 0, 0 }); m_simSpeedGauge->m_gauge->m_min = 0; m_simSpeedGauge->m_gauge->m_max = 1000; m_simSpeedGauge->m_gauge->m_minorStep = 50; m_simSpeedGauge->m_gauge->m_majorStep = 100; m_simSpeedGauge->m_gauge->m_maxMinorTick = 1000; m_simSpeedGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_simSpeedGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_simSpeedGauge->m_gauge->m_needleWidth = 4.0f; m_simSpeedGauge->m_gauge->m_gamma = 1.0f; m_simSpeedGauge->m_gauge->m_needleKs = 1000.0f; m_simSpeedGauge->m_gauge->m_needleKd = 20.0f; m_simSpeedGauge->m_gauge->setBandCount(0); m_audioLagGauge = addElement(); m_audioLagGauge->m_title = "LATENCY"; m_audioLagGauge->m_unit = ""; m_audioLagGauge->m_spaceBeforeUnit = false; m_audioLagGauge->m_precision = 1; m_audioLagGauge->setLocalPosition({ 0, 0 }); m_audioLagGauge->m_gauge->m_min = 50; m_audioLagGauge->m_gauge->m_max = 150; m_audioLagGauge->m_gauge->m_minorStep = 5; m_audioLagGauge->m_gauge->m_majorStep = 10; m_audioLagGauge->m_gauge->m_maxMinorTick = 1000; m_audioLagGauge->m_gauge->m_thetaMin = (float)constants::pi * 0.8f; m_audioLagGauge->m_gauge->m_thetaMax = (float)constants::pi * 0.2f; m_audioLagGauge->m_gauge->m_needleWidth = 4.0f; m_audioLagGauge->m_gauge->m_gamma = 1.0f; m_audioLagGauge->m_gauge->m_needleKs = 1000.0f; m_audioLagGauge->m_gauge->m_needleKd = 20.0f; m_audioLagGauge->m_gauge->setBandCount(3); m_audioLagGauge->m_gauge->setBand( { m_app->getForegroundColor(), 90, 110, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_audioLagGauge->m_gauge->setBand( { m_app->getRed(), 50, 90, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1); m_audioLagGauge->m_gauge->setBand( { m_app->getOrange(), 110, 150, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2); m_inputSamplesGauge = addElement(); m_inputSamplesGauge->m_title = "IN. BUFFER"; m_inputSamplesGauge->m_unit = ""; m_inputSamplesGauge->m_spaceBeforeUnit = false; m_inputSamplesGauge->m_precision = 1; m_inputSamplesGauge->setLocalPosition({ 0, 0 }); m_inputSamplesGauge->m_gauge->m_min = 0; m_inputSamplesGauge->m_gauge->m_max = 200; m_inputSamplesGauge->m_gauge->m_minorStep = 5; m_inputSamplesGauge->m_gauge->m_majorStep = 10; m_inputSamplesGauge->m_gauge->m_maxMinorTick = 1000; m_inputSamplesGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_inputSamplesGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_inputSamplesGauge->m_gauge->m_needleWidth = 4.0f; m_inputSamplesGauge->m_gauge->m_gamma = 1.0f; m_inputSamplesGauge->m_gauge->m_needleKs = 1000.0f; m_inputSamplesGauge->m_gauge->m_needleKd = 20.0f; m_inputSamplesGauge->m_gauge->setBandCount(3); m_inputSamplesGauge->m_gauge->setBand( { m_app->getForegroundColor(), 90, 110, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_inputSamplesGauge->m_gauge->setBand( { m_app->getRed(), 0, 10, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1); m_inputSamplesGauge->m_gauge->setBand( { m_app->getOrange(), 150, 200, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2); m_simulationFrequencyGauge = addElement(); m_simulationFrequencyGauge->m_title = "FREQUENCY"; m_simulationFrequencyGauge->m_unit = "hz"; m_simulationFrequencyGauge->m_precision = 0; m_simulationFrequencyGauge->setLocalPosition({ 0, 0 }); m_simulationFrequencyGauge->m_gauge->m_min = 1000; m_simulationFrequencyGauge->m_gauge->m_max = 51000; m_simulationFrequencyGauge->m_gauge->m_minorStep = 1000; m_simulationFrequencyGauge->m_gauge->m_majorStep = 10000; m_simulationFrequencyGauge->m_gauge->m_maxMinorTick = 50000; m_simulationFrequencyGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_simulationFrequencyGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_simulationFrequencyGauge->m_gauge->m_needleWidth = 4.0f; m_simulationFrequencyGauge->m_gauge->m_gamma = 0.9f; m_simulationFrequencyGauge->m_gauge->m_needleKs = 1000.0f; m_simulationFrequencyGauge->m_gauge->m_needleKd = 20.0f; m_simulationFrequencyGauge->m_gauge->setBandCount(1); m_simulationFrequencyGauge->m_gauge->setBand( { m_app->getForegroundColor(), 11025, 44100, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); } void PerformanceCluster::destroy() { UiElement::destroy(); } void PerformanceCluster::update(float dt) { UiElement::update(dt); m_filteredSimulationFrequency = 0.9 * m_filteredSimulationFrequency + 0.1 * m_simulator->getSimulationFrequency() * m_simulator->getSimulationSpeed(); } void PerformanceCluster::render() { Grid grid; grid.h_cells = 3; grid.v_cells = 2; constexpr float shortenAngle = (float)units::angle(1.0, units::deg); const double idealTimePerTimestep = (1.0 / m_filteredSimulationFrequency); m_timePerTimestepGauge->m_bounds = grid.get(m_bounds, 1, 0); m_timePerTimestepGauge->m_gauge->m_value = (float)(m_timePerTimestep / idealTimePerTimestep) * 100.0f; m_fpsGauge->m_bounds = grid.get(m_bounds, 0, 0); m_fpsGauge->m_gauge->m_value = m_app->getEngine()->GetAverageFramerate(); m_simSpeedGauge->m_bounds = grid.get(m_bounds, 2, 0); m_simSpeedGauge->m_gauge->m_value = 1 / (float)m_simulator->getSimulationSpeed(); m_audioLagGauge->m_bounds = grid.get(m_bounds, 0, 1); m_audioLagGauge->m_gauge->m_value = (float)m_audioLatency * 100.0f; m_inputSamplesGauge->m_bounds = grid.get(m_bounds, 1, 1); m_inputSamplesGauge->m_gauge->m_value = (float)m_inputBufferUsage * 100.0f; m_simulationFrequencyGauge->m_bounds = grid.get(m_bounds, 2, 1); m_simulationFrequencyGauge->m_gauge->m_value = (float)m_simulator->getSimulationFrequency(); UiElement::render(); } void PerformanceCluster::addTimePerTimestepSample(double sample) { const double r = 0.95; m_timePerTimestep = r * m_timePerTimestep + (1 - r) * sample; } void PerformanceCluster::addAudioLatencySample(double sample) { const double r = 0.95; m_audioLatency = r * m_audioLatency + (1 - r) * sample; } void PerformanceCluster::addInputBufferUsageSample(double sample) { const double r = 0.95; m_inputBufferUsage = r * m_inputBufferUsage + (1 - r) * sample; } ================================================ FILE: src/piston.cpp ================================================ #include "../include/piston.h" #include "../include/connecting_rod.h" #include "../include/crankshaft.h" #include "../include/cylinder_bank.h" #include Piston::Piston() { m_rod = nullptr; m_bank = nullptr; m_cylinderConstraint = nullptr; m_cylinderIndex = -1; m_compressionHeight = 0.0; m_displacement = 0.0; m_wristPinLocation = 0.0; m_mass = 0.0; m_blowby_k = 0.0; } Piston::~Piston() { /* void */ } void Piston::initialize(const Parameters ¶ms) { m_rod = params.Rod; m_bank = params.Bank; m_cylinderIndex = params.CylinderIndex; m_compressionHeight = params.CompressionHeight; m_displacement = params.Displacement; m_wristPinLocation = params.WristPinPosition; m_mass = params.mass; m_blowby_k = params.BlowbyFlowCoefficient; } void Piston::destroy() { /* void */ } double Piston::relativeX() const { return m_body.p_x - m_bank->getX(); } double Piston::relativeY() const { return m_body.p_y - m_bank->getY(); } double Piston::calculateCylinderWallForce() const { return std::sqrt( m_cylinderConstraint->F_x[0][0] * m_cylinderConstraint->F_x[0][0] + m_cylinderConstraint->F_y[0][0] * m_cylinderConstraint->F_y[0][0]); } ================================================ FILE: src/piston_engine_simulator.cpp ================================================ #include "../include/piston_engine_simulator.h" #include "../include/constants.h" #include "../include/units.h" #include #include #include #include PistonEngineSimulator::PistonEngineSimulator() { m_engine = nullptr; m_transmission = nullptr; m_vehicle = nullptr; m_delayFilters = nullptr; m_crankConstraints = nullptr; m_cylinderWallConstraints = nullptr; m_linkConstraints = nullptr; m_crankshaftFrictionConstraints = nullptr; m_crankshaftLinks = nullptr; m_exhaustFlowStagingBuffer = nullptr; m_derivativeFilter.m_dt = 1.0; m_fluidSimulationSteps = 8; } PistonEngineSimulator::~PistonEngineSimulator() { assert(m_crankConstraints == nullptr); assert(m_cylinderWallConstraints == nullptr); assert(m_linkConstraints == nullptr); assert(m_crankshaftFrictionConstraints == nullptr); assert(m_exhaustFlowStagingBuffer == nullptr); assert(m_delayFilters == nullptr); assert(m_antialiasingFilters == nullptr); } void PistonEngineSimulator::loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission) { Simulator::loadSimulation(engine, vehicle, transmission); m_engine = engine; m_vehicle = vehicle; m_transmission = transmission; const int crankCount = m_engine->getCrankshaftCount(); const int cylinderCount = m_engine->getCylinderCount(); const int linkCount = cylinderCount * 2; if (crankCount <= 0) return; m_crankConstraints = new atg_scs::FixedPositionConstraint[crankCount]; m_cylinderWallConstraints = new atg_scs::LineConstraint[cylinderCount]; m_linkConstraints = new atg_scs::LinkConstraint[linkCount]; m_crankshaftFrictionConstraints = new atg_scs::RotationFrictionConstraint[crankCount]; m_crankshaftLinks = new atg_scs::ClutchConstraint[crankCount - 1]; m_delayFilters = new DelayFilter[cylinderCount]; const double ks = 5000; const double kd = 10; for (int i = 0; i < crankCount; ++i) { Crankshaft *outputShaft = m_engine->getCrankshaft(0); Crankshaft *crankshaft = m_engine->getCrankshaft(i); m_crankConstraints[i].setBody(&crankshaft->m_body); m_crankConstraints[i].setWorldPosition( crankshaft->getPosX(), crankshaft->getPosY()); m_crankConstraints[i].setLocalPosition(0.0, 0.0); m_crankConstraints[i].m_kd = kd; m_crankConstraints[i].m_ks = ks; crankshaft->m_body.p_x = crankshaft->getPosX(); crankshaft->m_body.p_y = crankshaft->getPosY(); crankshaft->m_body.theta = 0; crankshaft->m_body.m = crankshaft->getMass() + crankshaft->getFlywheelMass(); crankshaft->m_body.I = crankshaft->getMomentOfInertia(); m_crankshaftFrictionConstraints[i].m_minTorque = -crankshaft->getFrictionTorque(); m_crankshaftFrictionConstraints[i].m_maxTorque = crankshaft->getFrictionTorque(); m_crankshaftFrictionConstraints[i].setBody(&m_engine->getCrankshaft(i)->m_body); m_system->addRigidBody(&m_engine->getCrankshaft(i)->m_body); m_system->addConstraint(&m_crankConstraints[i]); m_system->addConstraint(&m_crankshaftFrictionConstraints[i]); if (crankshaft != outputShaft) { atg_scs::ClutchConstraint *crankLink = &m_crankshaftLinks[i - 1]; crankLink->setBody1(&outputShaft->m_body); crankLink->setBody2(&crankshaft->m_body); m_system->addConstraint(crankLink); } } m_transmission->addToSystem(m_system, &m_vehicleMass, m_vehicle, m_engine); m_vehicle->addToSystem(m_system, &m_vehicleMass); m_vehicleDrag.initialize(&m_vehicleMass, m_vehicle); m_system->addConstraint(&m_vehicleDrag); m_vehicleMass.reset(); m_vehicleMass.m = 1.0; m_vehicleMass.I = 1.0; m_system->addRigidBody(&m_vehicleMass); for (int i = 0; i < cylinderCount; ++i) { Piston *piston = m_engine->getPiston(i); ConnectingRod *connectingRod = piston->getRod(); CylinderBank *bank = piston->getCylinderBank(); const double dx = std::cos(bank->getAngle() + constants::pi / 2); const double dy = std::sin(bank->getAngle() + constants::pi / 2); m_cylinderWallConstraints[i].setBody(&piston->m_body); m_cylinderWallConstraints[i].m_dx = dx; m_cylinderWallConstraints[i].m_dy = dy; m_cylinderWallConstraints[i].m_local_x = 0.0; m_cylinderWallConstraints[i].m_local_y = piston->getWristPinLocation(); m_cylinderWallConstraints[i].m_p0_x = bank->getX(); m_cylinderWallConstraints[i].m_p0_y = bank->getY(); m_cylinderWallConstraints[i].m_ks = ks; m_cylinderWallConstraints[i].m_kd = kd; piston->setCylinderConstraint(&m_cylinderWallConstraints[i]); m_linkConstraints[i * 2 + 0].setBody1(&connectingRod->m_body); m_linkConstraints[i * 2 + 0].setBody2(&piston->m_body); m_linkConstraints[i * 2 + 0] .setLocalPosition1(0.0, connectingRod->getLittleEndLocal()); m_linkConstraints[i * 2 + 0].setLocalPosition2(0.0, piston->getWristPinLocation()); m_linkConstraints[i * 2 + 0].m_ks = ks; m_linkConstraints[i * 2 + 0].m_kd = kd; double journal_x = 0.0, journal_y = 0.0; if (connectingRod->getMasterRod() == nullptr) { Crankshaft *crankshaft = connectingRod->getCrankshaft(); crankshaft->getRodJournalPositionLocal( connectingRod->getJournal(), &journal_x, &journal_y); m_linkConstraints[i * 2 + 1].setBody2(&crankshaft->m_body); } else { connectingRod->getMasterRod()->getRodJournalPositionLocal( connectingRod->getJournal(), &journal_x, &journal_y); m_linkConstraints[i * 2 + 1].setBody2(&connectingRod->getMasterRod()->m_body); } m_linkConstraints[i * 2 + 1].setBody1(&connectingRod->m_body); m_linkConstraints[i * 2 + 1] .setLocalPosition1(0.0, connectingRod->getBigEndLocal()); m_linkConstraints[i * 2 + 1] .setLocalPosition2(journal_x, journal_y); m_linkConstraints[i * 2 + 1].m_ks = ks; m_linkConstraints[i * 2 + 0].m_kd = kd; piston->m_body.m = piston->getMass(); piston->m_body.I = 1.0; connectingRod->m_body.m = connectingRod->getMass(); connectingRod->m_body.I = connectingRod->getMomentOfInertia(); m_system->addRigidBody(&piston->m_body); m_system->addRigidBody(&connectingRod->m_body); m_system->addConstraint(&m_linkConstraints[i * 2 + 0]); m_system->addConstraint(&m_linkConstraints[i * 2 + 1]); m_system->addConstraint(&m_cylinderWallConstraints[i]); m_system->addForceGenerator(m_engine->getChamber(i)); } m_dyno.connectCrankshaft(m_engine->getOutputCrankshaft()); m_system->addConstraint(&m_dyno); m_starterMotor.connectCrankshaft(m_engine->getOutputCrankshaft()); m_starterMotor.m_maxTorque = m_engine->getStarterTorque(); m_starterMotor.m_rotationSpeed = -m_engine->getStarterSpeed(); m_system->addConstraint(&m_starterMotor); placeAndInitialize(); initializeSynthesizer(); } double PistonEngineSimulator::getAverageOutputSignal() const { double sum = 0.0; for (int i = 0; i < m_engine->getExhaustSystemCount(); ++i) { sum += m_engine->getExhaustSystem(i)->getSystem()->pressure(); } return sum / m_engine->getExhaustSystemCount(); } void PistonEngineSimulator::placeAndInitialize() { const int cylinderCount = m_engine->getCylinderCount(); for (int i = 0; i < cylinderCount; ++i) { ConnectingRod *rod = m_engine->getConnectingRod(i); if (rod->getRodJournalCount() != 0) { placeCylinder(i); } } for (int i = 0; i < cylinderCount; ++i) { placeCylinder(i); } for (int i = 0; i < cylinderCount; ++i) { m_engine->getChamber(i)->m_system.initialize( units::pressure(1.0, units::atm), m_engine->getChamber(i)->getVolume(), units::celcius(25.0) ); Piston *piston = m_engine->getChamber(i)->getPiston(); CylinderHead *head = m_engine->getChamber(i)->getCylinderHead(); ExhaustSystem *exhaust = head->getExhaustSystem(piston->getCylinderIndex()); const double exhaustLength = head->getHeaderPrimaryLength(piston->getCylinderIndex()) + exhaust->getLength(); const double speedOfSound = 343.0 * units::m / units::sec; const double delay = exhaustLength / speedOfSound; m_delayFilters[i].initialize(delay, 10000.0); } m_engine->getIgnitionModule()->reset(); m_exhaustFlowStagingBuffer = new double[m_engine->getExhaustSystemCount()]; } void PistonEngineSimulator::placeCylinder(int i) { ConnectingRod *rod = m_engine->getConnectingRod(i); Piston *piston = m_engine->getPiston(i); CylinderBank *bank = piston->getCylinderBank(); double p_x, p_y; if (rod->getMasterRod() != nullptr) { rod->getMasterRod()->getRodJournalPositionGlobal(rod->getJournal(), &p_x, &p_y); } else { rod->getCrankshaft()->getRodJournalPositionGlobal(rod->getJournal(), &p_x, &p_y); } // (bank->m_x + bank->m_dx * s - p_x)^2 + (bank->m_y + bank->m_dy * s - p_y)^2 = (rod->m_length)^2 const double a = bank->getDx() * bank->getDx() + bank->getDy() * bank->getDy(); const double b = -2 * bank->getDx() * (p_x - bank->getX()) - 2 * bank->getDy() * (p_y - bank->getY()); const double c = (p_x - bank->getX()) * (p_x - bank->getX()) + (p_y - bank->getY()) * (p_y - bank->getY()) - rod->getLength() * rod->getLength(); const double det = b * b - 4 * a * c; if (det < 0) return; const double sqrt_det = std::sqrt(det); const double s0 = (-b + sqrt_det) / (2 * a); const double s1 = (-b - sqrt_det) / (2 * a); const double s = std::max(s0, s1); if (s < 0) return; const double e_x = s * bank->getDx() + bank->getX(); const double e_y = s * bank->getDy() + bank->getY(); const double theta = ((e_y - p_y) > 0) ? std::acos((e_x - p_x) / rod->getLength()) : 2 * constants::pi - std::acos((e_x - p_x) / rod->getLength()); rod->m_body.theta = theta - constants::pi / 2; double cl_x, cl_y; rod->m_body.localToWorld(0, rod->getBigEndLocal(), &cl_x, &cl_y); rod->m_body.p_x += p_x - cl_x; rod->m_body.p_y += p_y - cl_y; piston->m_body.p_x = e_x; piston->m_body.p_y = e_y; piston->m_body.theta = bank->getAngle() + constants::pi; } void PistonEngineSimulator::simulateStep_() { const double timestep = getTimestep(); IgnitionModule *im = m_engine->getIgnitionModule(); im->update(timestep); const int cylinderCount = m_engine->getCylinderCount(); for (int i = 0; i < cylinderCount; ++i) { if (im->getIgnitionEvent(i)) { m_engine->getChamber(i)->ignite(); } m_engine->getChamber(i)->update(timestep); } for (int i = 0; i < cylinderCount; ++i) { m_engine->getChamber(i)->resetLastTimestepExhaustFlow(); m_engine->getChamber(i)->resetLastTimestepIntakeFlow(); } const int exhaustSystemCount = m_engine->getExhaustSystemCount(); const int intakeCount = m_engine->getIntakeCount(); const double fluidTimestep = timestep / m_fluidSimulationSteps; for (int i = 0; i < m_fluidSimulationSteps; ++i) { for (int j = 0; j < exhaustSystemCount; ++j) { m_engine->getExhaustSystem(j)->process(fluidTimestep); } for (int j = 0; j < intakeCount; ++j) { m_engine->getIntake(j)->process(fluidTimestep); m_engine->getIntake(j)->m_flowRate += m_engine->getIntake(j)->m_flow; } for (int j = 0; j < cylinderCount; ++j) { m_engine->getChamber(j)->flow(fluidTimestep); } } im->resetIgnitionEvents(); } double PistonEngineSimulator::getTotalExhaustFlow() const { double totalFlow = 0.0; for (int i = 0; i < m_engine->getCylinderCount(); ++i) { totalFlow += m_engine->getChamber(i)->getLastTimestepExhaustFlow(); } return totalFlow; } void PistonEngineSimulator::endFrame() { Simulator::endFrame(); if (m_engine == nullptr) { return; } const double frameTimestep = simulationSteps() * getTimestep(); const int cylinderCount = m_engine->getCylinderCount(); for (int i = 0; i < m_engine->getIntakeCount(); ++i) { m_engine->getIntake(i)->m_flowRate /= frameTimestep; } } void PistonEngineSimulator::destroy() { if (m_system != nullptr) m_system->reset(); if (m_crankConstraints != nullptr) delete[] m_crankConstraints; if (m_cylinderWallConstraints != nullptr) delete[] m_cylinderWallConstraints; if (m_linkConstraints != nullptr) delete[] m_linkConstraints; if (m_crankshaftFrictionConstraints != nullptr) delete[] m_crankshaftFrictionConstraints; if (m_exhaustFlowStagingBuffer != nullptr) delete[] m_exhaustFlowStagingBuffer; if (m_system != nullptr) delete m_system; if (m_delayFilters != nullptr) delete[] m_delayFilters; m_crankConstraints = nullptr; m_cylinderWallConstraints = nullptr; m_linkConstraints = nullptr; m_crankshaftFrictionConstraints = nullptr; m_exhaustFlowStagingBuffer = nullptr; m_system = nullptr; m_vehicle = nullptr; m_transmission = nullptr; m_engine = nullptr; m_delayFilters = nullptr; } void PistonEngineSimulator::writeToSynthesizer() { const int exhaustSystemCount = m_engine->getExhaustSystemCount(); for (int i = 0; i < exhaustSystemCount; ++i) { m_exhaustFlowStagingBuffer[i] = 0; } const double attenuation = std::min(std::abs(filteredEngineSpeed()), 40.0) / 40.0; const double attenuation_3 = attenuation * attenuation * attenuation; static double lastValveLift[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; const double timestep = getTimestep(); const int cylinderCount = m_engine->getCylinderCount(); for (int i = 0; i < cylinderCount; ++i) { Piston *piston = m_engine->getPiston(i); CylinderBank *bank = piston->getCylinderBank(); CylinderHead *head = m_engine->getHead(bank->getIndex()); ExhaustSystem *exhaust = head->getExhaustSystem(piston->getCylinderIndex()); CombustionChamber *chamber = m_engine->getChamber(i); const double exhaustLength = head->getHeaderPrimaryLength(piston->getCylinderIndex()) + exhaust->getLength(); double exhaustFlow = attenuation_3 * 1600 * ( 1.0 * (chamber->m_exhaustRunnerAndPrimary.pressure() - units::pressure(1.0, units::atm)) + 0.1 * chamber->m_exhaustRunnerAndPrimary.dynamicPressure(1.0, 0.0) + 0.1 * chamber->m_exhaustRunnerAndPrimary.dynamicPressure(-1.0, 0.0)); lastValveLift[i] = head->exhaustValveLift(piston->getCylinderIndex()); const double delayedExhaustPulse = m_delayFilters[i].fast_f(exhaustFlow); ExhaustSystem *exhaustSystem = head->getExhaustSystem(piston->getCylinderIndex()); m_exhaustFlowStagingBuffer[exhaustSystem->getIndex()] += head->getSoundAttenuation(piston->getCylinderIndex()) * (exhaustSystem->getAudioVolume() * delayedExhaustPulse / cylinderCount) * (1 / (exhaustLength * exhaustLength)); } synthesizer().writeInput(m_exhaustFlowStagingBuffer); } ================================================ FILE: src/piston_object.cpp ================================================ #include "../include/piston_object.h" #include "../include/cylinder_bank.h" #include "../include/engine_sim_application.h" PistonObject::PistonObject() { m_piston = nullptr; m_wristPinHole = {}; } PistonObject::~PistonObject() { /* void */ } void PistonObject::generateGeometry() { GeometryGenerator *gen = m_app->getGeometryGenerator(); GeometryGenerator::Circle2dParameters circleParams; circleParams.center_x = 0.0f; circleParams.center_y = (float)m_piston->getWristPinLocation(); circleParams.maxEdgeLength = m_app->pixelsToUnits(5.0f); circleParams.radius = (float)(m_piston->getCylinderBank()->getBore() / 10) * 0.75f; gen->startShape(); gen->generateCircle2d(circleParams); gen->endShape(&m_wristPinHole); } void PistonObject::render(const ViewParameters *view) { const int layer = m_piston->getRod()->getLayer(); if (layer > view->Layer1 || layer < view->Layer0) return; const ysVector col = tintByLayer(m_app->getForegroundColor(), layer - view->Layer0); const ysVector holeCol = tintByLayer(m_app->getBackgroundColor(), layer - view->Layer0); resetShader(); setTransform( &m_piston->m_body, (float)(m_piston->getCylinderBank()->getBore() / 2), 0.0f, (float)(-m_piston->getCompressionHeight() - m_piston->getWristPinLocation())); m_app->getShaders()->SetBaseColor(col); m_app->getEngine()->DrawModel( m_app->getShaders()->GetRegularFlags(), m_app->getAssetManager()->GetModelAsset("Piston"), 0x32 - layer); setTransform(&m_piston->m_body); m_app->getShaders()->SetBaseColor(holeCol); m_app->drawGenerated(m_wristPinHole, 0x32 - layer); } void PistonObject::process(float dt) { /* void */ } void PistonObject::destroy() { /* void */ } ================================================ FILE: src/right_gauge_cluster.cpp ================================================ #include "../include/right_gauge_cluster.h" #include "../include/units.h" #include "../include/gauge.h" #include "../include/constants.h" #include "../include/engine_sim_application.h" #include #include RightGaugeCluster::RightGaugeCluster() { m_engine = nullptr; m_simulator = nullptr; m_afrCluster = nullptr; m_tachometer = nullptr; m_speedometer = nullptr; m_manifoldVacuumGauge = nullptr; m_volumetricEffGauge = nullptr; m_intakeCfmGauge = nullptr; m_combusionChamberStatus = nullptr; m_throttleDisplay = nullptr; m_fuelCluster = nullptr; m_isAbsolute = false; } RightGaugeCluster::~RightGaugeCluster() { /* void */ } void RightGaugeCluster::initialize(EngineSimApplication *app) { UiElement::initialize(app); m_tachometer = addElement(); m_speedometer = addElement(); m_manifoldVacuumGauge = addElement(); m_intakeCfmGauge = addElement(); m_volumetricEffGauge = addElement(); m_combusionChamberStatus = addElement(); m_throttleDisplay = addElement(); m_afrCluster = addElement(); m_fuelCluster = addElement(); m_speedUnits = app->getAppSettings()->speedUnits; m_pressureUnits = app->getAppSettings()->pressureUnits; m_combusionChamberStatus->m_engine = m_engine; constexpr float shortenAngle = (float)units::angle(1.0, units::deg); m_tachometer->m_title = "ENGINE SPEED"; m_tachometer->m_unit = "rpm"; m_tachometer->m_precision = 0; m_tachometer->setLocalPosition({ 0, 0 }); m_tachometer->m_gauge->m_min = 0; m_tachometer->m_gauge->m_max = 7000; m_tachometer->m_gauge->m_minorStep = 100; m_tachometer->m_gauge->m_majorStep = 1000; m_tachometer->m_gauge->m_maxMinorTick = INT_MAX; m_tachometer->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_tachometer->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_tachometer->m_gauge->m_needleWidth = 4.0f; m_tachometer->m_gauge->m_gamma = 1.0f; m_tachometer->m_gauge->m_needleKs = 1000.0f; m_tachometer->m_gauge->m_needleKd = 20.0f; m_tachometer->m_gauge->setBandCount(3); m_tachometer->m_gauge->setBand( { m_app->getForegroundColor(), 400, 1000, 3.0f, 6.0f }, 0); m_tachometer->m_gauge->setBand( { m_app->getOrange(), 5000, 5500, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1); m_tachometer->m_gauge->setBand( { m_app->getRed(), 5500, 7000, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2); m_speedometer->m_title = "VEHICLE SPEED"; m_speedometer->m_unit = "MPH"; m_speedometer->m_precision = 0; m_speedometer->setLocalPosition({ 0, 0 }); m_speedometer->m_gauge->m_min = 0; m_speedometer->m_gauge->m_max = 200; m_speedometer->m_gauge->m_minorStep = 5; m_speedometer->m_gauge->m_majorStep = 10; m_speedometer->m_gauge->m_maxMinorTick = 200; m_speedometer->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_speedometer->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_speedometer->m_gauge->m_needleWidth = 4.0f; m_speedometer->m_gauge->m_gamma = 1.0f; m_speedometer->m_gauge->m_needleKs = 1000.0f; m_speedometer->m_gauge->m_needleKd = 20.0f; m_speedometer->m_gauge->setBandCount(0); m_manifoldVacuumGauge->m_title = "MANIFOLD PRESSURE"; m_manifoldVacuumGauge->m_unit = "inHg"; m_manifoldVacuumGauge->m_precision = 0; m_manifoldVacuumGauge->setLocalPosition({ 0, 0 }); m_manifoldVacuumGauge->m_gauge->m_min = -30; m_manifoldVacuumGauge->m_gauge->m_max = 5; m_manifoldVacuumGauge->m_gauge->m_minorStep = 1; m_manifoldVacuumGauge->m_gauge->m_majorStep = 5; m_manifoldVacuumGauge->m_gauge->m_maxMinorTick = 200; m_manifoldVacuumGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_manifoldVacuumGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_manifoldVacuumGauge->m_gauge->m_needleWidth = 4.0f; m_manifoldVacuumGauge->m_gauge->m_gamma = 1.0f; m_manifoldVacuumGauge->m_gauge->m_needleKs = 1000.0f; m_manifoldVacuumGauge->m_gauge->m_needleKd = 50.0f; m_manifoldVacuumGauge->m_gauge->setBandCount(5); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getRed(), -5, -1, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getOrange(), -10, -5, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getBlue(), -22, -10, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -30, -22, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4); m_volumetricEffGauge->m_title = "VOLUMETRIC EFF."; m_volumetricEffGauge->m_unit = "%"; m_volumetricEffGauge->m_spaceBeforeUnit = false; m_volumetricEffGauge->m_precision = 1; m_volumetricEffGauge->setLocalPosition({ 0, 0 }); m_volumetricEffGauge->m_gauge->m_min = 0; m_volumetricEffGauge->m_gauge->m_max = 120; m_volumetricEffGauge->m_gauge->m_minorStep = 5; m_volumetricEffGauge->m_gauge->m_majorStep = 10; m_volumetricEffGauge->m_gauge->m_maxMinorTick = 200; m_volumetricEffGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_volumetricEffGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_volumetricEffGauge->m_gauge->m_needleWidth = 4.0f; m_volumetricEffGauge->m_gauge->m_gamma = 1.0f; m_volumetricEffGauge->m_gauge->m_needleKs = 1000.0f; m_volumetricEffGauge->m_gauge->m_needleKd = 50.0f; m_volumetricEffGauge->m_gauge->setBandCount(3); m_volumetricEffGauge->m_gauge->setBand( { m_app->getBlue(), 30, 80, 3.0f, 6.0f, 0.0f, shortenAngle }, 0); m_volumetricEffGauge->m_gauge->setBand( { m_app->getGreen(), 80, 100, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_volumetricEffGauge->m_gauge->setBand( { m_app->getRed(), 100, 120, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2); m_intakeCfmGauge->m_title = "AIR SCFM"; m_intakeCfmGauge->m_unit = ""; m_intakeCfmGauge->m_precision = 1; m_intakeCfmGauge->setLocalPosition({ 0, 0 }); m_intakeCfmGauge->m_gauge->m_min = 0; m_intakeCfmGauge->m_gauge->m_max = 1200; m_intakeCfmGauge->m_gauge->m_minorStep = 20; m_intakeCfmGauge->m_gauge->m_majorStep = 100; m_intakeCfmGauge->m_gauge->m_maxMinorTick = 1200; m_intakeCfmGauge->m_gauge->m_thetaMin = (float)constants::pi * 1.2f; m_intakeCfmGauge->m_gauge->m_thetaMax = -(float)constants::pi * 0.2f; m_intakeCfmGauge->m_gauge->m_needleWidth = 4.0f; m_intakeCfmGauge->m_gauge->m_gamma = 1.0f; m_intakeCfmGauge->m_gauge->m_needleKs = 1000.0f; m_intakeCfmGauge->m_gauge->m_needleKd = 50.0f; m_intakeCfmGauge->m_gauge->setBandCount(0); //Set display units setUnits(); } void RightGaugeCluster::destroy() { UiElement::destroy(); } void RightGaugeCluster::update(float dt) { m_combusionChamberStatus->m_engine = m_engine; m_throttleDisplay->m_engine = m_engine; m_afrCluster->m_engine = m_engine; m_fuelCluster->m_engine = m_engine; m_fuelCluster->m_simulator = m_simulator; UiElement::update(dt); } void RightGaugeCluster::render() { drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); const Bounds tachSpeedCluster = m_bounds.verticalSplit(0.5f, 1.0f); renderTachSpeedCluster(tachSpeedCluster); const Bounds fuelAirCluster = m_bounds.verticalSplit(0.0f, 0.5f); renderFuelAirCluster(fuelAirCluster); UiElement::render(); } void RightGaugeCluster::setEngine(Engine *engine) { m_engine = engine; } void RightGaugeCluster::renderTachSpeedCluster(const Bounds &bounds) { const Bounds left = bounds.horizontalSplit(0.0f, 0.5f); const Bounds right = bounds.horizontalSplit(0.5f, 1.0f); const Bounds tach = left.verticalSplit(0.5f, 1.0f); m_tachometer->m_bounds = tach; m_tachometer->m_gauge->m_value = (float)std::abs(getRpm()); constexpr float shortenAngle = (float)units::angle(1.0, units::deg); const float maxRpm = (float)std::ceil(units::toRpm(getRedline() * 1.25) / 1000.0) * 1000.0f; const float redline = (float)std::ceil(units::toRpm(getRedline()) / 500.0) * 500.0f; const float redlineWarning = (float)std::floor(units::toRpm(getRedline() * 0.9) / 500.0) * 500.0f; m_tachometer->m_gauge->m_max = (int)maxRpm; m_tachometer->m_gauge->setBandCount(3); m_tachometer->m_gauge->setBand( { m_app->getForegroundColor(), 400, 1000, 3.0f, 6.0f }, 0); m_tachometer->m_gauge->setBand( { m_app->getOrange(), redlineWarning, redline, 3.0f, 6.0f, -shortenAngle, shortenAngle }, 1); m_tachometer->m_gauge->setBand( { m_app->getRed(), redline, maxRpm, 3.0f, 6.0f, shortenAngle, -shortenAngle }, 2); const Bounds speed = left.verticalSplit(0.0f, 0.5f); m_speedometer->m_bounds = speed; m_speedometer->m_gauge->m_value = (m_speedUnits == "mph") ? (float)units::convert(std::abs(getSpeed()), units::mile / units::hour) : (float)units::convert(std::abs(getSpeed()), units::km / units::hour); m_combusionChamberStatus->m_bounds = right; } void RightGaugeCluster::renderFuelAirCluster(const Bounds &bounds) { const Bounds left = bounds.horizontalSplit(0.0f, 0.5f); const Bounds right = bounds.horizontalSplit(0.5f, 1.0f); const Bounds throttle = left.verticalSplit(0.5f, 1.0f); m_throttleDisplay->m_bounds = throttle; const Bounds fuelSection = left.verticalSplit(0.0f, 0.5f); const Bounds afr = fuelSection.horizontalSplit(0.0f, 0.5f); m_afrCluster->m_bounds = afr; const Bounds fuelConsumption = fuelSection.horizontalSplit(0.5f, 1.0f); m_fuelCluster->m_bounds = fuelConsumption; constexpr double ambientPressure = units::pressure(1.0, units::atm); constexpr double ambientTemperature = units::celcius(25.0); Grid grid = { 1, 3 }; const Bounds manifoldVacuum = grid.get(right, 0, 0, 1, 1); m_manifoldVacuumGauge->m_bounds = manifoldVacuum; const double vacuumReading = getManifoldPressureWithUnits(ambientPressure); if (m_isAbsolute) { m_manifoldVacuumGauge->m_gauge->m_value = static_cast(vacuumReading); } else { m_manifoldVacuumGauge->m_gauge->m_value = (vacuumReading > -0.5) ? 0.0f : static_cast(vacuumReading); } const double rpm = std::fmax(getRpm(), 0.0); const double theoreticalAirPerRevolution = (m_engine == nullptr) ? 0.0 : 0.5 * (ambientPressure * m_engine->getDisplacement()) / (constants::R * ambientTemperature); const double theoreticalAirPerSecond = theoreticalAirPerRevolution * rpm / 60.0; const double actualAirPerSecond = (m_engine == nullptr) ? 0.0 : m_engine->getIntakeFlowRate(); const double volumetricEfficiency = (std::abs(theoreticalAirPerSecond) < 1E-3) ? 0.0 : (actualAirPerSecond / theoreticalAirPerSecond); const Bounds cfmBounds = grid.get(right, 0, 1, 1, 1); m_intakeCfmGauge->m_bounds = cfmBounds; m_intakeCfmGauge->m_gauge->m_value = (float)units::convert(actualAirPerSecond, units::scfm); const Bounds volumetricEfficiencyBounds = grid.get(right, 0, 2, 1, 1); m_volumetricEffGauge->m_bounds = volumetricEfficiencyBounds; m_volumetricEffGauge->m_gauge->m_value = 100.0f * (float)volumetricEfficiency; } double RightGaugeCluster::getManifoldPressureWithUnits(double ambientPressure) { if (m_pressureUnits == "inHg") { return units::convert(std::fmin(getManifoldPressure() - ambientPressure, 0.0), units::inHg); } else if (m_pressureUnits == "kPa") { return units::convert(getManifoldPressure(), units::kPa); } else if (m_pressureUnits == "mbar") { return units::convert(getManifoldPressure(), units::mbar); } else if (m_pressureUnits == "bar") { return units::convert(getManifoldPressure(), units::bar); } else if (m_pressureUnits == "psi") { return units::convert(std::fmin(getManifoldPressure() - ambientPressure, 0.0), units::psi); } else { return units::convert(std::fmin(getManifoldPressure() - ambientPressure, 0.0), units::inHg); } } double RightGaugeCluster::getRpm() const { return (m_engine != nullptr) ? m_engine->getRpm() : 0; } double RightGaugeCluster::getRedline() const { return (m_engine != nullptr) ? m_engine->getRedline() : 0; } double RightGaugeCluster::getSpeed() const { return (m_simulator->getVehicle() != nullptr) ? m_simulator->getVehicle()->getSpeed() : 0; } double RightGaugeCluster::getManifoldPressure() const { return (m_engine != nullptr) ? m_engine->getManifoldPressure() : units::pressure(1.0, units::atm); } void RightGaugeCluster::setUnits() { constexpr float shortenAngle = (float)units::angle(1.0, units::deg); m_speedometer->m_unit = (m_speedUnits == "mph") ? "mph" : "kph"; if (m_pressureUnits == "kPa") { m_isAbsolute = true; m_manifoldVacuumGauge->m_unit = "kPa"; m_manifoldVacuumGauge->m_gauge->m_min = 0; m_manifoldVacuumGauge->m_gauge->m_max = 110; m_manifoldVacuumGauge->m_gauge->m_minorStep = 5; m_manifoldVacuumGauge->m_gauge->m_majorStep = 10; m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getRed(), 110, 90, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getOrange(), 30, 40, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getBlue(), 15, 29, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), 0, 14, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4); } else if (m_pressureUnits == "mbar") { m_isAbsolute = true; m_manifoldVacuumGauge->m_unit = "mbar"; m_manifoldVacuumGauge->m_gauge->m_min = 0; m_manifoldVacuumGauge->m_gauge->m_max = 1100; m_manifoldVacuumGauge->m_gauge->m_minorStep = 50; m_manifoldVacuumGauge->m_gauge->m_majorStep = 100; m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getRed(), 1100, 900, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getOrange(), 300, 400, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getBlue(), 150, 290, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), 0, 140, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4); } else if (m_pressureUnits == "bar") { m_isAbsolute = true; m_manifoldVacuumGauge->m_unit = "bar"; m_manifoldVacuumGauge->m_gauge->m_min = 0; m_manifoldVacuumGauge->m_gauge->m_max = 1.1f; m_manifoldVacuumGauge->m_gauge->m_minorStep = 1; m_manifoldVacuumGauge->m_gauge->m_majorStep = 1; m_manifoldVacuumGauge->m_precision = 2; m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getRed(), 0.8f, 1.1f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getOrange(), 0.3f, 0.5f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getBlue(), 0.15f, 0.29f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), 0, 0.14f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4); } else if (m_pressureUnits == "psi") { m_isAbsolute = false; m_manifoldVacuumGauge->m_unit = "psi"; m_manifoldVacuumGauge->m_gauge->m_min = -15; m_manifoldVacuumGauge->m_gauge->m_max = 3; m_manifoldVacuumGauge->m_gauge->m_minorStep = 1; m_manifoldVacuumGauge->m_gauge->m_majorStep = 5; m_manifoldVacuumGauge->m_precision = 1; m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getRed(), -4, 1, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getOrange(), -7, -4, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getBlue(), -12, -7, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -15, -12, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4); } else { m_isAbsolute = false; m_manifoldVacuumGauge->m_unit = "inHg"; m_manifoldVacuumGauge->m_gauge->m_min = -30; m_manifoldVacuumGauge->m_gauge->m_max = 5; m_manifoldVacuumGauge->m_gauge->m_minorStep = 1; m_manifoldVacuumGauge->m_gauge->m_majorStep = 5; m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getRed(), -5, -1, 3.0f, 6.0f, shortenAngle, shortenAngle }, 0); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -1.0f, 1.0f, 3.0f, 6.0f, shortenAngle, shortenAngle }, 1); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getOrange(), -10, -5, 3.0f, 6.0f, shortenAngle, shortenAngle }, 2); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getBlue(), -22, -10, 3.0f, 6.0f, shortenAngle, shortenAngle }, 3); m_manifoldVacuumGauge->m_gauge->setBand( { m_app->getForegroundColor(), -30, -22, 3.0f, 6.0f, shortenAngle, shortenAngle }, 4); } } ================================================ FILE: src/shaders.cpp ================================================ #include "../include/shaders.h" Shaders::Shaders() { m_cameraPosition = ysMath::LoadVector(0.0f, 0.0f); m_mainStage = nullptr; m_uiStage = nullptr; m_objectVariables.ColorReplace = 1; m_objectVariables.Lit = 0; m_objectVariables.Transform = ysMath::LoadIdentity(); m_screenVariables.FogNear = m_uiScreenVariables.FogNear = 16000.0f; m_screenVariables.FogFar = m_uiScreenVariables.FogFar = 16001.0f; } Shaders::~Shaders() { /* void */ } ysError Shaders::Initialize( dbasic::ShaderSet *shaderSet, ysRenderTarget *mainRenderTarget, ysRenderTarget *uiRenderTarget, ysShaderProgram *shaderProgram, ysInputLayout *inputLayout) { YDS_ERROR_DECLARE("Initialize"); YDS_NESTED_ERROR_CALL(shaderSet->NewStage("ShaderStage::Main", &m_mainStage)); YDS_NESTED_ERROR_CALL(shaderSet->NewStage("ShaderStage::UI", &m_uiStage)); m_mainStage->SetInputLayout(inputLayout); m_mainStage->SetRenderTarget(mainRenderTarget); m_mainStage->SetShaderProgram(shaderProgram); m_mainStage->SetFlagBit(0); m_mainStage->SetType(dbasic::ShaderStage::Type::FullPass); m_mainStage->NewConstantBuffer( "Buffer::ScreenData", 0, dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData, &m_screenVariables); m_mainStage->NewConstantBuffer( "Buffer::ObjectData", 1, dbasic::ShaderStage::ConstantBufferBinding::BufferType::ObjectData, &m_objectVariables); m_mainStage->NewConstantBuffer( "Buffer::LightingData", 3, dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData, &m_lightingControls); // UI Stage m_uiStage->SetInputLayout(inputLayout); m_uiStage->SetRenderTarget(uiRenderTarget); m_uiStage->SetShaderProgram(shaderProgram); m_uiStage->SetFlagBit(1); m_uiStage->SetClearTarget(false); m_uiStage->SetType(dbasic::ShaderStage::Type::FullPass); m_uiStage->NewConstantBuffer( "Buffer::ScreenData", 0, dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData, &m_uiScreenVariables); m_uiStage->NewConstantBuffer( "Buffer::ObjectData", 1, dbasic::ShaderStage::ConstantBufferBinding::BufferType::ObjectData, &m_objectVariables); m_uiStage->NewConstantBuffer( "Buffer::LightingData", 3, dbasic::ShaderStage::ConstantBufferBinding::BufferType::SceneData, &m_lightingControls); return YDS_ERROR_RETURN(ysError::None); } ysError Shaders::UseMaterial(dbasic::Material *material) { YDS_ERROR_DECLARE("UseMaterial"); return YDS_ERROR_RETURN(ysError::None); } void Shaders::SetObjectTransform(const ysMatrix &mat) { m_objectVariables.Transform = mat; } void Shaders::ConfigureModel(float scale, dbasic::ModelAsset *model) { /* void */ } void Shaders::SetBaseColor(const ysVector &color) { m_objectVariables.BaseColor = color; } void Shaders::ResetBaseColor() { m_objectVariables.BaseColor = ysMath::LoadVector(1.0f, 1.0f, 1.0f, 1.0f); } dbasic::StageEnableFlags Shaders::GetRegularFlags() const { return m_mainStage->GetFlags(); } dbasic::StageEnableFlags Shaders::GetUiFlags() const { return m_uiStage->GetFlags(); } void Shaders::CalculateCamera( float width, float height, const Bounds &cameraBounds, float screenWidth, float screenHeight, float angle) { const ysMatrix projection = ysMath::OrthographicProjection( width, height, 0.001f, 500.0f); const Point scale = Point(screenWidth, screenHeight); const Point center = (cameraBounds.getPosition() - Point(screenWidth / 2, screenHeight / 2)) / scale; m_screenVariables.Projection = ysMath::Transpose( ysMath::MatMult( projection, ysMath::MatMult( ysMath::ScaleTransform(ysMath::LoadVector( cameraBounds.width() / screenWidth, cameraBounds.height() / screenHeight, 1.0f)), ysMath::TranslationTransform(ysMath::LoadVector(center.x, center.y, 0.0f)) ) ) ); m_screenVariables.Projection = ysMath::Transpose(projection); const float sinAngle = std::sin(angle); const float cosAngle = std::cos(angle); const ysVector cameraEye = ysMath::Add( ysMath::LoadVector(10.0f * sinAngle, 0.0f, 10.0f * cosAngle, 1.0f), m_cameraPosition); const ysVector cameraTarget = ysMath::Add( ysMath::LoadVector(0.0f, 0.0f, 0.0f, 1.0f), m_cameraPosition); const ysVector up = ysMath::LoadVector(0.0f, 1.0f); m_screenVariables.CameraView = ysMath::Transpose(ysMath::CameraTarget(cameraEye, cameraTarget, up)); m_screenVariables.Eye = ysMath::LoadVector(cameraEye); } void Shaders::CalculateUiCamera(float screenWidth, float screenHeight) { m_uiScreenVariables.Projection = ysMath::Transpose( ysMath::OrthographicProjection( screenWidth, screenHeight, 0.001f, 500.0f)); const ysVector cameraEye = ysMath::LoadVector(0.0f, 0.0f, 10.0f, 1.0f); const ysVector cameraTarget = ysMath::LoadVector(0.0f, 0.0f, 0.0f, 1.0f); const ysVector up = ysMath::LoadVector(0.0f, 1.0f); m_uiScreenVariables.CameraView = ysMath::Transpose(ysMath::CameraTarget(cameraEye, cameraTarget, up)); m_uiScreenVariables.Eye = ysMath::LoadVector(cameraEye); } void Shaders::SetClearColor(const ysVector &col) { m_mainStage->SetClearColor(col); } ================================================ FILE: src/simulation_object.cpp ================================================ #include "../include/simulation_object.h" #include "../include/piston.h" #include "../include/cylinder_bank.h" #include "../include/engine_sim_application.h" SimulationObject::SimulationObject() { m_app = nullptr; } SimulationObject::~SimulationObject() { /* void */ } void SimulationObject::initialize(EngineSimApplication *app) { m_app = app; } void SimulationObject::generateGeometry() { /* void */ } void SimulationObject::render(const ViewParameters *settings) { /* void */ } void SimulationObject::process(float dt) { /* void */ } void SimulationObject::destroy() { /* void */ } Piston *SimulationObject::getForemostPiston(CylinderBank *bank, int layer) { Engine *engine = m_app->getSimulator()->getEngine(); Piston *frontmostPiston = nullptr; const int cylinderCount = engine->getCylinderCount(); for (int i = 0; i < cylinderCount; ++i) { Piston *piston = engine->getPiston(i); if (piston->getCylinderBank() == bank) { if (piston->getRod()->getJournal() >= layer) { if (frontmostPiston == nullptr || piston->getRod()->getJournal() < frontmostPiston->getRod()->getJournal()) { frontmostPiston = piston; } } } } return frontmostPiston; } void SimulationObject::resetShader() { m_app->getShaders()->ResetBaseColor(); m_app->getShaders()->SetObjectTransform(ysMath::LoadIdentity()); } void SimulationObject::setTransform( atg_scs::RigidBody *rigidBody, float scale, float lx, float ly, float angle, float z) { double p_x, p_y; rigidBody->localToWorld(lx, ly, &p_x, &p_y); const ysMatrix rot = ysMath::RotationTransform( ysMath::Constants::ZAxis, (float)rigidBody->theta + angle); const ysMatrix trans = ysMath::TranslationTransform( ysMath::LoadVector((float)p_x, (float)p_y, z)); const ysMatrix scaleTransform = ysMath::ScaleTransform(ysMath::LoadScalar(scale)); m_app->getShaders()->SetObjectTransform( ysMath::MatMult(ysMath::MatMult(trans, rot), scaleTransform)); } ysVector SimulationObject::tintByLayer(const ysVector &col, int layers) const { ysVector result = col; for (int i = 0; i < layers; ++i) { result = ysMath::Add( ysMath::Mul(result, ysMath::LoadScalar(0.3f)), ysMath::Mul(m_app->getBackgroundColor(), ysMath::LoadScalar(0.7f)) ); } return result; } ================================================ FILE: src/simulator.cpp ================================================ #include "../include/simulator.h" Simulator::Simulator() { m_engine = nullptr; m_vehicle = nullptr; m_transmission = nullptr; m_system = nullptr; m_physicsProcessingTime = 0; m_simulationSpeed = 1.0; m_targetSynthesizerLatency = 0.1; m_simulationFrequency = 10000; m_steps = 0; m_currentIteration = 0; m_filteredEngineSpeed = 0.0; m_dynoTorqueSamples = nullptr; m_lastDynoTorqueSample = 0; } Simulator::~Simulator() { assert(m_system == nullptr); assert(m_dynoTorqueSamples == nullptr); } void Simulator::initialize(const Parameters ¶ms) { if (params.systemType == SystemType::NsvOptimized) { atg_scs::OptimizedNsvRigidBodySystem *system = new atg_scs::OptimizedNsvRigidBodySystem; system->initialize( new atg_scs::GaussSeidelSleSolver); m_system = system; } else { atg_scs::GenericRigidBodySystem *system = new atg_scs::GenericRigidBodySystem; system->initialize( new atg_scs::GaussianEliminationSleSolver, new atg_scs::NsvOdeSolver); m_system = system; } m_dynoTorqueSamples = new double[DynoTorqueSamples]; for (int i = 0; i < DynoTorqueSamples; ++i) { m_dynoTorqueSamples[i] = 0.0; } } void Simulator::loadSimulation(Engine *engine, Vehicle *vehicle, Transmission *transmission) { m_engine = engine; m_vehicle = vehicle; m_transmission = transmission; } void Simulator::releaseSimulation() { m_synthesizer.endAudioRenderingThread(); if (m_system != nullptr) m_system->reset(); destroy(); } void Simulator::startFrame(double dt) { if (m_engine == nullptr) { m_steps = 0; return; } m_simulationStart = std::chrono::steady_clock::now(); m_currentIteration = 0; m_synthesizer.setInputSampleRate(m_simulationFrequency * m_simulationSpeed); const double timestep = getTimestep(); m_steps = (int)std::round((dt * m_simulationSpeed) / timestep); const double targetLatency = getSynthesizerInputLatencyTarget(); if (m_synthesizer.getLatency() < targetLatency) { m_steps = static_cast((m_steps + 1) * 1.1); } else if (m_synthesizer.getLatency() > targetLatency) { m_steps = static_cast((m_steps - 1) * 0.9); if (m_steps < 0) { m_steps = 0; } } if (m_steps > 0) { for (int i = 0; i < m_engine->getIntakeCount(); ++i) { m_engine->getIntake(i)->m_flowRate = 0; } } } bool Simulator::simulateStep() { if (getCurrentIteration() >= simulationSteps()) { auto s1 = std::chrono::steady_clock::now(); const long long lastFrame = std::chrono::duration_cast(s1 - m_simulationStart).count(); m_physicsProcessingTime = m_physicsProcessingTime * 0.98 + 0.02 * lastFrame; return false; } const double timestep = getTimestep(); m_system->process(timestep, 1); m_engine->update(timestep); m_vehicle->update(timestep); m_transmission->update(timestep); updateFilteredEngineSpeed(timestep); Crankshaft *outputShaft = m_engine->getOutputCrankshaft(); outputShaft->resetAngle(); for (int i = 0; i < m_engine->getCrankshaftCount(); ++i) { Crankshaft *shaft = m_engine->getCrankshaft(i); // Correct drift (temporary hack) shaft->m_body.theta = outputShaft->m_body.theta; } const int index = static_cast(std::floor(DynoTorqueSamples * outputShaft->getCycleAngle() / (4 * constants::pi))); const int step = m_engine->isSpinningCw() ? 1 : -1; m_dynoTorqueSamples[index] = m_dyno.getTorque(); if (m_lastDynoTorqueSample != index) { for (int i = m_lastDynoTorqueSample + step; i != index; i += step) { if (i >= DynoTorqueSamples) { i = -1; continue; } else if (i < 0) { i = DynoTorqueSamples; continue; } m_dynoTorqueSamples[i] = m_dyno.getTorque(); } m_lastDynoTorqueSample = index; } simulateStep_(); writeToSynthesizer(); ++m_currentIteration; return true; } double Simulator::getTotalExhaustFlow() const { return 0.0; } int Simulator::readAudioOutput(int samples, int16_t *target) { return m_synthesizer.readAudioOutput(samples, target); } void Simulator::endFrame() { m_synthesizer.endInputBlock(); } void Simulator::destroy() { m_synthesizer.destroy(); } void Simulator::startAudioRenderingThread() { m_synthesizer.startAudioRenderingThread(); } void Simulator::endAudioRenderingThread() { m_synthesizer.endAudioRenderingThread(); } double Simulator::getSynthesizerInputLatencyTarget() const { return m_targetSynthesizerLatency; } double Simulator::getFilteredDynoTorque() const { if (m_dynoTorqueSamples == nullptr) return 0; double averageTorque = 0; for (int i = 0; i < DynoTorqueSamples; ++i) { averageTorque += m_dynoTorqueSamples[i]; } return averageTorque / DynoTorqueSamples; } double Simulator::getDynoPower() const { return (m_engine != nullptr) ? getFilteredDynoTorque() * m_engine->getSpeed() : 0; } double Simulator::getAverageOutputSignal() const { return 0.0; } void Simulator::initializeSynthesizer() { Synthesizer::Parameters synthParams; synthParams.audioBufferSize = 44100; synthParams.audioSampleRate = 44100; synthParams.inputBufferSize = 44100; synthParams.inputChannelCount = m_engine->getExhaustSystemCount(); synthParams.inputSampleRate = static_cast(getSimulationFrequency()); m_synthesizer.initialize(synthParams); } void Simulator::simulateStep_() { } void Simulator::updateFilteredEngineSpeed(double dt) { const double alpha = dt / (100 + dt); m_filteredEngineSpeed = alpha * m_filteredEngineSpeed + (1 - alpha) * m_engine->getRpm(); } ================================================ FILE: src/standard_valvetrain.cpp ================================================ #include "../include/standard_valvetrain.h" #include "../include/camshaft.h" StandardValvetrain::StandardValvetrain() { m_intakeCamshaft = nullptr; m_exhaustCamshaft = nullptr; } StandardValvetrain::~StandardValvetrain() { /* void */ } void StandardValvetrain::initialize(const Parameters ¶ms) { m_intakeCamshaft = params.intakeCamshaft; m_exhaustCamshaft = params.exhaustCamshaft; } double StandardValvetrain::intakeValveLift(int cylinder) { return m_intakeCamshaft->valveLift(cylinder); } double StandardValvetrain::exhaustValveLift(int cylinder) { return m_exhaustCamshaft->valveLift(cylinder); } Camshaft *StandardValvetrain::getActiveIntakeCamshaft() { return m_intakeCamshaft; } Camshaft *StandardValvetrain::getActiveExhaustCamshaft() { return m_exhaustCamshaft; } ================================================ FILE: src/starter_motor.cpp ================================================ #include "../include/starter_motor.h" #include "../include/units.h" StarterMotor::StarterMotor() : atg_scs::Constraint(1, 1) { m_ks = 10.0; m_kd = 1.0; m_maxTorque = units::torque(80.0, units::ft_lb); m_rotationSpeed = -units::rpm(200.0); m_enabled = false; } StarterMotor::~StarterMotor() { /* void */ } void StarterMotor::connectCrankshaft(Crankshaft *crankshaft) { m_bodies[0] = &crankshaft->m_body; } void StarterMotor::calculate(Output *output, atg_scs::SystemState *state) { output->J[0][0] = 0; output->J[0][1] = 0; output->J[0][2] = 1; output->J_dot[0][0] = 0; output->J_dot[0][1] = 0; output->J_dot[0][2] = 0; output->ks[0] = m_ks; output->kd[0] = m_kd; output->C[0] = 0; output->v_bias[0] = -m_rotationSpeed; if (m_rotationSpeed < 0) { output->limits[0][0] = m_enabled ? -m_maxTorque : 0.0; output->limits[0][1] = 0.0; } else { output->limits[0][0] = 0.0; output->limits[0][1] = m_enabled ? m_maxTorque : 0.0; } } ================================================ FILE: src/synthesizer.cpp ================================================ #include "../include/synthesizer.h" #include "../include/utilities.h" #include "../include/delta.h" #include #include #undef min #undef max Synthesizer::Synthesizer() { m_inputChannels = nullptr; m_inputChannelCount = 0; m_inputBufferSize = 0; m_inputWriteOffset = 0.0; m_inputSamplesRead = 0; m_audioBufferSize = 0; m_inputSampleRate = 0.0; m_audioSampleRate = 0.0; m_lastInputSampleOffset = 0.0; m_run = true; m_thread = nullptr; m_filters = nullptr; } Synthesizer::~Synthesizer() { assert(m_inputChannels == nullptr); assert(m_thread == nullptr); assert(m_filters == nullptr); } void Synthesizer::initialize(const Parameters &p) { m_inputChannelCount = p.inputChannelCount; m_inputBufferSize = p.inputBufferSize; m_inputWriteOffset = p.inputBufferSize; m_audioBufferSize = p.audioBufferSize; m_inputSampleRate = p.inputSampleRate; m_audioSampleRate = p.audioSampleRate; m_audioParameters = p.initialAudioParameters; m_inputSamplesRead = 0; m_inputWriteOffset = 0; m_processed = true; m_audioBuffer.initialize(p.audioBufferSize); m_inputChannels = new InputChannel[p.inputChannelCount]; for (int i = 0; i < p.inputChannelCount; ++i) { m_inputChannels[i].transferBuffer = new float[p.inputBufferSize]; m_inputChannels[i].data.initialize(p.inputBufferSize); } m_filters = new ProcessingFilters[p.inputChannelCount]; for (int i = 0; i < p.inputChannelCount; ++i) { m_filters[i].airNoiseLowPass.setCutoffFrequency( m_audioParameters.airNoiseFrequencyCutoff, m_audioSampleRate); m_filters[i].derivative.m_dt = 1 / m_audioSampleRate; m_filters[i].inputDcFilter.setCutoffFrequency(10.0); m_filters[i].inputDcFilter.m_dt = 1 / m_audioSampleRate; m_filters[i].jitterFilter.initialize( 10, m_audioParameters.inputSampleNoiseFrequencyCutoff, m_audioSampleRate); m_filters[i].antialiasing.setCutoffFrequency(1900.0f, m_audioSampleRate); } m_levelingFilter.p_target = m_audioParameters.levelerTarget; m_levelingFilter.p_maxLevel = m_audioParameters.levelerMaxGain; m_levelingFilter.p_minLevel = m_audioParameters.levelerMinGain; m_antialiasing.setCutoffFrequency(m_audioSampleRate * 0.45f, m_audioSampleRate); for (int i = 0; i < m_audioBufferSize; ++i) { m_audioBuffer.write(0); } } void Synthesizer::initializeImpulseResponse( const int16_t *impulseResponse, unsigned int samples, float volume, int index) { unsigned int clippedLength = 0; for (unsigned int i = 0; i < samples; ++i) { if (std::abs(impulseResponse[i]) > 100) { clippedLength = i + 1; } } const unsigned int sampleCount = std::min(10000U, clippedLength); m_filters[index].convolution.initialize(sampleCount); for (unsigned int i = 0; i < sampleCount; ++i) { m_filters[index].convolution.getImpulseResponse()[i] = volume * impulseResponse[i] / INT16_MAX; } } void Synthesizer::startAudioRenderingThread() { m_run = true; m_thread = new std::thread(&Synthesizer::audioRenderingThread, this); } void Synthesizer::endAudioRenderingThread() { if (m_thread != nullptr) { m_run = false; endInputBlock(); m_thread->join(); delete m_thread; m_thread = nullptr; } } void Synthesizer::destroy() { m_audioBuffer.destroy(); for (int i = 0; i < m_inputChannelCount; ++i) { m_inputChannels[i].data.destroy(); m_filters[i].convolution.destroy(); } delete[] m_inputChannels; delete[] m_filters; m_inputChannels = nullptr; m_filters = nullptr; m_inputChannelCount = 0; } int Synthesizer::readAudioOutput(int samples, int16_t *buffer) { std::lock_guard lock(m_lock0); const int newDataLength = m_audioBuffer.size(); if (newDataLength >= samples) { m_audioBuffer.readAndRemove(samples, buffer); } else { m_audioBuffer.readAndRemove(newDataLength, buffer); memset( buffer + newDataLength, 0, sizeof(int16_t) * ((size_t)samples - newDataLength)); } const int samplesConsumed = std::min(samples, newDataLength); return samplesConsumed; } void Synthesizer::waitProcessed() { { std::unique_lock lk(m_lock0); m_cv0.wait(lk, [this] { return m_processed; }); } } void Synthesizer::writeInput(const double *data) { m_inputWriteOffset += (double)m_audioSampleRate / m_inputSampleRate; if (m_inputWriteOffset >= (double)m_inputBufferSize) { m_inputWriteOffset -= (double)m_inputBufferSize; } for (int i = 0; i < m_inputChannelCount; ++i) { RingBuffer &buffer = m_inputChannels[i].data; const double lastInputSample = m_inputChannels[i].lastInputSample; const size_t baseIndex = buffer.writeIndex(); const double distance = inputDistance(m_inputWriteOffset, m_lastInputSampleOffset); double s = inputDistance(baseIndex, m_lastInputSampleOffset); for (; s <= distance; s += 1.0) { if (s >= m_inputBufferSize) s -= m_inputBufferSize; const double f = s / distance; const double sample = lastInputSample * (1 - f) + data[i] * f; buffer.write(m_filters[i].antialiasing.fast_f(static_cast(sample))); } m_inputChannels[i].lastInputSample = data[i]; } m_lastInputSampleOffset = m_inputWriteOffset; } void Synthesizer::endInputBlock() { std::unique_lock lk(m_inputLock); for (int i = 0; i < m_inputChannelCount; ++i) { m_inputChannels[i].data.removeBeginning(m_inputSamplesRead); } if (m_inputChannelCount != 0) { m_latency = m_inputChannels[0].data.size(); } m_inputSamplesRead = 0; m_processed = false; lk.unlock(); m_cv0.notify_one(); } void Synthesizer::audioRenderingThread() { while (m_run) { renderAudio(); } } #undef max void Synthesizer::renderAudio() { std::unique_lock lk0(m_lock0); m_cv0.wait(lk0, [this] { const bool inputAvailable = m_inputChannels[0].data.size() > 0 && m_audioBuffer.size() < 2000; return !m_run || (inputAvailable && !m_processed); }); const int n = std::min( std::max(0, 2000 - (int)m_audioBuffer.size()), (int)m_inputChannels[0].data.size()); for (int i = 0; i < m_inputChannelCount; ++i) { m_inputChannels[i].data.read(n, m_inputChannels[i].transferBuffer); } m_inputSamplesRead = n; m_processed = true; lk0.unlock(); for (int i = 0; i < m_inputChannelCount; ++i) { m_filters[i].airNoiseLowPass.setCutoffFrequency( static_cast(m_audioParameters.airNoiseFrequencyCutoff), m_audioSampleRate); m_filters[i].jitterFilter.setJitterScale(m_audioParameters.inputSampleNoise); } for (int i = 0; i < n; ++i) { m_audioBuffer.write(renderAudio(i)); } m_cv0.notify_one(); } double Synthesizer::getLatency() const { return (double)m_latency / m_audioSampleRate; } int Synthesizer::inputDelta(int s1, int s0) const { return (s1 < s0) ? m_inputBufferSize - s0 + s1 : s1 - s0; } double Synthesizer::inputDistance(double s1, double s0) const { return (s1 < s0) ? (double)m_inputBufferSize - s0 + s1 : s1 - s0; } void Synthesizer::setInputSampleRate(double sampleRate) { if (sampleRate != m_inputSampleRate) { std::lock_guard lock(m_lock0); m_inputSampleRate = sampleRate; } } int16_t Synthesizer::renderAudio(int inputSample) { const float airNoise = m_audioParameters.airNoise; const float dF_F_mix = m_audioParameters.dF_F_mix; const float convAmount = m_audioParameters.convolution; float signal = 0; for (int i = 0; i < m_inputChannelCount; ++i) { const float r_0 = 2.0 * ((double)rand() / RAND_MAX) - 1.0; const float jitteredSample = m_filters[i].jitterFilter.fast_f(m_inputChannels[i].transferBuffer[inputSample]); const float f_in = jitteredSample; const float f_dc = m_filters[i].inputDcFilter.fast_f(f_in); const float f = f_in - f_dc; const float f_p = m_filters[i].derivative.f(f_in); const float noise = 2.0 * ((double)rand() / RAND_MAX) - 1.0; const float r = m_filters->airNoiseLowPass.fast_f(noise); const float r_mixed = airNoise * r + (1 - airNoise); float v_in = f_p * dF_F_mix + f * r_mixed * (1 - dF_F_mix); if (fpclassify(v_in) == FP_SUBNORMAL) { v_in = 0; } const float v = convAmount * m_filters[i].convolution.f(v_in) + (1 - convAmount) * v_in; signal += v; } signal = m_antialiasing.fast_f(signal); m_levelingFilter.p_target = m_audioParameters.levelerTarget; const float v_leveled = m_levelingFilter.f(signal) * m_audioParameters.volume; int r_int = std::lround(v_leveled); if (r_int > INT16_MAX) { r_int = INT16_MAX; } else if (r_int < INT16_MIN) { r_int = INT16_MIN; } return static_cast(r_int); } double Synthesizer::getLevelerGain() { std::lock_guard lock(m_lock0); return m_levelingFilter.getAttenuation(); } Synthesizer::AudioParameters Synthesizer::getAudioParameters() { std::lock_guard lock(m_lock0); return m_audioParameters; } void Synthesizer::setAudioParameters(const AudioParameters ¶ms) { std::lock_guard lock(m_lock0); m_audioParameters = params; } ================================================ FILE: src/throttle.cpp ================================================ #include "../include/throttle.h" Throttle::Throttle() { m_speedControl = 0; } Throttle::~Throttle() { /* void */ } void Throttle::setSpeedControl(double s) { m_speedControl = s; } void Throttle::update(double dt, Engine *engine) { /* void */ } ================================================ FILE: src/throttle_display.cpp ================================================ #include "../include/throttle_display.h" #include "../include/geometry_generator.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" ThrottleDisplay::ThrottleDisplay() { m_engine = nullptr; } ThrottleDisplay::~ThrottleDisplay() { /* void */ } void ThrottleDisplay::initialize(EngineSimApplication *app) { UiElement::initialize(app); } void ThrottleDisplay::destroy() { UiElement::destroy(); } void ThrottleDisplay::update(float dt) { UiElement::update(dt); } void ThrottleDisplay::render() { UiElement::render(); drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), m_app->getBackgroundColor()); const Bounds bounds = m_bounds.inset(10.0f); const Bounds title = bounds.verticalSplit(1.0f, 0.9f); drawCenteredText("THROTTLE", title.inset(10.0f), 24.0f); const Bounds mainDrawArea = bounds.verticalSplit(0.05f, 0.9f); renderThrottle(mainDrawArea); const Bounds speedControlDrawArea = bounds.verticalSplit(0.0f, 0.05f); renderSpeedControl(speedControlDrawArea); } void ThrottleDisplay::renderThrottle(const Bounds &bounds) { GeometryGenerator *gen = m_app->getGeometryGenerator(); const float width = pixelsToUnits(bounds.width()); const float height = pixelsToUnits(bounds.height()); const float size = std::fmin(width, height); const Point origin = getRenderPoint(bounds.getPosition(Bounds::center)); const float carbBore = size * 0.4f; const float carbHeight = size * 0.5f; const float plateWidth = carbBore * 0.8f; GeometryGenerator::Line2dParameters params; params.lineWidth = size * 0.01f; GeometryGenerator::Circle2dParameters circleParams; circleParams.radius = params.lineWidth / 2.0f; circleParams.maxEdgeLength = m_app->pixelsToUnits(5.0f); GeometryGenerator::GeometryIndices main, pivot, pivotShadow; gen->startShape(); params.x0 = origin.x + carbBore / 2.0f; params.y0 = origin.y - carbHeight / 2.0f; params.x1 = origin.x + carbBore / 2.0f; params.y1 = origin.y + carbHeight / 2.0f; gen->generateLine2d(params); circleParams.center_x = params.x1; circleParams.center_y = params.y1; gen->generateCircle2d(circleParams); circleParams.center_x = params.x1; circleParams.center_y = params.y0; gen->generateCircle2d(circleParams); params.x0 = origin.x - carbBore / 2.0f; params.x1 = origin.x - carbBore / 2.0f; gen->generateLine2d(params); circleParams.center_x = params.x1; circleParams.center_y = params.y1; gen->generateCircle2d(circleParams); circleParams.center_x = params.x1; circleParams.center_y = params.y0; gen->generateCircle2d(circleParams); // Draw throttle plate const float throttleAngle = (m_engine == nullptr) ? 0.0f : static_cast(m_engine->getThrottlePlateAngle()); const float cos_theta = std::cosf(throttleAngle); const float sin_theta = std::sinf(throttleAngle); params.y0 = origin.y - sin_theta * plateWidth / 2.0f; params.x0 = origin.x - cos_theta * plateWidth / 2.0f; params.y1 = origin.y + sin_theta * plateWidth / 2.0f; params.x1 = origin.x + cos_theta * plateWidth / 2.0f; gen->generateLine2d(params); circleParams.center_x = params.x0; circleParams.center_y = params.y0; gen->generateCircle2d(circleParams); circleParams.center_x = params.x1; circleParams.center_y = params.y1; gen->generateCircle2d(circleParams); gen->endShape(&main); gen->startShape(); circleParams.center_x = origin.x; circleParams.center_y = origin.y; circleParams.radius = size * 0.01f; gen->generateCircle2d(circleParams); gen->endShape(&pivot); gen->startShape(); circleParams.center_x = origin.x; circleParams.center_y = origin.y; circleParams.radius = 2 * size * 0.01f; gen->generateCircle2d(circleParams); gen->endShape(&pivotShadow); m_app->getShaders()->SetBaseColor(m_app->getForegroundColor()); m_app->drawGenerated(main, 0x11, m_app->getShaders()->GetUiFlags()); m_app->getShaders()->SetBaseColor(m_app->getBackgroundColor()); m_app->drawGenerated(pivotShadow, 0x11, m_app->getShaders()->GetUiFlags()); m_app->getShaders()->SetBaseColor(m_app->getForegroundColor()); m_app->drawGenerated(pivot, 0x11, m_app->getShaders()->GetUiFlags()); } void ThrottleDisplay::renderSpeedControl(const Bounds &bounds) { GeometryGenerator *gen = m_app->getGeometryGenerator(); Grid grid; grid.v_cells = 1; grid.h_cells = 3; const Bounds b = grid.get(bounds, 1, 0); const Point lm = b.getPosition(Bounds::lm); const Point rm = b.getPosition(Bounds::rm); const float s = (m_engine != nullptr) ? static_cast(m_engine->getSpeedControl()) : 0; const Bounds bar = Bounds(b.width(), 2.0f, lm, Bounds::lm); const Bounds speedControlBar = Bounds(b.width() * s, 2.0f, lm, Bounds::lm); drawFrame( bar, 1.0f, mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.001f), mix(m_app->getBackgroundColor(), m_app->getRed(), 0.01f) ); drawBox( speedControlBar, m_app->getRed() ); } ================================================ FILE: src/transmission.cpp ================================================ #include "../include/transmission.h" #include "../include/units.h" #include Transmission::Transmission() { m_gear = -1; m_newGear = -1; m_gearCount = 0; m_gearRatios = nullptr; m_maxClutchTorque = units::torque(1000.0, units::ft_lb); m_rotatingMass = nullptr; m_vehicle = nullptr; m_clutchPressure = 0.0; } Transmission::~Transmission() { if (m_gearRatios != nullptr) { delete[] m_gearRatios; } m_gearRatios = nullptr; } void Transmission::initialize(const Parameters ¶ms) { m_gearCount = params.GearCount; m_maxClutchTorque = params.MaxClutchTorque; m_gearRatios = new double[params.GearCount]; memcpy(m_gearRatios, params.GearRatios, sizeof(double) * m_gearCount); } void Transmission::update(double dt) { if (m_gear == -1) { m_clutchConstraint.m_minTorque = 0; m_clutchConstraint.m_maxTorque = 0; } else { m_clutchConstraint.m_minTorque = -m_maxClutchTorque * m_clutchPressure; m_clutchConstraint.m_maxTorque = m_maxClutchTorque * m_clutchPressure; } } void Transmission::addToSystem( atg_scs::RigidBodySystem *system, atg_scs::RigidBody *rotatingMass, Vehicle *vehicle, Engine *engine) { m_rotatingMass = rotatingMass; m_vehicle = vehicle; m_clutchConstraint.setBody1(&engine->getOutputCrankshaft()->m_body); m_clutchConstraint.setBody2(m_rotatingMass); system->addConstraint(&m_clutchConstraint); } void Transmission::changeGear(int newGear) { if (newGear < -1 || newGear >= m_gearCount) return; else if (newGear != -1) { const double m_car = m_vehicle->getMass(); const double gear_ratio = m_gearRatios[newGear]; const double diff_ratio = m_vehicle->getDiffRatio(); const double tire_radius = m_vehicle->getTireRadius(); const double f = tire_radius / (diff_ratio * gear_ratio); const double new_I = m_car * f * f; const double E_r = 0.5 * m_rotatingMass->I * m_rotatingMass->v_theta * m_rotatingMass->v_theta; const double new_v_theta = m_rotatingMass->v_theta < 0 ? -std::sqrt(E_r * 2 / new_I) : std::sqrt(E_r * 2 / new_I); m_rotatingMass->I = new_I; m_rotatingMass->p_x = m_rotatingMass->p_y = 0; m_rotatingMass->m = m_car; m_rotatingMass->v_theta = new_v_theta; } m_gear = newGear; } ================================================ FILE: src/ui_button.cpp ================================================ #include "../include/ui_button.h" #include "../include/engine_sim_application.h" #include "../include/ui_utilities.h" UiButton::UiButton() { m_text = ""; m_fontSize = 12; m_checkMouse = true; } UiButton::~UiButton() { /* void */ } void UiButton::update(float dt) { m_mouseBounds = m_bounds; } void UiButton::render() { ysVector color = m_app->getBackgroundColor(); if (isMouseHeld()) { color = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.02f); } else if (isMouseOver()) { color = mix(m_app->getBackgroundColor(), m_app->getForegroundColor(), 0.01f); } drawFrame(m_bounds, 1.0, m_app->getForegroundColor(), color); drawCenteredText(m_text, m_bounds, m_fontSize); } ================================================ FILE: src/ui_element.cpp ================================================ #include "../include/ui_element.h" #include "../include/engine_sim_application.h" #include UiElement::UiElement() { m_app = nullptr; m_parent = nullptr; m_signalTarget = nullptr; m_checkMouse = false; m_disabled = false; m_index = -1; m_draggable = false; m_mouseOver = false; m_mouseHeld = false; m_visible = true; } UiElement::~UiElement() { /* void */ } void UiElement::initialize(EngineSimApplication *app) { m_app = app; } void UiElement::destroy() { for (UiElement *child : m_children) { child->destroy(); delete child; } m_children.clear(); } void UiElement::update(float dt) { for (UiElement *child : m_children) { child->update(dt); } } void UiElement::render() { for (UiElement *child : m_children) { if (child->isVisible()) child->render(); } } void UiElement::signal(UiElement *element, Event event) { /* void */ } void UiElement::onMouseDown(const Point &mouseLocal) { m_mouseHeld = true; } void UiElement::onMouseUp(const Point &mouseLocal) { m_mouseHeld = false; } void UiElement::onMouseClick(const Point &mouseLocal) { signal(Event::Clicked); } void UiElement::onDrag(const Point &p0, const Point &mouse0, const Point &mouse) { if (m_draggable) { m_localPosition = p0 + (mouse - mouse0); } } void UiElement::onMouseOver(const Point &mouseLocal) { m_mouseOver = true; } void UiElement::onMouseLeave() { m_mouseOver = false; } void UiElement::onMouseScroll(int mouseScroll) { /* void */ } UiElement *UiElement::mouseOver(const Point &mouseLocal) { if (m_disabled) return nullptr; const int n = (int)getChildCount(); for (int i = n - 1; i >= 0; --i) { UiElement *child = m_children[i]; UiElement *clickedElement = child->mouseOver(mouseLocal - child->m_localPosition); if (clickedElement != nullptr) { return clickedElement; } } return (m_checkMouse && m_mouseBounds.overlaps(mouseLocal)) ? this : nullptr; } Point UiElement::getWorldPosition() const { return (m_parent != nullptr) ? m_parent->getWorldPosition() + m_localPosition : m_localPosition; } void UiElement::setLocalPosition(const Point &p, const Point &ref) { const Point current = m_bounds.getPosition(ref) + m_localPosition; m_localPosition += (p - current); } void UiElement::bringToFront(UiElement *element) { assert(element->m_parent == this); m_children.erase(m_children.begin() + element->m_index); m_children.push_back(element); int i = 0; for (UiElement *element : m_children) { element->m_index = i++; } } void UiElement::activate() { if (m_parent != nullptr) { m_parent->bringToFront(this); m_parent->activate(); } } void UiElement::signal(Event event) { if (m_signalTarget == nullptr) return; m_signalTarget->signal(this, event); } float UiElement::pixelsToUnits(float length) const { return length;// m_app->pixelsToUnits(length); } Point UiElement::pixelsToUnits(const Point &p) const { return { pixelsToUnits(p.x), pixelsToUnits(p.y) }; } float UiElement::unitsToPixels(float x) const { return x; // m_app->unitsToPixels(x); } Point UiElement::unitsToPixels(const Point &p) const { return { unitsToPixels(p.x), unitsToPixels(p.y) }; } Point UiElement::getRenderPoint(const Point &p) const { const Point offset( -(float)m_app->getScreenWidth() / 2, -(float)m_app->getScreenHeight() / 2); const Point posPixels = localToWorld(p) + offset; return pixelsToUnits(posPixels); } Bounds UiElement::getRenderBounds(const Bounds &b) const { return { getRenderPoint(b.m0), getRenderPoint(b.m1) }; } Bounds UiElement::unitsToPixels(const Bounds &b) const { return { unitsToPixels(b.m0), unitsToPixels(b.m1) }; } void UiElement::resetShader() { m_app->getShaders()->ResetBaseColor(); m_app->getShaders()->SetObjectTransform(ysMath::LoadIdentity()); } void UiElement::drawModel( dbasic::ModelAsset *model, const ysVector &color, const Point &p, const Point &s) { resetShader(); const Point p_render = getRenderPoint(p); const Point s_render = pixelsToUnits(s); m_app->getShaders()->SetObjectTransform( ysMath::MatMult( ysMath::TranslationTransform(ysMath::LoadVector(p_render.x, p_render.y, 0.0)), ysMath::ScaleTransform(ysMath::LoadVector(s_render.x, s_render.y, 0.0)) ) ); m_app->getShaders()->SetBaseColor(color); m_app->getEngine()->DrawModel(m_app->getShaders()->GetUiFlags(), model, 0x11); } void UiElement::drawFrame( const Bounds &bounds, float thickness, const ysVector &frameColor, const ysVector &fillColor, bool fill) { GeometryGenerator *generator = m_app->getGeometryGenerator(); const Bounds worldBounds = getRenderBounds(bounds); const Point position = worldBounds.getPosition(Bounds::center); GeometryGenerator::FrameParameters params; params.frameWidth = worldBounds.width(); params.frameHeight = worldBounds.height(); params.lineWidth = pixelsToUnits(thickness); params.x = position.x; params.y = position.y; GeometryGenerator::Line2dParameters lineParams; lineParams.lineWidth = worldBounds.height(); lineParams.y0 = lineParams.y1 = worldBounds.getPosition(Bounds::center).y; lineParams.x0 = worldBounds.left(); lineParams.x1 = worldBounds.right(); GeometryGenerator::GeometryIndices frame, body; resetShader(); if (fill) { generator->startShape(); generator->generateLine2d(lineParams); generator->endShape(&body); m_app->getShaders()->SetBaseColor(fillColor); m_app->drawGenerated(body, 0x11, m_app->getShaders()->GetUiFlags()); } generator->startShape(); generator->generateFrame(params); generator->endShape(&frame); m_app->getShaders()->SetBaseColor(frameColor); m_app->drawGenerated(frame, 0x11, m_app->getShaders()->GetUiFlags()); } void UiElement::drawBox(const Bounds &bounds, const ysVector &fillColor) { GeometryGenerator *generator = m_app->getGeometryGenerator(); const Bounds worldBounds = getRenderBounds(bounds); GeometryGenerator::Line2dParameters lineParams; lineParams.lineWidth = worldBounds.height(); lineParams.y0 = lineParams.y1 = worldBounds.getPosition(Bounds::center).y; lineParams.x0 = worldBounds.left(); lineParams.x1 = worldBounds.right(); GeometryGenerator::GeometryIndices body; generator->startShape(); generator->generateLine2d(lineParams); generator->endShape(&body); resetShader(); m_app->getShaders()->SetBaseColor(fillColor); m_app->drawGenerated(body, 0x11, m_app->getShaders()->GetUiFlags()); } void UiElement::drawText( const std::string &s, const Bounds &bounds, float height, const Point &ref) { const Bounds renderBounds = unitsToPixels(getRenderBounds(bounds)); const Point origin = renderBounds.getPosition(ref); m_app->getTextRenderer()->RenderText( s, origin.x, origin.y - height / 4, height); } void UiElement::drawAlignedText( const std::string &s, const Bounds &bounds, float height, const Point &ref, const Point &refText) { const Bounds renderBounds = unitsToPixels(getRenderBounds(bounds)); const Point origin = renderBounds.getPosition(ref); const float textWidth = m_app->getTextRenderer()->CalculateWidth(s, height); const float textHeight = height; const Bounds textBounds( textWidth, textHeight, { 0.0f, textHeight - textHeight * 0.25f }, Bounds::tl); const Point r = textBounds.getPosition(refText); m_app->getTextRenderer()->RenderText( s, origin.x - r.x, origin.y - r.y, height); } void UiElement::drawCenteredText( const std::string &s, const Bounds &bounds, float height, const Point &ref) { const Bounds renderBounds = unitsToPixels(getRenderBounds(bounds)); const Point origin = renderBounds.getPosition(ref); const float width = m_app->getTextRenderer()->CalculateWidth(s, height); m_app->getTextRenderer()->RenderText( s, origin.x - width / 2, origin.y - height / 4, height); } ================================================ FILE: src/ui_manager.cpp ================================================ #include "../include/ui_manager.h" #include "../include/engine_sim_application.h" UiManager::UiManager() { m_app = nullptr; m_dragStart = nullptr; m_hover = nullptr; m_lastMouseScroll = 0; } UiManager::~UiManager() { /* void */ } void UiManager::initialize(EngineSimApplication *app) { m_app = app; m_root.initialize(app); } void UiManager::destroy() { m_root.destroy(); m_hover = nullptr; m_dragStart = nullptr; m_app = nullptr; } void UiManager::update(float dt) { m_root.update(dt); int mouse_x, mouse_y; m_app->getEngine()->GetOsMousePos(&mouse_x, &mouse_y); Point mousePos = { (float)mouse_x, (float)mouse_y }; UiElement *newHover = m_root.mouseOver(mousePos); if (newHover != m_hover) { if (m_hover != nullptr) m_hover->onMouseLeave(); if (newHover != nullptr) newHover->onMouseOver(mousePos); m_hover = newHover; } if (m_app->getEngine()->ProcessMouseButtonDown(ysMouse::Button::Left)) { m_dragStart = m_hover; m_mouse_p0 = mousePos; if (m_dragStart != nullptr) { m_drag_p0 = m_dragStart->getLocalPosition(); m_dragStart->onMouseDown(m_dragStart->worldToLocal(mousePos)); } } else if (m_app->getEngine()->ProcessMouseButtonUp(ysMouse::Button::Left)) { UiElement *dragRelease = m_hover; if (m_dragStart != nullptr) m_dragStart->onMouseUp(mousePos); if (dragRelease != nullptr && m_dragStart == dragRelease) { m_dragStart->onMouseClick(m_dragStart->worldToLocal(mousePos)); } m_dragStart = nullptr; } const int newMouseScroll = m_app->getEngine()->GetMouseWheel(); if (m_lastMouseScroll != newMouseScroll) { if (m_hover != nullptr) { m_hover->onMouseScroll(newMouseScroll - m_lastMouseScroll); } m_lastMouseScroll = newMouseScroll; } if (m_dragStart != nullptr) { m_dragStart->onDrag(m_drag_p0, m_mouse_p0, mousePos); } } void UiManager::render() { m_root.render(); } ================================================ FILE: src/ui_math.cpp ================================================ #include "../include/ui_math.h" const Point Bounds::center = { 0.5f, 0.5f }; const Point Bounds::tl = { 0.0f, 1.0f }; const Point Bounds::tr = { 1.0f, 0.0f }; const Point Bounds::tm = { 0.5f, 1.0f }; const Point Bounds::bl = { 0.0f, 0.0f }; const Point Bounds::br = { 1.0f, 0.0f }; const Point Bounds::bm = { 0.5f, 0.0f }; const Point Bounds::lm = { 0.0f, 0.5f }; const Point Bounds::rm = { 1.0f, 0.5f }; ================================================ FILE: src/ui_utilities.cpp ================================================ #include "../include/ui_utilities.h" ysVector mix(const ysVector &c1, const ysVector &c2, float s) { return ysMath::Add( ysMath::Mul(c1, ysMath::LoadScalar(1 - s)), ysMath::Mul(c2, ysMath::LoadScalar(s))); } ================================================ FILE: src/utilities.cpp ================================================ #include "../include/utilities.h" #include double modularDistance(double a0, double b0, double mod) { double a, b; if (a0 < b0) { a = a0; b = b0; } else { a = b0; b = a0; } return std::fmin(b - a, a + mod - b); } double positiveMod(double x, double mod) { if (x < 0) { x = std::ceil(-x / mod) * mod + x; } return std::fmod(x, mod); } double erfApproximation(double x) { const double a1 = 0.278393; const double a2 = 0.230389; const double a3 = 0.000972; const double a4 = 0.078108; const double x2 = x * x; const double x3 = x2 * x; const double x4 = x3 * x; const double q = 1 / (1 + a1 * x + a2 * x2 + a3 * x3 + a4 * x4); const double q2 = q * q; const double q4 = q2 * q2; return 1 - q4; } ================================================ FILE: src/valvetrain.cpp ================================================ #include "../include/valvetrain.h" Valvetrain::Valvetrain() { /* void */ } Valvetrain::~Valvetrain() { /* void */ } ================================================ FILE: src/vehicle.cpp ================================================ #include "../include/vehicle.h" #include Vehicle::Vehicle() { m_rotatingMass = nullptr; m_mass = 0; m_dragCoefficient = 0; m_crossSectionArea = 0; m_diffRatio = 0; m_tireRadius = 0; m_travelledDistance = 0; m_rollingResistance = 0; } Vehicle::~Vehicle() { /* void */ } void Vehicle::initialize(const Parameters ¶ms) { m_mass = params.mass; m_dragCoefficient = params.dragCoefficient; m_crossSectionArea = params.crossSectionArea; m_diffRatio = params.diffRatio; m_tireRadius = params.tireRadius; m_rollingResistance = params.rollingResistance; } void Vehicle::update(double dt) { m_travelledDistance += getSpeed() * dt; } void Vehicle::addToSystem(atg_scs::RigidBodySystem *system, atg_scs::RigidBody *rotatingMass) { m_rotatingMass = rotatingMass; } double Vehicle::getSpeed() const { const double E_r = 0.5 * m_rotatingMass->I * m_rotatingMass->v_theta * m_rotatingMass->v_theta; const double vehicleSpeed = std::sqrt(2 * E_r / m_mass); return vehicleSpeed; // E_r = 0.5 * I * v_theta^2 // E_k = 0.5 * m * v^2 } double Vehicle::linearForceToVirtualTorque(double force) const { const double rotationToKineticRatio = std::sqrt(m_rotatingMass->I / m_mass); return rotationToKineticRatio * force; } ================================================ FILE: src/vehicle_drag_constraint.cpp ================================================ #include "../include/vehicle_drag_constraint.h" #include "../include/constants.h" #include "../include/units.h" #include "../include/vehicle.h" VehicleDragConstraint::VehicleDragConstraint() : Constraint(1, 1) { m_ks = 10.0; m_kd = 1.0; m_vehicle = nullptr; } VehicleDragConstraint::~VehicleDragConstraint() { /* void */ } void VehicleDragConstraint::initialize(atg_scs::RigidBody *rotatingMass, Vehicle *vehicle) { m_bodies[0] = rotatingMass; m_vehicle = vehicle; } void VehicleDragConstraint::calculate(Output *output, atg_scs::SystemState *system) { output->C[0] = 0; output->J[0][0] = 0.0; output->J[0][1] = 0.0; output->J[0][2] = -1.0; output->J[0][3] = 0.0; output->J[0][4] = 0.0; output->J[0][5] = 1.0; output->J_dot[0][0] = 0; output->J_dot[0][1] = 0; output->J_dot[0][2] = 0; output->J_dot[0][3] = 0; output->J_dot[0][4] = 0; output->J_dot[0][5] = 0; output->kd[0] = m_kd; output->ks[0] = m_ks; output->v_bias[0] = 0; constexpr double airDensity = units::AirMolecularMass * units::pressure(1.0, units::atm) / (constants::R * units::celcius(25.0)); const double v = m_vehicle->getSpeed(); const double v_squared = v * v; const double c_d = m_vehicle->getDragCoefficient(); const double A = m_vehicle->getCrossSectionArea(); const double rollingResistance = m_vehicle->getRollingResistance(); output->limits[0][0] = -m_vehicle->linearForceToVirtualTorque(rollingResistance + 0.5 * airDensity * v_squared * c_d * A); output->limits[0][1] = 0; } ================================================ FILE: src/vtec_valvetrain.cpp ================================================ #include "../include/vtec_valvetrain.h" #include "../include/engine.h" VtecValvetrain::VtecValvetrain() { m_intakeCamshaft = nullptr; m_exhaustCamshaft = nullptr; m_vtecIntakeCamshaft = nullptr; m_vtecExhaustCamshaft = nullptr; m_engine = nullptr; m_minRpm = 0.0; m_minSpeed = 0.0; m_minThrottlePosition = 0.0; m_manifoldVacuum = 0.0; } VtecValvetrain::~VtecValvetrain() { /* void */ } void VtecValvetrain::initialize(const Parameters ¶meters) { m_intakeCamshaft = parameters.intakeCamshaft; m_exhaustCamshaft = parameters.exhaustCamshaft; m_vtecIntakeCamshaft = parameters.vtecIntakeCamshaft; m_vtecExhaustCamshaft = parameters.vtexExhaustCamshaft; m_minRpm = parameters.minRpm; m_minSpeed = parameters.minSpeed; m_minThrottlePosition = parameters.minThrottlePosition; m_manifoldVacuum = parameters.manifoldVacuum; m_engine = parameters.engine; } double VtecValvetrain::intakeValveLift(int cylinder) { return isVtecEnabled() ? m_vtecIntakeCamshaft->valveLift(cylinder) : m_intakeCamshaft->valveLift(cylinder); } double VtecValvetrain::exhaustValveLift(int cylinder) { return isVtecEnabled() ? m_vtecExhaustCamshaft->valveLift(cylinder) : m_exhaustCamshaft->valveLift(cylinder); } Camshaft *VtecValvetrain::getActiveIntakeCamshaft() { return isVtecEnabled() ? m_vtecIntakeCamshaft : m_intakeCamshaft; } Camshaft *VtecValvetrain::getActiveExhaustCamshaft() { return isVtecEnabled() ? m_vtecExhaustCamshaft : m_exhaustCamshaft; } bool VtecValvetrain::isVtecEnabled() const { return m_engine->getManifoldPressure() > m_manifoldVacuum && m_engine->getSpeed() > m_minRpm && (1 - m_engine->getThrottle()) > m_minThrottlePosition; } ================================================ FILE: test/function_test.cpp ================================================ #include #include "../include/function.h" #include TEST(FunctionTests, FunctionSanityCheck) { Function f; f.initialize(16, 1.0); f.destroy(); } TEST(FunctionTests, FunctionTriangleFilterTest) { Function f; f.initialize(0, 1.0); for (int i = 0; i < 10; ++i) { f.addSample((double)i, (double)i * 2); } EXPECT_NEAR(f.sampleTriangle(-1.0), 0.0, 1E-6); EXPECT_NEAR(f.sampleTriangle(11.0), 18.0, 1E-6); for (int i = 0; i < 10; ++i) { EXPECT_NEAR(f.sampleTriangle((double)i), (double)i * 2, 1E-6); } f.destroy(); } TEST(FunctionTests, FunctionClosestTest) { Function f; f.initialize(0, 1.0); f.addSample(0.0, 1.0); f.addSample(2.0, 1.0); f.addSample(3.0, 1.1); f.addSample(1.0, 1.0); f.addSample(5.0, 10.0); f.addSample(4.0, 9.0); EXPECT_EQ(f.closestSample(2.4), 2); EXPECT_EQ(f.closestSample(6.0), 5); f.destroy(); } TEST(FunctionTests, FunctionRandomAddTest) { Function f; f.initialize(0, 1.0); for (int i = 0; i < 1000; ++i) { f.addSample(rand() % 1000, i); } EXPECT_TRUE(f.isOrdered()); f.destroy(); } TEST(FunctionTests, FunctionGaussianTest) { Function f; f.initialize(0, 1.0); f.addSample(0.0, 1.0); f.addSample(2.0, 1.0); f.addSample(3.0, 5.0); f.addSample(1.0, 1.0); f.addSample(5.0, 10.0); f.addSample(4.0, 9.0); EXPECT_NEAR(f.sampleGaussian(2.0), 1.0, 0.1); EXPECT_NEAR(f.sampleGaussian(4.0), 9.0, 0.3); EXPECT_NEAR(f.sampleGaussian(100.0), 10.0, 1E-3); EXPECT_NEAR(f.sampleGaussian(-100.0), 1.0, 1E-3); for (double s = 2.0; s <= 3.0; s += 0.001) { const double v = f.sampleGaussian(s); std::cerr << v << "\n"; } f.destroy(); } ================================================ FILE: test/gas_system_tests.cpp ================================================ #include #include "../include/gas_system.h" #include "../include/units.h" #include "../include/csv_io.h" #include TEST(GasSystemTests, GasSystemSanity) { GasSystem system; system.initialize(0.0, 0.0, 0.0); } TEST(GasSystemTests, AdiabaticEnergyConservation) { constexpr double pistonArea = units::area(1.0, units::cm2); constexpr double vesselHeight = units::distance(1.0, units::cm); const double compression = vesselHeight * 0.5; const int steps = 10000; GasSystem system; system.initialize( units::pressure(1.0, units::atm), units::volume(1.0, units::cc), units::celcius(25.0) ); const double initialSystemEnergy = system.kineticEnergy(); const double initialMolecules = system.n(); double W = 0.0; double currentPistonHeight = vesselHeight; for (int i = 1; i <= steps; ++i) { const double newPistonHeight = vesselHeight - (compression / steps) * i; const double dH = (currentPistonHeight - newPistonHeight); const double F = system.pressure() * pistonArea; W += F * dH; system.changeVolume((newPistonHeight - currentPistonHeight) * pistonArea); currentPistonHeight = newPistonHeight; } const double finalSystemEnergy = system.kineticEnergy(); const double finalMolecules = system.n(); EXPECT_NEAR(finalMolecules, initialMolecules, 1E-6); EXPECT_NEAR(finalSystemEnergy - initialSystemEnergy, W, 1E-4); } TEST(GasSystemTests, PressureEqualizationEnergyConservation) { GasSystem system1, system2; system1.initialize( units::pressure(1.0, units::atm), units::volume(1000.0, units::cc), units::celcius(25.0) ); system2.initialize( units::pressure(2.0, units::atm), units::volume(1000.0, units::cc), units::celcius(25.0) ); const double initialSystemEnergy = system1.totalEnergy() + system2.totalEnergy(); const double initialMolecules = system1.n() + system2.n(); GasSystem::FlowParameters params; params.k_flow = 0.000001; params.crossSectionArea_0 = 1.0; params.crossSectionArea_1 = 1.0; params.direction_x = 1.0; params.direction_y = 0.0; params.dt = 1.0; params.system_0 = &system1; params.system_1 = &system2; const double dt = 1 / 100.0; const int steps = 1000; for (int i = 1; i <= steps; ++i) { GasSystem::flow(params); } const double finalSystemEnergy = system1.totalEnergy() + system2.totalEnergy(); const double finalMolecules = system1.n() + system2.n(); const double p0 = system1.pressure(); const double p1 = system2.pressure(); EXPECT_NEAR(finalMolecules, initialMolecules, 1E-6); EXPECT_NEAR(finalSystemEnergy, initialSystemEnergy, 1E-4); } TEST(GasSystemTests, PressureEquilibriumMaxFlow) { GasSystem system1, system2; system1.initialize( units::pressure(1.0, units::atm), units::volume(1.0, units::cc), units::celcius(2500.0) ); system2.initialize( units::pressure(2.0, units::atm), units::volume(1.0, units::cc), units::celcius(25.0) ); const double maxFlowIn = system1.pressureEquilibriumMaxFlow(&system2); system1.loseN(maxFlowIn, system1.kineticEnergyPerMol()); system2.gainN(maxFlowIn, system1.kineticEnergyPerMol(), system1.mix()); EXPECT_NEAR(system1.pressure(), system2.pressure(), 1E-6); system1.changePressure(units::pressure(100.0, units::atm)); const double maxFlowOut = system1.pressureEquilibriumMaxFlow(&system2); system1.loseN(maxFlowIn, system1.kineticEnergyPerMol()); system2.gainN(maxFlowIn, system1.kineticEnergyPerMol(), system1.mix()); EXPECT_NEAR(system1.pressure(), system2.pressure(), 1E-6); } TEST(GasSystemTests, PressureEquilibriumMaxFlowInfinite) { GasSystem system1; system1.initialize( units::pressure(1.0, units::atm), units::volume(1.0, units::cc), units::celcius(25.0) ); constexpr double P_env = units::pressure(2.0, units::atm); constexpr double T_env = units::celcius(25.0); const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env); const double E_k_per_mol = GasSystem::kineticEnergyPerMol(T_env, system1.degreesOfFreedom()); system1.gainN(maxFlow, E_k_per_mol); EXPECT_NEAR(system1.pressure(), P_env, 1E-6); } TEST(GasSystemTests, PressureEquilibriumMaxFlowInfiniteOverpressure) { GasSystem system1; system1.initialize( units::pressure(100.0, units::atm), units::volume(1.0, units::m3), units::celcius(2500.0) ); constexpr double P_env = units::pressure(2.0, units::atm); constexpr double T_env = units::celcius(25.0); const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env); system1.loseN(maxFlow, GasSystem::kineticEnergyPerMol(T_env, 5)); EXPECT_NEAR(system1.pressure(), P_env, 1E-6); } TEST(GasSystemTests, FlowVariableVolume) { GasSystem system1; system1.initialize( units::pressure(100.0, units::atm), units::volume(1.0, units::m3), units::celcius(25.0) ); constexpr double P_env = units::pressure(2.0, units::atm); constexpr double T_env = units::celcius(25.0); const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env); constexpr double dV = units::volume(1000000.0, units::cc) / 100; for (int i = 0; i < 100; ++i) { const double flowRate0 = system1.flow(0.01, 1/60.0, units::pressure(0.1, units::atm), units::celcius(25.0)); const double flowRate1 = system1.flow(0.01, 1 / 60.0, units::pressure(0.2, units::atm), units::celcius(25.0)); system1.changeVolume(-dV); system1.changeTemperature(100); std::cerr << flowRate0 << ", " << flowRate1 << "\n"; } } TEST(GasSystemTests, PowerStrokeTest) { GasSystem system1; system1.initialize( units::pressure(100.0, units::atm), units::volume(1.0, units::m3), units::celcius(2000.0) ); constexpr double dV = units::volume(1000000.0, units::cc) / 100; for (int i = 0; i < 100; ++i) { const double flowRate0 = system1.flow(1.0, 1 / 60.0, units::pressure(1.0, units::atm), units::celcius(25.0)); std::cerr << i << ", " << flowRate0 << ", " << system1.pressure() << "\n"; } } TEST(GasSystemTests, IntakeStrokeTest) { atg_csv::CsvData csv; csv.initialize(); csv.m_columns = 3; GasSystem system1, system2; system1.initialize( units::pressure(1.0, units::atm), units::volume(1000.0, units::m3), units::celcius(25.0) ); system2.initialize( units::pressure(1.0, units::atm), units::volume(1.0, units::m3), units::celcius(25.0) ); csv.write("t"); csv.write("n"); csv.write("theoretical"); constexpr double dV = units::volume(4.0, units::m3); constexpr double dt = 0.01; system2.changeVolume(dV * dt * 100); for (int i = 0; i < 100; ++i) { GasSystem::FlowParameters flowParams; flowParams.crossSectionArea_0 = 1.0; flowParams.crossSectionArea_1 = 1.0; flowParams.direction_x = 1.0; flowParams.direction_y = 0.0; flowParams.dt = dt; flowParams.k_flow = GasSystem::k_carb(100000.0); flowParams.system_0 = &system1; flowParams.system_1 = &system2; GasSystem::flow(flowParams); //system2.changeVolume(dV * dt); //system2.changeTemperature(units::celcius(25.0) - system2.temperature()); csv.write(std::to_string(i * dt).c_str()); csv.write(std::to_string(system2.n()).c_str()); csv.write(std::to_string( units::pressure(1.0, units::atm) * system2.volume() / (constants::R * units::celcius(25.0))).c_str()); } csv.m_rows = 100 + 1; csv.writeCsv("intake_stroke.csv"); csv.destroy(); } TEST(GasSystemTests, FlowLimit) { GasSystem system1; system1.initialize( units::pressure(100.0, units::atm), units::volume(1.0, units::m3), units::celcius(2000.0) ); constexpr double P_env = units::pressure(1.0, units::atm); constexpr double T_env = units::celcius(25.0); const double maxFlow = system1.pressureEquilibriumMaxFlow(P_env, T_env); system1.flow(15.0, 10.0, P_env, T_env); EXPECT_NEAR(system1.pressure(), P_env, 1E-6); } TEST(GasSystemTests, IdealGasLaw) { GasSystem system1; system1.initialize( units::pressure(100.0, units::atm), units::volume(1.0, units::m3), units::celcius(2000.0) ); const double PV = system1.pressure() * system1.volume(); const double nRT = system1.n() * constants::R * system1.temperature(); EXPECT_NEAR(PV, nRT, 1E-6); } TEST(GasSystemTests, CompositionSanityCheck) { GasSystem::Mix a, b; a.p_fuel = 1.0; a.p_inert = 1.0; a.p_o2 = 1.0; b.p_fuel = 0.0; b.p_inert = 0.0; b.p_o2 = 0.0; GasSystem system1; system1.initialize( units::pressure(100.0, units::atm), units::volume(100.0, units::m3), units::celcius(2000.0), a ); GasSystem system2; system2.initialize( units::pressure(1.0, units::atm), units::volume(1.0, units::m3), units::celcius(2000.0), b ); const double PV = system1.pressure() * system1.volume(); const double nRT = system1.n() * constants::R * system1.temperature(); GasSystem::FlowParameters params; params.k_flow = 1.0; params.crossSectionArea_0 = 1.0; params.crossSectionArea_1 = 1.0; params.direction_x = 1.0; params.direction_y = 0.0; params.dt = 1 / 60.0; params.system_0 = &system1; params.system_1 = &system2; for (int i = 0; i < 200; ++i) { const double flowRate = GasSystem::flow(params); std::cerr << i << ", " << flowRate << ", " << system1.pressure() << "\n"; } EXPECT_NEAR(system2.mix().p_fuel, 1.0, 2E-2); EXPECT_NEAR(system2.mix().p_inert, 1.0, 2E-2); EXPECT_NEAR(system2.mix().p_o2, 1.0, 2E-2); } TEST(GasSystemTests, ChokedFlowTest) { GasSystem system1; system1.initialize( units::pressure(2.5, units::atm), units::volume(1.0, units::m3), units::celcius(2000.0) ); const double flow_k = GasSystem::flowConstant( units::flow(400, units::scfm), units::pressure(2.5, units::atm), units::pressure(1.5, units::atm), units::celcius(2000.0), GasSystem::heatCapacityRatio(5) ); const double chokedFlow = system1.flowRate( flow_k, system1.pressure(), units::pressure(1.0, units::atm), system1.temperature(), units::celcius(25), GasSystem::heatCapacityRatio(5), GasSystem::chokedFlowLimit(5), GasSystem::chokedFlowRate(5)); const double noncriticalFlow = system1.flowRate( flow_k, system1.pressure(), units::pressure(2.0, units::atm), system1.temperature(), units::celcius(25), GasSystem::heatCapacityRatio(5), GasSystem::chokedFlowLimit(5), GasSystem::chokedFlowRate(5)); const double chokedFlowScfm = units::convert(chokedFlow, units::scfm); const double noncriticalFlowScfm = units::convert(noncriticalFlow, units::scfm); } TEST(GasSystemTests, CfmConversions) { constexpr double standardPressure = units::pressure(1.0, units::atm); constexpr double standardTemp = units::celcius(25.0); constexpr double airDensity = units::AirMolecularMass * (standardPressure * units::volume(1.0, units::m3)) / (constants::R * standardTemp); const double flow_28 = GasSystem::k_28inH2O(300); const double flowRate = GasSystem::flowRate( flow_28, units::pressure(1.0, units::atm), units::pressure(1.0, units::atm) - units::pressure(41.0, units::inH2O), units::celcius(25.0), units::celcius(25.0), GasSystem::heatCapacityRatio(5), GasSystem::chokedFlowLimit(5), GasSystem::chokedFlowRate(5)); const double flowRateCfm = units::convert(flowRate, units::scfm); } TEST(GasSystemTests, FlowRateConstant) { const double flow_k = GasSystem::flowConstant( units::flow(400, units::scfm), units::pressure(2.5, units::atm), units::pressure(0.5, units::atm), units::celcius(2000.0), GasSystem::heatCapacityRatio(5) ); const double flowRate = GasSystem::flowRate( flow_k, units::pressure(2.5, units::atm), units::pressure(2.5 - 0.5, units::atm), units::celcius(2000.0), units::celcius(25), GasSystem::heatCapacityRatio(5), GasSystem::chokedFlowLimit(5), GasSystem::chokedFlowRate(5)); EXPECT_NEAR(flowRate, units::flow(400, units::scfm), 1E-6); } TEST(GasSystemTests, GasVelocityReducesStaticPressure) { atg_csv::CsvData csv; csv.initialize(); csv.m_columns = 6; GasSystem system1, system2; system1.initialize( units::pressure(15, units::psi), units::volume(300, units::cc), units::celcius(25.0) ); system1.setGeometry(units::distance(10, units::cm), units::distance(10, units::cm), 1.0, 0.0); system2.initialize( units::pressure(2, units::psi), units::volume(1.0, units::L), units::celcius(25.0) ); system2.setGeometry(units::distance(10, units::cm), units::distance(2, units::cm), 1.0, 0.0); const double initialSystemEnergy = system1.totalEnergy() + system2.totalEnergy(); const double initialMolecules = system1.n() + system2.n(); GasSystem::FlowParameters params; params.k_flow = GasSystem::k_28inH2O(500.0) * 1.0; params.crossSectionArea_0 = 50.0 * units::cm * units::cm; params.crossSectionArea_1 = 4.0 * units::cm * units::cm; params.direction_x = 1.0; params.direction_y = 0.0; params.dt = 1 / 10000.0; params.system_0 = &system1; params.system_1 = &system2; csv.write("time"); csv.write("P_0"); csv.write("P_1"); csv.write("v_0"); csv.write("v_1"); csv.write("total_energy"); const int steps = 1000; for (int i = 1; i <= steps; ++i) { const double staticPressure = system2.pressure() + system2.dynamicPressure(0.0, 1.0); const double totalPressure = system2.pressure() + system2.dynamicPressure(1.0, 0.0); const double systemEnergy = system1.totalEnergy() + system2.totalEnergy(); const double velocity_x_0 = system1.velocity_x(); const double velocity_x_1 = system2.velocity_x(); const double P_0 = system1.pressure(); const double P_1 = system2.pressure() + system2.dynamicPressure(-1.0, 0.0); GasSystem::flow(params); system1.updateVelocity(params.dt); system2.updateVelocity(params.dt); //system1.dissipateVelocity(params.dt, 0.01); //system2.dissipateVelocity(params.dt, 0.01); ++csv.m_rows; csv.write(std::to_string(i * params.dt).c_str()); csv.write(std::to_string(P_0).c_str()); csv.write(std::to_string(P_1).c_str()); csv.write(std::to_string(velocity_x_0).c_str()); csv.write(std::to_string(velocity_x_1).c_str()); csv.write(std::to_string(systemEnergy).c_str()); } const double finalSystemEnergy = system1.totalEnergy() + system2.totalEnergy(); const double finalMolecules = system1.n() + system2.n(); const double p0 = system1.pressure(); const double p1 = system2.pressure(); EXPECT_NEAR(finalMolecules, initialMolecules, 1E-6); EXPECT_NEAR(finalSystemEnergy, initialSystemEnergy, 1E-4); csv.writeCsv("gas_system_test_output.csv", nullptr, '\t'); csv.destroy(); } TEST(GasSystemTests, GasVelocityProducesScavengingEffect) { atg_csv::CsvData csv; csv.initialize(); csv.m_columns = 7; constexpr double cylinderArea = constants::pi * units::distance(2.0, units::inch) * units::distance(2.0, units::inch); constexpr double tubeArea = constants::pi * units::distance(1.75 / 2, units::inch) * units::distance(1.75 / 2, units::inch); GasSystem system1, system2, atmosphere; system1.initialize( units::pressure(1000, units::psi), units::volume(1000, units::cc), units::celcius(1000.0) ); system1.setGeometry( units::distance(10.0, units::cm), units::distance(1.0, units::cm), 1.0, 0.0); system2.initialize( units::pressure(15, units::psi), tubeArea * units::distance(50.0, units::inch), units::celcius(25.0) ); system2.setGeometry( units::distance(50.0, units::inch), std::sqrt(tubeArea), 1.0, 0.0); atmosphere.initialize( units::pressure(15, units::psi), units::volume(10000, units::m3), units::celcius(25.0) ); const double initialSystemEnergy = system1.totalEnergy() + system2.totalEnergy() + atmosphere.totalEnergy(); const double initialMolecules = system1.n() + system2.n(); const double atmosphereArea = 1000.0; GasSystem::FlowParameters params; params.k_flow = GasSystem::k_28inH2O(230.0) * 1.0; params.direction_x = 1.0; params.direction_y = 0.0; params.dt = 1 / (16 * 4000.0); params.system_0 = &system1; params.system_1 = &system2; csv.write("iteration"); csv.write("time"); csv.write("static_cylinder_pressure"); csv.write("exhaust_pressure"); csv.write("v_0"); csv.write("v_1"); csv.write("exhaust_static_pressure"); const int steps = 10000; for (int i = 1; i <= steps; ++i) { const double staticPressure = system2.pressure() + system2.dynamicPressure(0.0, 1.0); const double totalPressure = system2.pressure() + system2.dynamicPressure(1.0, 0.0); const double systemEnergy0 = system1.totalEnergy() + system2.totalEnergy() + atmosphere.totalEnergy(); const double velocity_x_0 = system1.velocity_x(); const double velocity_x_1 = system2.velocity_x(); const double P_0 = system1.pressure(); const double P_1 = system2.pressure() + system2.dynamicPressure(1.0, 0.0); const double exhaustStaticPressure = system2.pressure(); if (system1.volume() > units::volume(118.0, units::cc)) { system1.changeVolume(-units::volume(200000.0, units::cc) * params.dt); } else { system1.setVolume(units::volume(118.0, units::cc)); } params.system_0 = &system1; params.system_1 = &system2; params.crossSectionArea_0 = cylinderArea; params.crossSectionArea_1 = tubeArea; params.k_flow = GasSystem::k_28inH2O(230.0); GasSystem::flow(params); const double systemEnergy1 = system1.totalEnergy() + system2.totalEnergy() + atmosphere.totalEnergy(); params.system_0 = &system2; params.system_1 = &atmosphere; params.crossSectionArea_0 = tubeArea; params.crossSectionArea_1 = atmosphereArea; params.k_flow = GasSystem::k_carb(1000); GasSystem::flow(params); const double systemEnergy2 = system1.totalEnergy() + system2.totalEnergy() + atmosphere.totalEnergy(); system1.updateVelocity(params.dt); system2.updateVelocity(params.dt); system1.dissipateExcessVelocity(); system2.dissipateExcessVelocity(); ++csv.m_rows; csv.write(std::to_string(i).c_str()); csv.write(std::to_string(i * params.dt).c_str()); csv.write(std::to_string(P_0).c_str()); csv.write(std::to_string(P_1).c_str()); csv.write(std::to_string(velocity_x_0).c_str()); csv.write(std::to_string(velocity_x_1).c_str()); csv.write(std::to_string(exhaustStaticPressure).c_str()); } csv.writeCsv("gas_system_test_output.csv", nullptr, '\t'); csv.destroy(); } TEST(GasSystemTests, GasVelocityProducesRamEffect) { atg_csv::CsvData csv; csv.initialize(); csv.m_columns = 9; constexpr double cylinderArea = constants::pi * units::distance(2.0, units::inch) * units::distance(2.0, units::inch); constexpr double runnerArea = constants::pi * units::distance(0.5, units::inch) * units::distance(0.5, units::inch); GasSystem cylinder, runner, atmosphere; cylinder.initialize( units::pressure(1.0, units::atm), units::volume(118, units::cc), units::celcius(25.0) ); cylinder.setGeometry( units::distance(10.0, units::cm), units::distance(1.0, units::cm), 1.0, 0.0); runner.initialize( units::pressure(1.0, units::atm), units::volume(320, units::cc), units::celcius(25.0) ); runner.setGeometry( units::distance(5.0, units::inch), std::sqrt(runnerArea), 1.0, 0.0); atmosphere.initialize( units::pressure(1.0, units::atm), units::volume(10000, units::m3), units::celcius(25.0) ); const double initialSystemEnergy = cylinder.totalEnergy() + runner.totalEnergy() + atmosphere.totalEnergy(); const double initialMolecules = cylinder.n() + runner.n(); const double atmosphereArea = 1000.0; GasSystem::FlowParameters params; params.dt = 1 / (16 * 4000.0); params.direction_x = 1.0; params.direction_y = 0.0; csv.write("iteration"); csv.write("time"); csv.write("crank_angle"); csv.write("cylinder_volume"); csv.write("n_mol"); csv.write("cylinder_air_velocity"); csv.write("runner_air_velocity"); csv.write("cylinder_pressure"); csv.write("runner_pressure"); constexpr double speed = 3000; // rpm constexpr double stroke = units::distance(4.0, units::inch); double max_n = 0.0; double max_n_angle = 0.0; double max_v = 0.0; double max_v_angle = 0.0; double flow = 1.0; const int steps = 10000; for (int i = 1; i <= steps; ++i) { const double velocity_x_0 = cylinder.velocity_x(); const double velocity_x_1 = runner.velocity_x(); const double P_0 = cylinder.pressure(); const double P_1 = runner.pressure() + runner.dynamicPressure(1.0, 0.0); const double exhaustStaticPressure = runner.pressure(); const double t = i * params.dt; const double pistonHeight = units::distance(0.25, units::inch) + stroke / 2 + (stroke / 2) * -std::cos(2 * constants::pi * (t * (speed / 60))); cylinder.setVolume(pistonHeight * cylinderArea); if (i == 1) { cylinder.changePressure(units::pressure(1.0, units::atm) - cylinder.pressure()); } params.system_0 = &runner; params.system_1 = &cylinder; params.crossSectionArea_0 = runnerArea; params.crossSectionArea_1 = cylinderArea; params.k_flow = GasSystem::k_28inH2O(230.0) * flow; GasSystem::flow(params); params.system_0 = &atmosphere; params.system_1 = &runner; params.crossSectionArea_0 = atmosphereArea; params.crossSectionArea_1 = cylinderArea; params.k_flow = GasSystem::k_carb(500); GasSystem::flow(params); cylinder.updateVelocity(params.dt, 1.0); runner.updateVelocity(params.dt, 0.1); cylinder.dissipateExcessVelocity(); runner.dissipateExcessVelocity(); const double angle = 360 * (t * (speed / 60)); if (cylinder.n() > max_n) { max_n = cylinder.n(); max_n_angle = angle; } if (cylinder.volume() > max_v) { max_v = cylinder.volume(); max_v_angle = angle; } ++csv.m_rows; csv.write(std::to_string(i).c_str()); csv.write(std::to_string(i * params.dt).c_str()); csv.write(std::to_string(angle).c_str()); csv.write(std::to_string(cylinder.volume()).c_str()); csv.write(std::to_string(cylinder.n()).c_str()); csv.write(std::to_string(velocity_x_0).c_str()); csv.write(std::to_string(velocity_x_1).c_str()); csv.write(std::to_string(P_0).c_str()); csv.write(std::to_string(P_1).c_str()); } csv.writeCsv("gas_system_test_output.csv", nullptr, '\t'); csv.destroy(); const double delay = max_n_angle - max_v_angle; const double m_n = max_n; int a = 0; } TEST(GasSystemTests, GasVelocityStabilizesInClosedSystem) { atg_csv::CsvData csv; csv.initialize(); csv.m_columns = 2; GasSystem system1; system1.initialize( units::pressure(100, units::psi), units::volume(1000, units::cc), units::celcius(1000.0) ); system1.setGeometry( units::distance(10.0, units::cm), units::distance(10.0, units::cm), 1.0, 0.0); const double initialSystemEnergy = system1.totalEnergy(); const double initialMolecules = system1.n(); csv.write("time"); csv.write("velocity"); const int steps = 10000; const double dt = 1 / 10000.0; for (int i = 1; i <= steps; ++i) { system1.updateVelocity(dt); ++csv.m_rows; csv.write(std::to_string(i * dt).c_str()); csv.write(std::to_string(system1.velocity_x()).c_str()); } csv.writeCsv("gas_system_test_output.csv", nullptr, '\t'); csv.destroy(); } ================================================ FILE: test/synthesizer_tests.cpp ================================================ #include #include "../include/synthesizer.h" #include using namespace std::chrono_literals; void setupStandardSynthesizer(Synthesizer &synth) { Synthesizer::Parameters params; params.audioBufferSize = 512 * 16; params.audioSampleRate = 16; params.inputBufferSize = 256; params.inputChannelCount = 8; params.inputSampleRate = 32; Synthesizer::AudioParameters audioParams; audioParams.airNoise = 0.0; audioParams.inputSampleNoise = 0.0; audioParams.levelerMaxGain = 1.0; audioParams.levelerMinGain = 1.0; audioParams.dF_F_mix = 0.0; params.initialAudioParameters = audioParams; synth.initialize(params); } void setupSynchronizedSynthesizer(Synthesizer &synth) { Synthesizer::Parameters params; params.audioBufferSize = 512 * 16; params.audioSampleRate = 32; params.inputBufferSize = 1024; params.inputChannelCount = 8; params.inputSampleRate = 32; Synthesizer::AudioParameters audioParams; audioParams.airNoise = 0.0; audioParams.inputSampleNoise = 0.0; audioParams.levelerMaxGain = 1.0; audioParams.levelerMinGain = 1.0; audioParams.dF_F_mix = 0.0; params.initialAudioParameters = audioParams; synth.initialize(params); } TEST(SynthesizerTests, SynthesizerSanityCheck) { Synthesizer synth; setupStandardSynthesizer(synth); synth.destroy(); } /* TEST(SynthesizerTests, SynthesizerConversionTest) { Synthesizer synth; setupStandardSynthesizer(synth); EXPECT_NEAR(synth.inputSampleToTimeOffset(0.0), 0.0, 1E-6); EXPECT_NEAR(synth.inputSampleToTimeOffset(1.0), 1 / 32.0, 1E-6); EXPECT_NEAR(synth.audioSampleToTimeOffset(0), -0.5, 1E-6); synth.destroy(); } TEST(SynthesizerTests, SynthesizerTrimTest) { Synthesizer synth; setupStandardSynthesizer(synth); const double timeOffset0 = synth.audioSampleToTimeOffset(0); synth.trimInput(0.5, false); const double timeOffset1 = synth.audioSampleToTimeOffset(8); EXPECT_NEAR(timeOffset1, timeOffset0, 1E-6); synth.destroy(); } TEST(SynthesizerTests, SynthesizerSampleTest) { Synthesizer synth; setupStandardSynthesizer(synth); for (int i = 0; i < 1024; ++i) { const double v = (double)i; const double data[] = { v, v, v, v, v, v, v, v }; synth.writeInput(data); } const double end_t = 1023 / 32.0; const double v0 = synth.sampleInput(end_t, 0); const double v1 = synth.sampleInput(end_t - 1 / 64.0, 0); EXPECT_NEAR(v0, 1023.0, 1E-6); EXPECT_NEAR(v1, 1022.5, 1E-6); synth.trimInput(0.5); const double v0_trim = synth.sampleInput(end_t, 0); const double v1_trim = synth.sampleInput(end_t - 1 / 64.0, 0); EXPECT_NEAR(v0, v0_trim, 1E-6); EXPECT_NEAR(v1, v1_trim, 1E-6); synth.destroy(); } */ TEST(SynthesizerTests, SynthesizerSystemTestSingleThread) { constexpr int inputSamples = 64; constexpr int outputSamples = 63; Synthesizer synth; setupSynchronizedSynthesizer(synth); int16_t *output = new int16_t[outputSamples]; int totalSamples = 0; for (int i = 0; i < inputSamples;) { for (int j = 0; j < 16; ++j, ++i) { const double v = (double)i; const double data[] = { v, v, v, v, v, v, v, v }; synth.writeInput(data); } synth.endInputBlock(); synth.renderAudio(); totalSamples += synth.readAudioOutput(16, output + totalSamples); int a = 0; } const int rem = synth.readAudioOutput(outputSamples - totalSamples, output + totalSamples); EXPECT_EQ(rem, outputSamples - totalSamples); for (int i = 0; i < 16; ++i) { EXPECT_EQ(output[i], 0); } for (int i = 16; i < outputSamples; ++i) { EXPECT_EQ(output[i], (i - 16) * 10 * 8); } synth.destroy(); delete[] output; } TEST(SynthesizerTests, SynthesizerSystemTest) { constexpr int inputSamples = 1024; constexpr int outputSamples = 1023; Synthesizer synth; setupSynchronizedSynthesizer(synth); synth.startAudioRenderingThread(); int16_t *output = new int16_t[outputSamples]; int totalSamples = 0; for (int i = 0; i < inputSamples;) { for (int j = 0; j < 16; ++j, ++i) { const double v = (double)i; const double data[] = { v, v, v, v, v, v, v, v }; synth.writeInput(data); } const int samplesReturned = synth.readAudioOutput(8, output + totalSamples); totalSamples += samplesReturned; } synth.endInputBlock(); synth.waitProcessed(); const int rem = synth.readAudioOutput(outputSamples - totalSamples, output + totalSamples); EXPECT_EQ(rem, outputSamples - totalSamples); for (int i = 0; i < 16; ++i) { EXPECT_EQ(output[i], 0); } for (int i = 16; i < outputSamples; ++i) { EXPECT_EQ(output[i], std::min(32767, (i - 16) * 10 * 8)); } synth.endAudioRenderingThread(); synth.destroy(); delete[] output; }