Enter the Codebase

ohlidalp

Infamous Developer
Administrator
Developer
This is an entry point for new RoR developers - it describes how RoR currently works, why it sucks and what I'd like to do about it. The reason to write it down right here right now is this excellent contribution from a new developer

** split to multiple posts because of character limit **

Document status:

Early draft, just notes from the top of my head. I'll edit this to add more later. Hopefully I'll compose something meaningful to put to our Doxygen docs.


Design :

  • RoR isn't OOP, it's structured and just makes use of objects. I think OOP has 'one O too many'. However, some previous authors seem to have loved OOP while disregarding common sense.
  • We embrace global variables via 'GVar' mechanism inspired by Quake's cvars - see file 'Application.h' and older 'GlobalEnvironment.h'
  • Our entry point is straightforward 'main()' function - it performs inits and then enters game state loop governed by GVar 'app_state'
  • We use OGRE 3D for rendering - we init engine at startup and shut down on exit. We use it's built-in windowing.
  • Our main simulation loop lives in `RoRFrameListener::EnterSimulationLoop()` and calls OGRE's 'renderOneFrame()' which invokes `frameStarted()` callback (same thread).
  • When loading an actor [aka vehicle/truck/load, see below] ( Truck file format) we load+parse the file first into `RigDef::File` object, then spawn from it. But for historical reasons, the parsed object is not reused.
  • Our physics use Euler integration and run on 2khz frequency. We accumulate time spent rendering and then advance physics. But for historical reasons, some physics calculations run on variable timestep (time since last frame).
  • We employ a threadpool but make limited use of it. Our design is singlethreaded: {1. update sim 2. render 3. repeat}. As part of the 'update sim' phase, we parallelize some tasks but immediately wait for them to complete. Our fancy 'threadpool' is a contribution from mikadou . GitHub
  • We have 2 ways of attaching meshes (3d models) to our actors: 'props' (just positioning) and 'flexbodies' (positioning+deformation, see Truck file format -). Flexbodies work entirely on CPU. On spawn, for each vertex, 3 nodes are selected to form reference space. On runtime, the vertex is positoned relatively to this space and the updated mesh is then uploaded to graphics card.
  • Proper Overview

RoR history (why is it such a mess?)
  • Until v0.4.5, RoR had no main menu and no unloading mechanisms. Usage was: {use RoRConfig to edit RoR.cfg -> start RoR -> choose map -> play (load and "unload" vehicles) -> exit RoR}. Thus none of the subsystems could un-initialize itself, all unloading logic was added later.
  • Until v0.4.5, OGRE's rendering loop (which updates whole game via 'frameStarted()' callback) was started as soon as the game started, and game state detection was done at last minute, so there were "is this initialized yet? (check pointer)" checks everywhere - some are still lying around.
  • Until v0.4.5, loading vehicles was done on the fly while truckfile was being read line by line. This caused heavy dependence on order of definitions, much of it undocumented. Also there was tight coupling with simulation data structures.
  • Same thing with terrains. This is still mostly true.
  • Until v0.4.6 the physics timestep was not constant 0.0005 (2khz) but it slightly varied thanks to nonsense computation logic. Fixed by Ulteq.
  • Until v0.4 RoR was singlethreaded. Ulteq (ulteq . GitHub) added multithreading sometime around Version 0.4.0.7 - but it was just limited.
  • Also see 'development timeline by Aivar' in our archived forum
 
