Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👼Script: OGRE bindings for editing scene, meshes and textures - with examples #3030

Merged
merged 37 commits into from
Mar 14, 2024

Conversation

ohlidalp
Copy link
Member

@ohlidalp ohlidalp commented Mar 24, 2023

EDIT: This branch grew much beyond the original scope; see The End Goal See also updates at the end of this post.

Also remember:

//     #=====================================================================
//     #              #### WARNING - DANGEROUS API ####
//     #  OGRE objects don't use reference counting
//     #    - __ N E V E R __ keep pointers across `frameStep()` invocations!
//     #  Prefer always looking up the resource from OGRE - slower but safer.
//     #=====================================================================

~~~~Following text is the original opening post:~~~~

Work smarter, not harder.

This started as a Discord debate about shift stick animation and how to make it smooth. I half-jokingly suggested to use skeletal animation in the prop which would be controlled from scripting, and to my surprise that idea was supported. So, the primary goal of this PR is to bind OGRE's skeletal animation controls to AngelScript, for smoother shifting :)

The strong point of OGRE renderer is that the internal API is very well organized and documented. To display a 3D model in scene, you load Ogre::Mesh, use Ogre::SceneManager to create Ogre::Entity of the mesh and attach it to Ogre::SceneNode. To do the skeletal anim, you get Ogre::AnimationState from the entity and that's it - you call setTimePosition() or addTime() to play it. It's all in the docs: https://ogrecave.github.io/ogre/api/1.11/. Basically this is something modders could easily do directly, if it just wasn't C++. Since I already need to bind Ogre::Entity and so on for the primary goal, why not do it from the ground up... so the secondary goal here is to let modders load an arbitrary mesh, position/rotate/scale it in scene ... and play skeletal anims.