Last edited:
Flaws and what to do about them


  • Terminology: Actors Currently the prevalent naming for our softbody objects is 'truck' because RoR started as heavy truck sim - but there are alternatives in use: {truck/load/Beam(with capital B)/vehicle/whaever}. The unique instance ID is 'trucknum'. Let's refer to simulated softbody objects as 'Actors' instead. It's already somewhat used in upstream and fully cleaned in https://github.com/only-a-ptr/rigs-o.../dev-master-v4
  • Code behind actors: The softbody actors (I'll call them such from now on) comprise of 2 structs: `class Beam` and ancestor `struct rig_t`. This sucks for both terminology and coding. I already removed the duality it in https://github.com/only-a-ptr/rigs-o.../dev-master-v4 but the new 'Actor' class is still a monster.
  • single-and-half-threaded software: We're just a singlethreaded app which sometimes parallelizes something - see 'history' and 'design' above. I want RoR to be truly multithreaded with physics running in parallel with rendering. My scheme: single threadpool, physics tasks are inserted at fixed rate. When time comes for rendering, relevant data are copied from simulation context to rendering context and rendering runs independently from next physics update.
  • OGRE upgrade: We're using outdated OGRE version - 1.9. Current 1.x version is 1.10x magnifficently maintained by Pavel Rojtberg, see 'new and noteworthy' in 1.10 - then there's nextgen OGRE 2.1+, see FAQ on their wiki
  • Unicode: We can't deal with it - there is a mixture of workarounds for this in the code (Ogre::UTFString, MyGUI::UString, broken copypasted snippets). I introduced `*SanitizeUtf8*` functions to at least prevent exceptions. The worst part is we can't deal with Unicode in file paths (I'm worried OGRE itself has problems). Solution: use UTF-8 for everything and build appropriate filesystem API - I already started not long ago: https://github.com/only-a-ptr/rigs-o...latformUtils.h
  • engine/powertrain/drivertrain simulation: Although we're primarlily a land vehicle simulator, our capabilities are ridiculously limited and the code is a quagmire. Motor sim starts in class `BeamEngine` (because Actor is Beam) and continues through a lot of ad-hoc updates elsewhere (brakes and more are done on frame-dependent timestep!!). My idea: split apart raw softbody physics and any external updates (steering, hydraulics, torque, pressure) and perform the updates under const timestep via AngelScript scripting (performance is not a showstopper, tested). I already did some experiments with Lua scripting (times ago) but I got nowhere because of the cross-simthread/mainthread logic and overall fragility (it blew up in my face). Here I at least cleaned up the `BeamEngine` and renamed it to `EngineSim`: https://github.com/only-a-ptr/rigs-o.../dev-master-v4
  • Our physics are a house of madness - we have several collision systems (static heightmap, static terrain objects[boxes-working, polygonal-broken] and other actors) which work together by mixture of directly updating node positions ("nodes" mean "softbody vertices" in RoR jargon: http://docs.rigsofrods.org/gameplay/jargon/) and accumulating forces. It's fragile!!! Basically any code change can cause subtle differences in vehicle handling. Solution: work reasonably - first detect all collisions, compute resulting forces, then apply forces - all in physics timestep, please!
  • We don't do broadphase collision detection. We have a sleep mechanism which does pretty much that, but we don't reuse it's results, possibly because they may get outdated at any moment, see 'house of madness' above. We have collision boxes and "predicted collision boxes" which are both of limited use (house of madness) and the "predicted" ones are really "current ones plus node#0 current velocity" which is garbage although it probably had observable positive results when it's author tested it. Solution: fix house of madness, create actual bounding boxes, use them for broadphase and profit!
  • We mix simulation and Gfx everywhere - heritage of singlethreaded experimental demo. Sometime ago I introduced class `GfxActor` and I'm slowly moving actor's Gfx data+logic there. See 'threading' above.
  • Disregarding CPU cache usage: We don't separate hot and cold data. Our softbody structs are stupidly fat. The original devs had some idea how it works, but instead of doing the one thing that had to be done, they kept slapping things together and adding useless comments: https://github.com/RigsOfRods/rigs-o...Data.h#L29-L35 and workarounds: https://github.com/RigsOfRods/rigs-o...uler.cpp#L1835
  • Terrain editor: We don't have one, see In-game terrain editor mode
  • Mac port: We haven't had one in ages. Theres a contributor willing to work on this ("chilledfrogs" on Discord) but there's a showstopper - for handling input we use OIS which is abandonware (some fragmented mainenance repos exist), but it's traditional to use in OGRE samples (shipped with it's SDK until OGRE 1.10). We must port to SDL.
  • No MP collisions: ... because we use TCP for multiplayer, see https://1024monkeys.wordpress.com/2014/04/01/game-servers-udp-vs-tcp/ - but also because all the other problems with our physics. Solution: Use UDP like John Carmack: http://trac.bookofhook.com/bookofhook/trac.cgi/wiki/Quake3Networking
  • Flexbody+tracks/trans: We waste a lot of RAM->VRAM bandwidth, see details and solution on GitHub
  • Horrible mesh collisions: Our inter-actor collision code is (as far as I can tell) super inefficient - I didn't study it close because of the general fragility of RoR (I'm cleaning up the place first) but it looks like a combination of problems - poor CPU cache utilization and arcane ad-hoc logic. To curb the performance issues, some original wiseguy added 'collision avoidance counters' which effectively limit number of collision tests. See also this glorified workaround: https://github.com/RigsOfRods/rigs-of-rods/pull/626
  • Wasteful flexbodies: They're all updated each frame, even if there's no chance to see them in the rendered image. They're done on CPU and then waste RAM->VRAM bandwidth when uploaded. Solution: do them in vertex shader! Modify the vertex buffer to contain relative positions and the 3 "ref-space" node IDs, upload node positions as constant buffer, do the same calculation that's now done on CPU, and profit!
  • No normal mapping on flexbodies: Technically normals can be computed the same way vertex positions are computed (by storing the normal relative to reference nodes) and there was even some unused code for this, but I deleted it. Flexbodies are bad enough for RAM->VRAM traffic even without the extra 3x4 bytes. Solution: do flexbodies in vertex shader (see above) - then you can calculate normals with no extra cost.
 
Last edited:
Thanks for this great write-up. One thing though, I don't think "Actors" is great terminology either. I know naming is hard. Good names are names which are automatically associated with the right idea/concept of what is being named (for the majority of readers).

To me "Vehicle" seems much more suitable than "Actor". E.g. just search both terms on Wikipedia and and it becomes obvious which one fits better (IMHO). I know "Actor" is also often used in a generic way, unrelated to movies, (e.g. in UML modelling). But that again is too generic in my view, e.g. Actor could also mean the RoR-Bot.
 
@mikadou Good point, but I think I chose well enough, let me explain.

I did consider 'vehicles' but quickly discarded it because it doesn't match neither current nor future use of our softbody objects. Currently they're mostly vehicles, but you can also create 'fixes' like monorail track in NHelens, fixed machines like warehouse gates or simulated objects like traffic cones in Auriga. In the future, I'd like RoR to use softbodies for even more things - vegetation, fences, traffic signs/lights - all those things should be breakable and I know I can boost the performance enough to make it possible.

I chose 'actor' primarily for naming directly in code - because it's somewhat usual term in game development and physics software, see:
- architecture - What is an actor in game development? - Game Development Stack Exchange
- Doc:ES/2.4/Manual/Game Engine/Logic/Actors - BlenderWiki
- 3. Adjusting Actor Physics & Collision | Unreal Engine

As for the RoRbot, I actually think he should become an 'actor' in the sense that he should be equipped with some softbody structure to implicitly interact with other softbody objects (i.e. to be able to enter transport vehicles without any special scripting or other tricks)
 
Last edited:
[MENTION=356]only_a_ptr[/MENTION] Thank you for the explanation.Makes sense to me. In fact, I had also previously thought how cool it would be to have a physically simulated RoR-Bot :) I've always appalled by the huge blob named Beam. Instead I thought there should be vehicle objects which aggregate a Drivetrain object and a Softbody object. Softbodies could of course also be used in other cases, as you explained. As I understand your idea is the vehicles, trees, gates, Ror-Bot etc. are all actors.then.

One more thought: It would be great to have Softbodies and Rigidbodies side-by-side in the simulation. For objects like traffic cones it is probably sufficient to have a Rigidbody without deformation. The collision part is the same for Softbodies and Rigidbodies.
 
....Softbodies and Rigidbodies side-by-side ...it is probably sufficient to have a Rigidbody
with RoR's "highly explosive" softbody dynamics (we use unstable integration), mixing softbodies and rigid bodies is just asking for trouble IMO. And since the collisions would be the same, the lack of deformation would bring no performance benefits - deformation alone is cheap, collisions are expensive. A simple tough softbody will do just fine.

Architecture overview

INIT
  • entry point = classic 'main()' function which does all the inits on main thread
  • application state is tracked by global variables "GVars" in namespace App (see file Application.h)
  • settings from RoR.cfg and command line are parsed by class Settings (see file util\Settings.h) and put to GVars. Those which don't have a GVar yet are kept in class Settings in a hashmap (older legacy method)
  • OGRE is initialized, resources are loaded via OGRE's resource system, OGRE scene manager and camera are created, other inits are done
  • a game state loop is started, controlled by Gvar 'app_state'
MAIN MENU

  • 'main()' invokes 'MainMenu::EnterMainMenuLoop()' which opens menu loop - driven by Gvar 'app_state' again, processes inputs and updates graphics (classic singlethreaded approach)
  • Settings panel (class: GUI::GameSettings, file: gui/panels/GUI_GameSettings.h|cpp) is made in DearIMGUI and immediately updates Gvars , there's no [save] button.
  • MP server selector (file: gui/panels/GUI_MultiplayerSelector.h|cpp) is also in DearIMGUI and uses CURL to fetch serverlist from website (on it's own thread). It enables editing MP settings (GVars) and joining selected server (Gvar 'app_mp_state')
  • MP connection is initiated by func 'MainMenu::MainMenuLoopUpdate()' when 'app_mp_state' has pending value CONNECTED. The heavy lifting is done by functions in namespace Networking (file: network/Network.h|cpp) on it's own thread. Once connected, GUI panel 'MpClientList' (file: gui/panels/GUI_MultiplayerClientList.h|cpp) is displayed. It's an ugly old MyGUI panel.
  • For terrain selector, we use multipurpose panel 'GUI::MainSelector' (file: gui/panels/GUI_MainSelector.h|cpp) set to mode "LoaderType::LT_Terrain" (defined in 'RoRPrerequisites.h')


SIMULATION


  • RoRFrameListener is the main controller - it's created when sim starts and destroyed when it ends
  • Terrainmanager loads "*.terrn2" file (master config)
  • TerrainGeometryManager (config=*.otc) takes care of OGRE's OgreTerrain component (loads heightmap, generates and manages meshed geometry)
  • TerrainObjectManager uses .tobj and .odef files to place static objects on maps (=load OGRE resources, generate collision boxes/meshes and place them)
  • CharacterFactory creates instance of Character - the RoRbot (manages gfx, simplistic physics, networking)
  • BeamFactory keeps list of all softbodies and knows which vehicle the player is using
  • Beam - our softbody actor, handles physics, gfx (flares, props, particles, skidmarks...), sound, gameplay features (commands, hydros, hooks, ties, slidenodes)
  • BeamEngine - simulates engine, transmission (auto/manual) and hydropump.
  • --various vehicle elements-- TurboProp, TurboJet, ScrewProp, Axle, AutoPilot - manage various features (control gfx, sound, simulation, sometimes also physics)
  • FlexBody, FlexMesh, Flexmeshwheel, FlexAirfoil - various classes implementing the "FlexBody" concept. Mostly copypasted and very dirty code inside.

SIMULATION - FLOW:
main function invokes "RoRFramelistener::EnterGameplayLoop()" which hosts the main sim loop. the sim loop periodically calls "OGre::Root::renderOneFrame" which invokes callback function "RoRFrameLstener::frameStarted()" which does the actual updating of the simulation. steps below (all done on main thread = the rendering and input processing thread).


  1. if in multiplayer, network data are processed
  2. inputs are acquired
  3. Flexbody tasks are pushed to threadpool and started (on background)
  4. various gameplay elements are updated - both gfx and simulation of whatever
  5. wait for flexbody tasks to complete (background)
  6. function "BeamFactory::UpdatePhysicsSimulation()" is invoked on background thread (added to threadpool)
  7. wait for flexbody tasks to complete
  8. let rendering begin!
  9. [on background thread] "BeamFactory::UpdatePhysicsSimulation()" pushes tasks to threapool to update individual sotbody physics (intra collisions) and waits for them to complete
  10. [on background thread] "BeamFactory::UpdatePhysicsSimulation()" resolves collisions between softbodies

if you're thinking that physics actually run in parallel with rendering and thus RoR should crash from unguarded access to OGRE, you're right, it should - I don't know why it's not happening, but it definitely causes the "camera stuttering" which is really a race condition between camera update (main thread) physics (threadpool) and flexbodies (other threadpool).

my ideal concept:
SimController - handles all gameplay simulation (only logic + sound, no GFX)
PhysicsWorld - manages raw softbodies - just dynamics and collisions, no gfx. Manages actors and static terrain objects.
Actor - raw softbody
<scripting> - reads and updates flexbodies and other sim state to implement gameplay logic
GfxScene - reads data from SimController (which reads PhysicsWorld) and maintains a visual representation of it.

update flow (simplified):

  1. check network and local player inputs
  2. If it's time to render (FPS limiter), copy out simulation state (N/B positions etc...) from SimContrller/PhysicsWorld to GfxScene and push a rendering task to threadpool. The rendering task will update GfxScene from the copied data and render a frame.
  3. if it's time for another simulation step (2Khz), push a simulation task to threadpool - it will update SimController (+PhysicsWorld, +associated scripts). Rendering will not be affected because it runs on copied data.
  4. Update network and other outputs (OutGauge, force feedback)
 
Last edited:
Nice. I went ahead and started to visualize your ideal concept. I hope you don't mind. You can find it here: GitHub - mikadou/ror-design

main.png

Updated version:
main.png


Where do you think the softbody vertices (nodes) should be stored? I understand, we want to have them all in one place in memory to benefit from caching (e.g. pre-fetching cache-lines). Should this large array be stored in PhysicsWorld?

And what about scripting, does it really need to run at 2kHz?
 
Last edited:
@mikadou Scripting needs to be invoked on 2 ocassions:
- Once per simulation update - to update softbodies or other sim. features - RoR's physics run on 2khz, so the other simulation features and scripting must as well.
- Once per rendered frame - to update GUI and other visuals

I started writing GfxScene proof-of-concept code : Branch "devmaster4-GfxScene-UNTESTED" GitHub I'm focusing mainly on splitting the sim/gfx data - that's the major architecture concern (multithreading). To answer your question: physics data are unchanged, copy is stored in GfxActor. For technical intro, check this code comment at GitHub
 
Last edited:
@only-a-ptr Which aspects of the game do you plan to be scriptable?
I have been thinking that RoR is very much about creative creation of content, but also imagination and role play. It would be great to make scriptable scenarios for maps. For example someone could create a highway crash scenario with multiple subquests. A quest would be something like driving an ambulance from point A to point B (the crash site) and back to point A (the hospital). This could even lead to cooperative multi-player scenarios. This kind of scripting would not need to run at very high frequency (definitely not 2khz). Another thing is: People have always created their own versions of the game (Monster trucks, stock car racing...). Maybe we should just facilitate these efforts by providing an option to create your own main menu. E.g. content creators could implement a career mode in menu for a specific type of game (think rally dakar or similar). In my opinion this should be a scripting language which is mature, widely used and very accessible: Best candidate is probably Python.

For the simulation of the power train, we need something different. Python does not have the required performance. I'm still uncertain if the powertrain simulation really needs to run at 2khz though. Maybe something like 200hz or 400hz would be sufficient. I could also be wrong and 2khz is actually required. Should be implemented flexible and tested IMO. Furthermore I have been thinking that implementing the powertrain with an imperative scripting language is maybe not the best way to go about this. Have you ever considered something like Modelica? It is a language explicitly made for simulating such physical systems. It can also be easily used with graphical modeling environments. See here OMWEBbook
It is more geared towards mechanical engineers than programmers (which I think is good for our purpose).

In this other thread (Project 'NextSim') there was some discussion about how most of the powertrain could also be simulated with node/beam physics. I don't think this is the right way to go. Performance will be worse than dedicated equations and the results definitely less realistic. And also harder to maintain and tweak for content creators. Some stuff simply won't be possible (how to model an electrical battery with n/b?)

Please let me know what you think about these points. Also, other non-developer members of the forum, please feel free to share your thoughts 🙂
 
@mikadou:

{imagination,roleplay,quests,multiplayer} - Sure. Ideally, RoR should be a raw game engine providing softbody dynamics, collisions and some gfx helpers, and let the modders supply the content. All existing features must be kept, of course.

{Python} - absolutely not, for any purpose. I already have experience with it, it's horribly bloated and cumbersome. There are many implementations of it, some even lightweight for embedded platforms (as advertised), but RoR already has Angelscript.

{2khz frequency} You're thinking too hard. :) All of what you're noting is understood implicitly, don't worry. The point is that the script must get a chance to react to any physics update, it doesn't mean that it will actually do something each time. Think of what is really scripting: basically running a FOR-loop over an array of bytes (bytecode), doing a SWITCH and calling some functions. Is it so scary?

{Modelica} - It's an interesting inspiration, but both the language and code are useless to us. It's a huge spec with a complex software behind it - just studying it closely enough would consume enormous amount of effort. RoR is primarily a game, and like you said above, we don't have to be all about trucks and machines.

{Powertrain} - You're right that imperative scripting language is not perfect, but we already have a subsystem for it (AngelScript) and I tested that it's fast enough for the job. It's something that can be actually done in meaningfully close future. And it's extensible - meaning we can revisit your ideas later. I'm not trying to define a final excellent concept of RoR, I'm just deciding what general direction to move forward. And the most important aspect to consider is: can we pull it off?

{NextSim} - Again, you're thinking too hard with insufficient experience. Leave performance to me. :) Also, the idea was to model transmissions/differentials in N/B, not everything :)

At the moment I'm researching what updates are happening to the softbody in a single frame. I want to know how individual updates depend on each other so I can split them apart.

UPDATE: research attached as text file because of character limit
~Fixed - Austin

Code:
ROR MAIN UPDATE LOOP
====================
1. RoRFrameListener::frameStarted()
2. BeamFactory::SyncWithSimThread() -- BeamFactory::m_sim_task->join();
3. in multiplayer -> m_beam_factory.handleStreamData(packets); (multiple options)-> m_trucks[t]->receiveStreamData() -> pushNetwork() ->
TO BE FINISHED
ROR PHYSICS UPDATE
==================
Scenario: singleplayer, 2 active vehicles in game, player in vehicle, threadpool disabled
RoRFrameListener::frameStarted()
    RoRFrameListener::UpdateInputEvents()
        AircraftSimulation::Updatevehicle()
        LandVehicleSimulation::UpdateVehicle()
    ScriptEngine::getSingleton().framestep(dt);
    BeamFactory::update(float dt)
        BeamFactory::UpdateSleepingState(dt);
        Beam::updateDashBoards(dt);
        Beam::ForceFeedbackStep();
        BeamFactory::UpdatePhysicsSimulation()
            FOR EACH ACTOR
                Beam::preUpdatePhysics() -> ((conditional)) moveOrigin();
            FOR EACH PHYSICS STEP
                FOR EACH TRUCK
                    Beam::calcForcesEulerPrepare() ==> calcBeamsInterTruck()
                        FOR EACH INTER-ACTOR BEAM // these are ties and hooks, but not ropes
                            WRITE beam_t: L, maxposstress, maxnegstress, minmaxposnegstress, broken, disabled, strength
                            READ node_t (both ends): AbsPosition, Velocity, contacter, pos
                            WRITE node_t (both ends): Forces 
                            
                    Beam::calcForcesEulerCompute()
                        EngineSim::update() // Note: this is called BeamEngine::update() in upstream, work in progress
                            // no node/beam updates here
                        Beam::calcBeams()
                            READ node_t: RelPosition ,contacter , pos,Velocity
                            WRITE node_t:  Forces
                            READ beam_t: L,k,d,bounded,shortbound,longbound,type,shock,Velocity , strength,   detacher_group , p2truck
                            WRITE beam_t: stress,L, maxposstress, maxnegstress, minmaxposnegstress ,broken,disabled, strength, 
                            READ wheel_t: detacher_group
                            WRITE wheel_t: detached
                            (conditional) Beam::calcShocks2()
                                READ beam_t: L, longbound, shortbound, shock
                                WRITE beam_t:
                                READ shock_t: lastpos, springout, dampout , sprogout, dprogout, springin,
                                              dampin, sbd_spring, sbd_damp, flags, trigger_cmdshort, trigger_switch_state ,
                                              last_debug_state , trigger_enabled , beamid
                                WRITE shock_t: last_debug_state, lastpos, trigger_enabled , trigger_switch_state
                        Beam::hookToggle()
                            READ node_t: id, AbsPosition
                            WRITE node_t: --nothing--
                            WRITE beam_t: p2, p2truck,L,disabled
                            READ beam_t: disabled, commandShort,L,stress,
                        /* forcefeedback */
                        /* mousenode */
                        updateSlideNodeForces()
                            SlideNode::UpdatePosition()
                                READ node_t: AbsPosition
                                READ beam_t: broken
                            SlideNode::UpdateForces()
                                READ beam_t: broken
                                WRITE node_t: Forces
                        calcNodes()
                            READ node_t:  contactless, disable_particles, contacted, disable_sparks, iswheel, mass, locked, 
                            READWRITE node_t: wettime, wetstate, collTestTimer, RelPosition, AbsPosition, Forces,
                            WRITE node_t: --nothing--
                            READ wheel_t: --todo--
                            WRITE wheel_t: lastContactInner, lastContactOuter, isSkidding, lastGroundModel, lastSlip, lastContactType
                        /* bounding boxes*/
                            READ node_t: AbsPosition, collisionBoundingBoxID , Velocity
                        FOR EACH AEROENGINE
                            AeroEngine::updateForces() (virtual function)
                                Turboprop::updateForces()
                                    READ node_t: AbsPosition, Velocity, RelPosition, 
                                    WRITE node_t: Forces
                                Turbojet::updateForces(()
                                    READ node_t: RelPosition, 
                                    WRITE node_t: Forces
                                    
                        FOR EACH SCREWPROP
                            ScrewProp::updateForces
                                READ node_t: RelPosition,   AbsPosition
                                WRITE node_t: Forces
                        FOR EACH WING
                            FlexAirfoil::updateForces
                                READ node_t: RelPosition, Velocity,
                                WRITE node_t: Forces
                        /* fusedrag */
                            READ node_t: RelPosition, Velocity,
                            WRITE node_t: Forces
                        FOR EACH AIRBRAKE
                            airbrake::applyForce
                                READ node_t: Velocity,AbsPosition
                                WRITE node_t: Forces                    
                        FOR EACH BUOYCAB
                            Buoyance::computeNodeForces
                                READ node_t: Velocity,AbsPosition
                                WRITE node_t: Forces   
                        FOR EACH PROPPAIR  // old style axles
                            READ wheel_t: detached, speed
                            WRITE wheel_t: speed,     
                        FOR EACH AXLE // new style axles
                            READ Axle: wheel_1, wheel_2 , delta_rotation
                            WRITE Axle: delta_rotation
                            READ wheel_t: detached, 
                            WRITE wheel_t: speed,    
                            WRITE Actor: intertorque  
                        FOR EACH AXLE // new style axles again  
                            READ wheel_t: detached, 
                            WRITE wheel_t: speed, 
                            READ Axle: wheel_1, wheel_2 
                            WRITE Axle: delta_rotation
                        /*wheels*/
                            READ node 0 Velocity
                            READ EngineSim::getAcc()
                            FOR EACH WHEEL
                                READ Beam: intertorque, 
                                WRITE Beam: antilockbrake ,     
                                READ wheel_t: propulsed, braked, speed, firstLock, lastSpeed, avgSpeed , nbnodes
                                WRITE wheel_t: avgSpeed, firstLock
                                READ node_t: RelPosition , Velocity
                                WRITE node_t: Forces
                            FOR EACH WHEEL
                                WRITE  wheel_t: lastSpeed,speed
                            WRITE Beam: WheelSpeed
                        /*stabilizer shocks*/
                            READ Beam: has_active_shocks, stabcommand, stabratio
                            READ shock_t: flags
                            READ beam_t: refL
                            WRITE beam_t: L
                            READ node_t: RelPosition
                        /*hydros (steering)*/
                            READ Beam: hydrodirstate, hydrodircommand , hydroSpeedCoupling
                            WRITE Beam: hydrodirstate
                        /*rudder (steering)*/
                            READ Beam: hydrorudderstate , hydroruddercommand ,
                            WRITE Beam: hydrorudderstate 
                        /*elevator*/
                            READ Beam: hydroelevatorstate  , hydroelevatorcommand ,
                            WRITE Beam: hydroelevatorstate 
                        /*hydros (steering)*/
                            FOR EACH HYDRO
                                READ Beam: WheelSpeed
                                READ beam_t: hydroFlags , animFlags  , hydroRatio, Lhydro, shortbound, longbound
                                Beam::calcAnimators() // only reading
                                    ANIM_FLAG_BRUDDER
                                        ScrewProp::getRudder
                                    ANIM_FLAG_BTHROTTLE
                                        ScrewProp::getThrottle
                                    ANIM_FLAG_HEADING
                                        READ node_t RelPosition (cameras)
                                    ANIM_FLAG_TORQUE
                                        engine->getCrankFactor();
                                    ANIM_FLAG_SHIFTER
                                        // gears, commandkeys
                                    ANIM_FLAG_SPEEDO
                                        READ Beam WheelSpeed
                                    ANIM_FLAG_AIRSPEED
                                        READ node_t Velocity, AbsPosition (node 0)
                                    ANIM_FLAG_VVI
                                        READ node_t Velocity (node 0)
                                    ANIM_FLAG_ALTIMETER
                                        READ node_t  AbsPosition (node 0)
                                    ANIM_FLAG_AOA
                                        READ wing_t aoa
                                        READ node_t Velocity (node 0)
                                    READ node_t  RelPosition (node 0 or cameras)
                        /*commands*/
                            READ Beam: hascommands
                            WEITE Beam: canwork (ar_engine_hydraulics_ready)
                            FOR EACH COMMAND
                                WRITE beam_t autoMoveLock
                            FOR EACH COMMAND
                                READ command_t: commandValue, beams
                                WRITE command_t: commandValue, triggerInputValue  , commandValueState
                            FOR EACH COMMAND  // now process normal commands
                                READ command_t:  beams , commandValue, commandValueState
                                READ beam_t: isForceRestricted, isCentering, autoMoveLock, L, refL, centerLength ,playsSound
                                             commandLong  , commandShort  , isOnePressMode, autoMovingMode , commandNeedsEngine, commandEngineCoupling  
                                WRITE beam_t: autoMoveLock , autoMovingMode , pressedCenterMode 
                                FOR EACH COMMANDKEY::ROTATOR
                                    READ rotator_t: rotatorNeedsEngine, rotatorEngineCoupling
                                    WRITE 
                        /*rotators*/
                            FOR EACH ROTATOR
                                READ node_t: RelPosition, 
                                WRITE node_t: Forces
                                READ rotator_t: angle, force, tolerance
                        /*ties*/
                            FOR EACH TIE
                                READ tie_t: tying
                                READ beam_t: L, refL, commandShort, commandRatioShort, maxtiestress
                                WRITE beam_t: tying, L
                    //END Beam::calcForcesEulerCompute()
                            
                    Beam::calcForcesEulerFinal()
                        Beam::calcHooks()
                            FOR EACH HOOK
                                READ hook_t: lockNode, locked, lockTruck , lockspeed, maxforce, nodisable
                                WRITE hook_t: locked, lockNode, lockTruck 
                                READ beam_t: disabled, commandShort, L , stress,
                                WRITE beam_t: p2, p2truck, L, disabled,
                                READ node_t: AbsPosition
                            
                        Beam::calcRopes()
                            FOR EACH ROPE
                                READ rope_t: lockedto, beam
                                READWRITE node_t: AbsPosition, RelPosition, Forces, Velocity
                    
                    IF ROLLON ENABLED
                        Beam::IntraPointCD()->update()
                            PointColDetector::update(Beam*, bool) // overloaded function!!!
                                PointColDetector::update_structures_for_contacters()
                                    FOR EACH TRUCK
                                        FOR EACH CONTACTER
                                            READ contacter_t: nodeid
                                            READ node_t: AbsPosition
                                                        
                        intraTruckCollisions()   // global function in file {physics/collision/DynamicCollision.cpp}
                            FOR EACH COLLCAB
                                READ node_t: AbsPosition, iswheel,
                                PointColDetector::query()
                                FOR EACH COLLIDING NODE (aka 'hit') // actually, there's maximum of 1 due to `return` in `queryrec()` - this had been there since the beginning of Git history
                                    ResolveCollisionForces()     // global function in file {physics/collision/DynamicCollision.cpp}
                                        READ node_t: Velocity, Forces
                                        WRITE node_t: Forces
                                        primitiveCollision()   // global function in file {physics/collision/Collision.cpp}
                                            // no external access here                  
                        
                    Beam::InterPointCD()->update()
                    interTruckCollisions()
                        // --todo--
            FOR EACH TRUCK
                Beam::postUpdatePhysics()
                    Beam::calculateAveragePosition()
                        READ node_t: AbsPosition
                        WRITE Beam: position
        // END BeamFactory::UpdatePhysicsSimulation()
    // END BeamFactory::update()
 

Attachments

Last edited by a moderator:
{NextSim} - Again, you're thinking too hard with insufficient experience. Leave performance to me. :) Also, the idea was to model transmissions/differentials in N/B, not everything :)

Small note here... while this is totally possible (differentials are anyways... dunno about transmissions... never tried one of them before) many moders might not grasp exactly how to build something along the lines of a differential purely out of nodes and beams even if they do actually understand how they operate in real life. In games like this (Rigs of Rods, BeamNG, Garrys Mod... other... physics sandbox's if there are any?) usually the easiest way to make a differential is to throw your understanding of how to actually make one out the window and build something called (in the world of Garrys Mod) a Biggles Rotative Drive System or a BRD.

The BRD System (Gearless rotation transfer method) - Stuff You've Made - Garry's Mod

This method, if replicated in Rigs of Rods, not only works beautifully, but also MASSIVELY cuts down on the amount of nodes and beams you would normally use to do this... but again, most people building mods might not know to do this.

So, this brings me to my next point.

While I am all for simulating as many things as possible physically rather than just through code, that and I could care less about performance (I have 24 virtual cores, 32GB's of RAM, and a GTX 1070... it can probably handle just about anything) we do have to think about how to make adding these things into the game easy enough so anyone (well... anyone competent enough to build a truck currently) can do it... otherwise everyone will just try to skirt around having to add these features in at all.

I see two ways (well... 3 if we count building them manually and just having a massive amount of information on the wiki like we do now) that we can do this. Way 1 would be to have these structures loaded into an editor (I remember you saying you desperately wanted to make one anyways) that could be added and scaled to vehicles and then manually connected in as needed.

Way 2 would be to have them automatically built. You pick a node to center it around (speaking purely for the differential right now) define the nodes to stabilize it (could need a few as 4 or as many as 8) and then the game would mathematically add it into the truck upon spawn just like it does with the wheels. Naturally, this would be easiest for the modder, but much more difficult to code in. It is mostly just trig, but you get my point.

I can't help much most other places when it comes to code, but when it comes down to physics, math, and building functioning things, that is my strong point. So I figured I would add that tidbit into the discussion.
 
Flaws and what to do about them, 2023 edition.
This is 1:1 update to this 2018's post. I originally wrote these as colored inline updates to the original post, but I ran into character limit, so I'm posting separately.
  • Terminology: Actors Fixed for good. C++ now uses exclusively `class Actor` and `typedef int ActorInstanceID_t`. Scripting still uses `class BeamClass` for compatibility but comments it clearly.
  • Code behind actors: Fairly complete. `struct rig_t` is history, class Actor was cleaned up signifficantly and I have a proven strategy to make it tip top - see my favourite part of it - I pioneered it as part of a bughunt and now I'm refining it for upstream.
  • single-and-half-threaded software: Done as planned - physics now run on background thread, data relevant to GUI/scene updates are captured as 'SimBuffers' and scene is updated in parallel with the next physics batch - see relevant part of game main loop in main.cpp. There's still room for improvement though, see this GitHub writeup.
  • OGRE upgrade: we're outdated again, this time at OGRE 1.11.6 while latest is effectivelly OGRE 1.14 (numbering changed so it's formally OGRE 14). We have a functional port ready but due to our non-existent geometry batching solution, the Community Map which already performed poorly performs abysmally in the ports. See this thread.
  • Unicode: All fixed on our side except OGRE 1.11x has flawed dependency (zziplib) which breaks archive access on Unicode paths on windows, and we need to migrate to OGRE 1.13 and newer which uses MiniZ.
  • engine/powertrain/drivertrain simulation: Code was cleaned up but it didn't get any more flexible. I still believe scripting will salvage us, see this GitHub draft
  • Our physics are a house of madness Code was signifficantly improved, both static and dynamic collisions now use the same underlying helper (thanks to Ulteq). We still apply collision forces directly though.
  • We don't do broadphase No change here.
  • We mix simulation and Gfx everywhere - The {simulation -> simbuffer -> gfx scene} architecture is firmly in place;See comments in SimBuffer.h
  • Disregarding CPU cache usage: The base `node_t/beam_t` stucts got a lot leaner and visuals were moved to `GfxNode/GfxBeam`. Still a lot of room for improvement.
  • Terrain editor: We have a very crude, hotkey-based editing mode in the game, which dumps the edited file to logs directory. The future is in scripting: the 'road_editor.as' script provides nice GUI-based editing and reloading of roads.
  • Mac port: see GitHub#2981
  • No MP collisions: My current plan is to migrate our netcode as-is to ENet; We also need to do collisions in 2 steps: 1=buffer collision forces, 2=apply collision forces(possibly over network) - I did this as experiment (see the commit notes) once but I botched the local force accumulation (I should have kept max value instead of averaging!!), otherwise we'd probably have MP collisions already.
  • Flexbody+tracks/trans: zero progress here.
  • Horrible mesh collisions: not much changed here. I'm working on incorporating a live profiler to see what I can do here.
  • Wasteful flexbodies: No change here but some research was done.
  • No normal mapping on flexbodies: see above link.
 
Last edited:
Back
Top