Finally, I love diagnostic tools, and OGRE also allows user to enumerate all it's objects and traverse hierarchies. Since I already had to bind Ogre::SceneManager for the secondary goal, the only extra step for complete OGRE integration is to bind Ogre::Root global object. I did so, with a syntax that mimics the C++ side almost indistinguishably. This is a screenshot of the ogre demo script which shows it in action:
obrazek
Before you ask... yes, our scene hierarchy is a complete mess. We have 2 scene managers (technically legit but what's the other for?), the root scene node has 148 direct children and there's an unnamed node with 341 children (WTF? you need 1 node for each prop/flexbody/character/static objects ... but I'm on an empty map with 1 Mini!). Of course, the important point here is that now this internal mess which isn't normally seen (in C++ you use pointers, that's why names are mostly empty) had been brought to daylight.

UPDATE (Corrected 2023-04-01): Secondary goal is met.
You can now load/position/rotate/animate an arbitrary mesh from just AngelScript. The bundled script 'ogre_demo.as' now has 2 sections: Inspector (the above screenshot) and Pose Demo (the below screenshot). To run the script, open in-game console and say loadscript ogre_demo.as.

obrazek

UPDATE 2 (2023-04-01):
I just realized I'm not done here yet. The primary goal explicitly refers to stick shift animation, and even though the animation bit is in place, a data source for the shift status is missing. I'm going to need to add bindings for it, and that means binding the RoR::EngineSim object, which encapsulates engine+gearbox+differential and it's going to be a piece of work by itself. I'm going to create a separate branch for it so I can properly describe and present it, complete with demo script.

UPDATE 3 (2023-05-28):
I decided to separate out the shiftstick/engine part to #3048 and get this merged because I already have other ideas of building on top of this, and it's large enough already.

UPDATE 4 (2023-09-18):
I ended up dealing with the shift stick the simple way: #3065 and in the meantime I also developed more OGRE-AngelScript bindings in the script_editor branch: #3074 (comment) and just now I moved them here.

UPDATE 5 (2024-02-11)
I've finally dealt with the Tuning branch and I'm back on this. I've set an end goal of this branch to optimize performance of our terrains, especially "Community map", and in the process add the ability to generate meshes and textures. See #3030 (comment)

UPDATE 6 (2024-02-26)
All the necessary script extensions are in place, I'm working on the tool

UPDATE 7 (2024-03-14)
I decided to merge this PR "early" (while the advertised TerrnBatcher tool is still in alpha stage) because the bulk of this PR is really the script extensionsw would that enable this tool to happen - but those have a vastly greater potential and opportunities to use it have been springing up all over the place lately.

@ohlidalp
Copy link
Member Author

Progress. I added Ogre::MovableObject to the API and inspector. in OGRE, everything you can render is a movable object - most often 'Entity' but also 'ManualObject', 'ParticleSystem', 'BillboardSet' etc. It also controls the visibility and shadows, so I added checkboxes for it.

Finally, I cleaned the scene graph a lot, at least until you load any mods :) I named the camera/character nodes and grouped particle nodes by type (those took up most of the space):
obrazek

@ohlidalp ohlidalp marked this pull request as ready for review March 26, 2023 00:32
ohlidalp added a commit to ohlidalp/rigs-of-rods that referenced this pull request Apr 9, 2023
It doesn't work this way. I'm comitting anyway because the extra node in the hierarchy will be useful for RigsOfRods#3030 and I think origin + rel. positions in simbuffer will also become handy later. Otherwise I'll just drop this commit.
@willmichals
Copy link

Tested & works fine!

@ohlidalp
Copy link
Member Author

ohlidalp commented Feb 11, 2024

End goal: improve terrain performance

Our terrains suffer of low FPS due to overly high batch count (= number of draw calls). This is because we use many low-poly meshes and materials. The graphics chips and drivers like large meshes with a single material containing a big texture atlas. Number of polygons or texture size doesn't matter - one mesh with single material counting 1M polygons will render faster than multiple meshes (different material each) counting 1K polygons together. The way to go is to simply ditch our existing meshes and generate new ones from their geometry and textures, merging them together for optimal rendering.

There are 3 (EDIT: 4) key ingredients to this:

I've finally dealt with the Tuning branch and I'm back on this. The tuning branch is now merged and brings project support - the ability to create subdirs under Documents/My Games/Rigs of Rods/projects and populate them with files from an existing mod. The intended use was vehicles, but it will work with terrains just the same.

New feature: arbitrary mesh generation

The screenshot below showcases a new example script 'example_ogre_MeshedConcrete.as' (run it using loadscript command in console) which shows how to generate a mesh which follows the shape of the terrain, effectivelly forming a meshed decal. The material used in the example is "taxiwayconcrete" but you can use any material.
obrazek

@ohlidalp
Copy link
Member Author

Texture blitting works!

I've just successfully added the ability to edit textures via AngelScript. This literally modifies the pixels in the texture by specifying source image and destination box. OGRE can do more but this is a start.

The screenshot showcases a new example script 'example_ogre_textureBlitting.as' - you can try it out by opening console (Hotkey ~ or topMenubar->tools->ShowConsole) and using loadscript command. NOTE: the example script only blits to topmost mipmap, so to see the effect, you must be close to the object! This will be improved.
obrazek

Example code:

    // src image
    Ogre::Image gSrcImg = game.loadImageResource("sign-roadnarrows.dds", "TexturesRG");
    // dst texture
    Ogre::TexturePtr gDstTex = Ogre::TextureManager::getSingleton().load("character.dds", "TexturesRG");
    // let the MAGIC happen
    Ogre::HardwarePixelBufferPtr pixbuf = gDstTex.getBuffer(/*cubemap face index:*/0, /*mipmap:*/0);
    Ogre::PixelBox srcPixbox = gSrcImg.getPixelBox(0,0); // getPixelBox(cubemapFaceIndex, mipmapIndex);
    box gDstBox; // where you want to put it.
    pixbuf.blitFromMemory(srcPixbox, gDstBox);

@paroj
Copy link
Contributor

paroj commented Feb 15, 2024

note that Ogre already provides an API to do this, given you can use the same material for all of the entities:
https://ogrecave.github.io/ogre/api/latest/tut__static_geom.html

@ohlidalp
Copy link
Member Author

I'm aware, except that's a no go given the state of our community. We have loads of separate meshes, many using multiple submeshes with separate materials, and a long history of doing stuff exclusively this way.

Writing a custom batching tool is really the only viable way:

  • Blender or anything complex isn't involved, so users won't freak out.
  • 95% of our assets use just diffuse texture, so no complicated material analysis is needed.
  • Being able to gen. Meshes is super versatile - meshed decals, adaptive prefabs, tire tracks and soft ground, snow, dynamic debris, water...
  • being able to blit textures live also gives possibilities: damage, dirt, mud, soaking...

@paroj
Copy link
Contributor

paroj commented Feb 19, 2024

Writing a custom batching tool is really the only viable way:

my point was that you can use StaticGeometry inside your tool to take care of merging the meshes. It already handles SubMeshes and LOD. Your tool would then just have to merge the materials beforehand.

@ohlidalp
Copy link
Member Author

ohlidalp commented Feb 19, 2024

Point taken, thanks, but I'm determined to try by hand first, to explore and demonstrate the possibilities of the script bindings. Our terrain system already relies on generating meshes (procedural roads) and I intend to improve and extend these.

PS: also LODs are mostly non present on meshes produced by our community:D

@paroj
Copy link
Contributor

paroj commented Feb 19, 2024

you might want to consider LOD in the design of this and auto generate them at some point though:
https://youtu.be/hf27qsQPRLQ?si=W4x8w8QCDqJrUdM-&t=705

https://ogrecave.github.io/ogre/api/latest/meshlod-generator.html

ohlidalp added a commit to ohlidalp/rigs-of-rods that referenced this pull request Feb 20, 2024
Task 3/3 from RigsOfRods#3030.
This means I can start writing a script to batch together terrain objects (meshes+textures) for performance - especially for Community Map

New script API:
* objects: `MeshPtr, SubMesh, MeshManager`
* to get vert positions, use "array<vector3>@ __getVertexPositions()" on SubMesh
* ditto for vert texcoords, note the extra index param because mesh can have more than 1 texcoord layer: "array<vector2>@ __getVertexTexcoords(uint index)"

See example script "example_ogre_vertexData.as"
@ohlidalp
Copy link
Member Author

ohlidalp commented Feb 20, 2024

Vertex data reading works

I opted to helper functions here because the raw vertex/index buffer API is involved and most importantly relies on retyping void* pointers which isn't a thing in AngelScript.

New script API:

  • objects: MeshPtr, SubMesh, MeshManager
  • to get vert positions, use "array@ __getVertexPositions()" on SubMesh
  • ditto for vert texcoords, note the extra index param because mesh can have more than 1 texcoord layer: "array@ __getVertexTexcoords(uint index)"

See example script "example_ogre_vertexData.as" (on screenshot) - notice the texture contains a complete face but only one half of it is covered in texcoords. This isn't a bug in the tool but a quirk of the mesh - it actually uses that one half of texture to cover both halves of the face.
obrazek

ohlidalp added a commit to ohlidalp/rigs-of-rods that referenced this pull request Feb 26, 2024
Task 3/3 from RigsOfRods#3030.
This means I can start writing a script to batch together terrain objects (meshes+textures) for performance - especially for Community Map

New script API:
* objects: `MeshPtr, SubMesh, MeshManager`
* to get vert positions, use "array<vector3>@ __getVertexPositions()" on SubMesh
* ditto for vert texcoords, note the extra index param because mesh can have more than 1 texcoord layer: "array<vector2>@ __getVertexTexcoords(uint index)"

See example script "example_ogre_vertexData.as"
ohlidalp added a commit to ohlidalp/rigs-of-rods that referenced this pull request Feb 26, 2024
This introduces 'example_ogre_terrnBatcher.as' which shows how Mesh/Material API works and will evolve into a production tool. this will conclude RigsOfRods#3030.

THE INTENDED USE of the future completed TERRN BATCHER:
1. You start with a list of OGRE SceneNodes - see green box.
2. you pick those you want to batch together - see orange box.
3. [WIP] The tool generates one mesh which contains all those picked (keeping position, scale and rotation) and cover them in single material containing all the textures.
4. [TBD] The tool remembers what batches were done as a 'schedule', and allows saving/loading the schedule as file. Modders can share and improve the schedules.
5. [TBD] Modders can launch the tool directly from .terrn2 and auto-execute a schedule on map load.
6. Profit
@ohlidalp
Copy link
Member Author

ohlidalp commented Feb 26, 2024

Added OGRE Mesh/Material bindings + example

This introduces 'example_ogre_terrnBatcher.as' which shows how Mesh/Material API works and will evolve into a production tool.

THE INTENDED USE of the future completed TERRN BATCHER:

  1. You start with a list of OGRE SceneNodes - see green box.
  2. you pick those you want to batch together - see orange box.
  3. [WIP] The tool generates one mesh which contains all those picked (keeping position, scale and rotation) and cover them in single material containing all the textures.
  4. [TBD] The tool remembers what batches were done as a 'schedule', and allows saving/loading the schedule as file. Modders can share and improve the schedules.
  5. [TBD] Modders can launch the tool directly from .terrn2 and auto-execute a schedule on map load.
  6. Profit

obrazek

@ohlidalp ohlidalp changed the title Naked OGRE AngelScript bindings (+ demo script) TerrnBatcher (terrain mesh & material optimizer script) Feb 26, 2024
ohlidalp added 22 commits March 11, 2024 22:04
This has no effect on rendering, it just helps users to diagnose the scene graph.
Prettier and more uniform object names - Helper func `ActorSpawner::ComposeName()` is now smarter, outputs nicer IDs and is used even more.

This has no effect on rendering, it just helps users to diagnose the scene graph using the inspector script.
2 new example scripts.

New script API:
* `enum Ogre::RenderOperation`
* `class Ogre::ManualObject` REF | NOCOUNT object type. This is castable to MovableObject. Official tutorial for Ogre::ManualObject: https://ogrecave.github.io/ogre/api/latest/manual-mesh-creation.html
* Functions in OGRE SceneManager: `createManualObject()`, `getManualObject()`, `destroyManualObject()`
* Extra feature: function `TerrainClass::getHeightAt()` for the MeshedConcrete example to work.
NOTE: the example script only blits to topmost mipmap, so to see the effect, you must be close to the object! This will be improved.

New Angelscript API:
* `game.loadImageResource(filename, rg)` - see GameScript.cpp & GameScriptAngelscript.cpp
* `ImDrawList::AddImage()` - see ImGuiAngelscript.cpp
* Ogre objects: `box; PixelBox; HardwarePixelBufferSharedPtr`, Texture functions `getBuffer(); getNumMipmaps()`

Example code:
```
    // src image
    Ogre::Image gSrcImg = game.loadImageResource("sign-roadnarrows.dds", "TexturesRG");
    // dst texture
    Ogre::TexturePtr gDstTex = Ogre::TextureManager::getSingleton().load("character.dds", "TexturesRG");
    // let the MAGIC happen
    Ogre::HardwarePixelBufferPtr pixbuf = gDstTex.getBuffer(/*cubemap face index:*/0, /*mipmap:*/0);
    Ogre::PixelBox srcPixbox = gSrcImg.getPixelBox(0,0); // getPixelBox(cubemapFaceIndex, mipmapIndex);
    box gDstBox; // where you want to put it.
    pixbuf.blitFromMemory(srcPixbox, gDstBox);
```

New/updated scripts:
* new 'example_ogre_textureBlitting.as' - draws UI using GridViewer and lets user mouse-draw destination pixelbox. Hardcoded to use character.dds as destination.
* 'gridviewer_utils.as' - added 'zoomMin' config, new func `screenToLocalPos()` to enable mouse-interactions with the viewed data.
… empty.

The OGRE version we use (11.6) crashes anyway when normalheight parameter is blank, although for OGRE13+ it's supposed to be okay (regarding to Paroj @ GitterChat)
There are now 2 funcs:
* `localToScreenPos()` - originally `projectPos()`, renamed to align with the below.
* `screenToLocalPos()` - added recently to make texture-blitting example work.
Task 3/3 from RigsOfRods#3030.
This means I can start writing a script to batch together terrain objects (meshes+textures) for performance - especially for Community Map

New script API:
* objects: `MeshPtr, SubMesh, MeshManager`
* to get vert positions, use "array<vector3>@ __getVertexPositions()" on SubMesh
* ditto for vert texcoords, note the extra index param because mesh can have more than 1 texcoord layer: "array<vector2>@ __getVertexTexcoords(uint index)"

See example script "example_ogre_vertexData.as"
If you loaded a document with regions, their recorded character count would be stuck on the original value, so if you added/removed characters the folding would break. This was fixed.

There is still a bug - orphan regions don't resurface as expected.
Problem: The `restoreRegionFoldStates()` didn't actually modify buffer, just restored the flag.
This introduces 'example_ogre_terrnBatcher.as' which shows how Mesh/Material API works and will evolve into a production tool. this will conclude RigsOfRods#3030.

THE INTENDED USE of the future completed TERRN BATCHER:
1. You start with a list of OGRE SceneNodes - see green box.
2. you pick those you want to batch together - see orange box.
3. [WIP] The tool generates one mesh which contains all those picked (keeping position, scale and rotation) and cover them in single material containing all the textures.
4. [TBD] The tool remembers what batches were done as a 'schedule', and allows saving/loading the schedule as file. Modders can share and improve the schedules.
5. [TBD] Modders can launch the tool directly from .terrn2 and auto-execute a schedule on map load.
6. Profit
Problem: The character offsets weren't updated, so when you edited text above a folded region, it's content would come out corrupted after unfolding.

This also factors out helper function `mergeCollectedFoldingRegionsWithExisting()` with extra commentary.
I really suspected the `array<dictionary> bufferLinesMeta` to be the culprit. When that was disproved, I thought collecting regions or merging region info could be it. Both were disproved too. To my surprise it was the `isChar()` helper.

Profiling results [in microseconds] opening 'example_ogre_terrnBatcher.as':
 * Before:  PROFILING analyzeBuffer(): total 205834us (CPU 206000us) regions 324us (CPU 0us) dict 15805us (CPU 17000us) merging 151us (CPU 1000us)
 * After: PROFILING analyzeBuffer() : total 32313us (CPU 32000us) regions 280us (CPU 0us) dict 15653us (CPU 16000us) merging 146us (CPU 0us)
// New Script API:
// -- `BeamClass.getManagedMaterialNames()` -> `array<string>@`
// -- `BeamClass.getManagedMaterialInstance()` -> `Ogre::MaterialPtr`
// -- `Ogre::Pass.__getNamedConstants()` -> `array<string>@`
// -- `Ogre::Pass.getFragmentProgramParameters()` -> `Ogre::GpuProgramParametersPtr`

New internal API, `class Actor`:
- getManagedMaterialInstance(const std::string& orig_name); --> Ogre::MaterialPtr
- getManagedMaterialNames(); --> std::vector<std::string>
The 'example_ogre_terrnBatcher.as' script was updated
- Use the new indexBuffer bindings
- Perform merging of selected meshes (buggy at the moment)
- Display an inline control for scene node visibility - useful to compare original vs. generated meshes
Script 'example_ogre_terrnBatcher.as' updates:
- Added [dump] button to inspector scenegraph - tested to work also for ManualObjects
- Fixed mesh generation (I forgot to offset indices for every new appended mesh)
- Reduced debug outputs

Code changes:
- added RGN_LOGS, auto-created at startup (along with other resource groups)
- the actor-dump code now uses RGN_LOGS
- moved RGN_ defs from ResourceManager.h to Application.h
Copy link
Collaborator

@CuriousMike56 CuriousMike56 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@ohlidalp ohlidalp changed the title TerrnBatcher (terrain mesh & material optimizer script) 👼Script: OGRE bindings for editing scene, meshes and textures - with examples Mar 14, 2024
@ohlidalp
Copy link
Member Author

I've just reverted the title of this PR from the intermediate "TerrnBatcher - terrain optimzation tool" to the original "AngelScript bindings for OGRE scene and stuff" because that's really what the bulk of the code here does, and I want to merge it already because lately I've been mentioning it almost in every my response in the sense "The PR 3030 would let you do this...", so let's not hold all those ideas back any longer. I'll create a new, dedicated branch for the TerrnBatcher script.

@ohlidalp ohlidalp merged commit a35a9d8 into RigsOfRods:master Mar 14, 2024
2 checks passed
ohlidalp added a commit that referenced this pull request Mar 14, 2024
Task 3/3 from #3030.
This means I can start writing a script to batch together terrain objects (meshes+textures) for performance - especially for Community Map

New script API:
* objects: `MeshPtr, SubMesh, MeshManager`
* to get vert positions, use "array<vector3>@ __getVertexPositions()" on SubMesh
* ditto for vert texcoords, note the extra index param because mesh can have more than 1 texcoord layer: "array<vector2>@ __getVertexTexcoords(uint index)"

See example script "example_ogre_vertexData.as"
ohlidalp added a commit that referenced this pull request Mar 14, 2024
This introduces 'example_ogre_terrnBatcher.as' which shows how Mesh/Material API works and will evolve into a production tool. this will conclude #3030.

THE INTENDED USE of the future completed TERRN BATCHER:
1. You start with a list of OGRE SceneNodes - see green box.
2. you pick those you want to batch together - see orange box.
3. [WIP] The tool generates one mesh which contains all those picked (keeping position, scale and rotation) and cover them in single material containing all the textures.
4. [TBD] The tool remembers what batches were done as a 'schedule', and allows saving/loading the schedule as file. Modders can share and improve the schedules.
5. [TBD] Modders can launch the tool directly from .terrn2 and auto-execute a schedule on map load.
6. Profit
@ohlidalp ohlidalp deleted the raw_OGRE branch March 14, 2024 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants