Skip to content

Commit

Permalink
👼Script: Ogre::ManualObject API - create manual mesh
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ohlidalp committed Feb 10, 2024
1 parent 8626cbb commit 9e7cfa3
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 0 deletions.
17 changes: 17 additions & 0 deletions resources/scripts/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
RIGS OF RODS - bundled scripts
==============================

TIP: you can run any script in game using 'loadscript' command in the console. To display console, press '~' anytime or in simulation go to top menu > Tools > Show console.

FOR DEVELOPERS: Visit https://developer.rigsofrods.org/ and click "Script-side APIs" link on main page for detailed docs.

Conventions:
* Scripts named 'example_*.as' are short introductory scripts that perform one thing, usually showing UI with tips and controls. You can run them using 'loadscript'.
* Scripts named '*_editor.as' are complete tools. You can run them using 'loadscript'.
* Scripts named '*_utils.as' are includes - running them directly is not possible, 'loadscript' would result in error "there is no main() function".

Special scripts:
* 'default.as' - fallback terrain script, loaded if modder didn't provide custom script.
* 'base.as' - an include script for terrain, handles some basic object events.
* 'races.as' - an include script for terrain, implements waypoint-based driving challenges.
* 'AI.as' - a backend script for the "Vehicle AI" menu in top menubar. Spawns an AI-driven vehicle, as configured in the menu.
53 changes: 53 additions & 0 deletions resources/scripts/example_ogre_ManualObject.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// \title example - OGRE - ManualObject
// \brief shows how to make custom mesh and place it to scene
// ===================================================


array< Ogre::ManualObject@ > gManualObjects;
Ogre::SceneNode@ gGroupingSceneNode = game.getSceneManager().getRootSceneNode().createChildSceneNode(makeID(""));

string makeID(string name)
{
// to avoid clash with leftover scene nodes created before, we include the NID in the name - using automatic global var `thisScript`.
return "example-OGRE-ManuaObject.as(NID:"+thisScript+"): "+name;
}

Ogre::ManualObject@ makePlane(string meshName, string matName)
{
Ogre::ManualObject@ mo = game.getSceneManager().createManualObject(meshName);
mo.begin(matName, Ogre::OT_TRIANGLE_LIST, game.getTerrain().getHandle().getTerrainFileResourceGroup());
// verts
mo.position(vector3(-1, -1, -1));
mo.textureCoord(vector2(0,0));
mo.position(vector3(-1, -1, 1));
mo.textureCoord(vector2(0,1));
mo.position(vector3(1, -1, 1));
mo.textureCoord(vector2(1,1));
mo.position(vector3(1, -1, -1));
mo.textureCoord(vector2(1,0));
// tris
mo.index(0); mo.index(1); mo.index(2);
mo.index(0); mo.index(2); mo.index(3);

mo.end();
return mo;
}

// `frameStep()` runs every frame; `dt` is delta time in seconds.
void frameStep(float dt)
{
ImGui::Text("Num manual objects: "+gManualObjects.length());

if (ImGui::Button("make plane at character position"))
{
string moname = makeID("mo-"+gManualObjects.length());
string matname = "sign-autostrasse";
Ogre::ManualObject@ mo = makePlane(moname, matname);
Ogre::SceneNode@ snode = gGroupingSceneNode.createChildSceneNode(moname+"-node");
snode.attachObject(cast<Ogre::MovableObject@>(mo));
snode.setPosition(game.getPersonPosition()+vector3(0,2,0));
gManualObjects.insertLast(mo);
}


}
104 changes: 104 additions & 0 deletions resources/scripts/example_ogre_meshedConcrete.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// \title example - OGRE - MeshedConcrete
// \brief shows how to make custom mesh along the ground
// ===================================================


array< Ogre::ManualObject@ > gManualObjects;
Ogre::SceneNode@ gGroupingSceneNode = game.getSceneManager().getRootSceneNode().createChildSceneNode(makeID(""));
int gNumTilesX = 2;
int gNumTilesZ = 2;
float gTileSizeX = 1.f;
float gTileSizeZ = 1.f;
float gHeightBias = 0.1; // 10cm above ground
bool gDebugLog = true;

string makeID(string name)
{
// to avoid clash with leftover scene nodes created before, we include the NID in the name - using automatic global var `thisScript`.
return "example-OGRE-ManuaObject.as(NID:"+thisScript+"): "+name;
}

Ogre::ManualObject@ makeGrass(string meshName, string matName)
{
Ogre::ManualObject@ mo = game.getSceneManager().createManualObject(meshName);
mo.begin(matName, Ogre::OT_TRIANGLE_LIST, game.getTerrain().getHandle().getTerrainFileResourceGroup());

TerrainClass@ terrn = game.getTerrain();
int numVerts = 0;
vector3 startPos = game.getPersonPosition() - vector3(gTileSizeX*(gNumTilesX/2), 0, gTileSizeZ*(gNumTilesZ/2));
float tileSizeU = (1.f/gNumTilesX); // texcoords, aka UV coords
float tileSizeV = (1.f/gNumTilesZ);
for (int x = 0; x < gNumTilesX; x++)
{
for (int z = 0; z < gNumTilesZ; z++)
{
if (gDebugLog) { game.log("DBG: adding tile X="+x+"/"+gNumTilesX+" Z="+z+"/"+gNumTilesZ); }

// world horizontal position
float tileX = x*gTileSizeX + startPos.x;
float tileZ = z*gTileSizeZ + startPos.z;
// texcoords, aka UV coords
float tileU = x*tileSizeU;
float tileV = z*tileSizeV;

if (gDebugLog){game.log("DBG \tTileX="+tileX+" tileZ="+tileZ+" tileU="+tileU+" tileV="+tileV);}

// verts
mo.position(vector3 (tileX, terrn.getHeightAt(tileX, tileZ)+gHeightBias, tileZ));
mo.textureCoord(vector2(tileU, tileV));

mo.position(vector3 (tileX, terrn.getHeightAt(tileX, tileZ+gTileSizeZ)+gHeightBias, tileZ+gTileSizeZ));
mo.textureCoord(vector2(tileU, tileV+tileSizeV));

mo.position(vector3 (tileX+gTileSizeX, terrn.getHeightAt(tileX+gTileSizeX, tileZ+gTileSizeZ)+gHeightBias, tileZ+gTileSizeZ));
mo.textureCoord(vector2(tileU+tileSizeU, tileV+tileSizeV));

mo.position(vector3 (tileX+gTileSizeX, terrn.getHeightAt(tileX+gTileSizeX, tileZ)+gHeightBias, tileZ));
mo.textureCoord(vector2(tileU+tileSizeU, tileV));

// tris
mo.index(numVerts+0); mo.index(numVerts+1); mo.index(numVerts+2);
mo.index(numVerts+0); mo.index(numVerts+2); mo.index(numVerts+3);

numVerts+= 4;
}
}

mo.end();
return mo;

}

// `frameStep()` runs every frame; `dt` is delta time in seconds.
void frameStep(float dt)
{
ImGui::TextDisabled("MESHED CONCRETE EXAMPLE");
ImGui::TextDisabled("Generates one-layer mesh along ground");
ImGui::TextDisabled("Remember, XZ are horizontal, Y is up.");

ImGui::Separator();

ImGui::Text("Num manual objects: "+gManualObjects.length());


ImGui::InputInt("Num tiles X", gNumTilesX);
ImGui::InputInt("Num tiles Z", gNumTilesZ);
ImGui::InputFloat("Tile size X", gTileSizeX);
ImGui::InputFloat("Tile size Z", gTileSizeZ);
ImGui::InputFloat("Height bias (m)", gHeightBias);
ImGui::Checkbox("Debug logging", gDebugLog);

if (ImGui::Button("add mesh"))
{
string moname = makeID("mo-"+gManualObjects.length());
string matname = "taxiwayconcrete";
Ogre::ManualObject@ mo = makeGrass(moname, matname);
Ogre::SceneNode@ snode = gGroupingSceneNode.createChildSceneNode(moname+"-node");
snode.attachObject(cast<Ogre::MovableObject@>(mo));
// unlike the ManualObject example, we don't position the node - verts are already in world position.
gManualObjects.insertLast(mo);
}



}
45 changes: 45 additions & 0 deletions source/main/scripting/bindings/OgreAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ void registerOgreAnimationState(AngelScript::asIScriptEngine* engine);
void registerOgreAnimationStateSet(AngelScript::asIScriptEngine* engine);
void registerOgreTexture(AngelScript::asIScriptEngine* engine);
void registerOgreTextureManager(AngelScript::asIScriptEngine* engine);
void registerOgreManualObject(AngelScript::asIScriptEngine* engine);

// main registration method
void RoR::RegisterOgreObjects(AngelScript::asIScriptEngine* engine)
Expand Down Expand Up @@ -397,6 +398,8 @@ void RoR::RegisterOgreObjects(AngelScript::asIScriptEngine* engine)
r = engine->RegisterObjectType("TextureManager", sizeof(TextureManager), asOBJ_REF | asOBJ_NOCOUNT);
ROR_ASSERT(r >= 0);

r = engine->RegisterObjectType("ManualObject", sizeof(TextureManager), asOBJ_REF | asOBJ_NOCOUNT);
ROR_ASSERT(r >= 0);
// dictionary/array view types, also under namespace `Ogre`

SceneManagerInstanceDict::RegisterReadonlyScriptDictView(engine, "SceneManagerInstanceDict", "SceneManager");
Expand All @@ -411,6 +414,14 @@ void RoR::RegisterOgreObjects(AngelScript::asIScriptEngine* engine)
r = engine->RegisterEnumValue("TransformSpace", "TS_PARENT", Node::TS_PARENT); /// Transform is relative to the space of the parent node
r = engine->RegisterEnumValue("TransformSpace", "TS_WORLD", Node::TS_WORLD); /// Transform is relative to world space

r = engine->RegisterEnum("RenderOperation"); ROR_ASSERT(r >= 0); // NOTE: `Ogre::RenderOperation` is a wrapper class - the enum is `OperationType`
r = engine->RegisterEnumValue("RenderOperation", "OT_POINT_LIST", Ogre::RenderOperation::OT_POINT_LIST); /// A list of points, 1 vertex per point
r = engine->RegisterEnumValue("RenderOperation", "OT_LINE_LIST", Ogre::RenderOperation::OT_LINE_LIST); /// A list of lines, 2 vertices per line
r = engine->RegisterEnumValue("RenderOperation", "OT_LINE_STRIP", Ogre::RenderOperation::OT_LINE_STRIP); /// A strip of connected lines, 1 vertex per line plus 1 start vertex
r = engine->RegisterEnumValue("RenderOperation", "OT_TRIANGLE_LIST", Ogre::RenderOperation::OT_TRIANGLE_LIST); /// A list of triangles, 3 vertices per triangle
r = engine->RegisterEnumValue("RenderOperation", "OT_TRIANGLE_STRIP", Ogre::RenderOperation::OT_TRIANGLE_STRIP); /// A strip of triangles, 3 vertices for the first triangle, and 1 per triangle after that
r = engine->RegisterEnumValue("RenderOperation", "OT_TRIANGLE_FAN", Ogre::RenderOperation::OT_TRIANGLE_FAN); /// A fan of triangles, 3 vertices for the first triangle, and 1 per triangle after that

r = engine->SetDefaultNamespace(""); ROR_ASSERT(r >= 0);

// Now we register the object properties and methods
Expand All @@ -433,6 +444,7 @@ void RoR::RegisterOgreObjects(AngelScript::asIScriptEngine* engine)
registerOgreTexture(engine);
registerOgreTextureManager(engine);
registerOgreOverlay(engine);
registerOgreManualObject(engine);

// To estabilish class hierarchy in AngelScript you need to register the reference cast operators opCast and opImplCast.

Expand All @@ -442,6 +454,10 @@ void RoR::RegisterOgreObjects(AngelScript::asIScriptEngine* engine)
// - `Entity` derives from `MovableObject`
r = engine->RegisterObjectMethod("Ogre::MovableObject", "Ogre::Entity@ opCast()", asFUNCTION((ScriptRefCastNoCount<Ogre::MovableObject, Ogre::Entity>)), asCALL_CDECL_OBJLAST); assert(r >= 0);
r = engine->RegisterObjectMethod("Ogre::Entity", "Ogre::MovableObject@ opImplCast()", asFUNCTION((ScriptRefCastNoCount<Ogre::Entity, Ogre::MovableObject>)), asCALL_CDECL_OBJLAST); assert(r >= 0);
// - `ManualObject` derives from `MovableObject`
r = engine->RegisterObjectMethod("Ogre::MovableObject", "Ogre::ManualObject@ opCast()", asFUNCTION((ScriptRefCastNoCount<Ogre::MovableObject, Ogre::ManualObject>)), asCALL_CDECL_OBJLAST); assert(r >= 0);
r = engine->RegisterObjectMethod("Ogre::ManualObject", "Ogre::MovableObject@ opImplCast()", asFUNCTION((ScriptRefCastNoCount<Ogre::ManualObject, Ogre::MovableObject>)), asCALL_CDECL_OBJLAST); assert(r >= 0);


// Also register the const overloads so the cast works also when the handle is read only

Expand All @@ -451,6 +467,9 @@ void RoR::RegisterOgreObjects(AngelScript::asIScriptEngine* engine)
// - `Entity` derives from `MovableObject`
r = engine->RegisterObjectMethod("Ogre::MovableObject", "const Ogre::Entity@ opCast() const", asFUNCTION((ScriptRefCastNoCount<Ogre::MovableObject, Ogre::Entity>)), asCALL_CDECL_OBJLAST); assert(r >= 0);
r = engine->RegisterObjectMethod("Ogre::Entity", "const Ogre::MovableObject@ opImplCast() const", asFUNCTION((ScriptRefCastNoCount<Ogre::Entity, Ogre::MovableObject>)), asCALL_CDECL_OBJLAST); assert(r >= 0);
// - `ManualObject` derives from `MovableObject`
r = engine->RegisterObjectMethod("Ogre::MovableObject", "const Ogre::ManualObject@ opCast() const", asFUNCTION((ScriptRefCastNoCount<Ogre::MovableObject, Ogre::ManualObject>)), asCALL_CDECL_OBJLAST); assert(r >= 0);
r = engine->RegisterObjectMethod("Ogre::ManualObject", "const Ogre::MovableObject@ opImplCast() const", asFUNCTION((ScriptRefCastNoCount<Ogre::ManualObject, Ogre::MovableObject>)), asCALL_CDECL_OBJLAST); assert(r >= 0);

}

Expand Down Expand Up @@ -1161,6 +1180,12 @@ void registerOgreSceneManager(AngelScript::asIScriptEngine* engine)
r = engine->RegisterObjectMethod("SceneManager", "void destroySceneNode(SceneNode@)", asMETHODPR(SceneManager, destroySceneNode, (SceneNode*), void), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod("SceneManager", "void destroySceneNode(const string &in)", asMETHODPR(SceneManager, destroySceneNode, (const Ogre::String&), void), asCALL_THISCALL); ROR_ASSERT(r >= 0);

// ManualObject:
r = engine->RegisterObjectMethod("SceneManager", "ManualObject@ createManualObject(const string &in)", asMETHODPR(SceneManager, createManualObject, (const Ogre::String&), Ogre::ManualObject*), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod("SceneManager", "ManualObject@ getManualObject(const string &in)", asMETHODPR(SceneManager, getManualObject, (const Ogre::String&) const, Ogre::ManualObject*), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod("SceneManager", "ManualObject@ destroyManualObject(const string &in)", asMETHODPR(SceneManager, destroyManualObject, (const Ogre::String&), void), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod("SceneManager", "void destroyManualObject(ManualObject@)", asMETHODPR(SceneManager, destroyManualObject, (Ogre::ManualObject*), void), asCALL_THISCALL); ROR_ASSERT(r >= 0);

r = engine->SetDefaultNamespace(""); ROR_ASSERT(r >= 0);
}

Expand Down Expand Up @@ -1407,3 +1432,23 @@ void registerOgreOverlay(AngelScript::asIScriptEngine* engine)
engine->SetDefaultNamespace("");

}

void registerOgreManualObject(AngelScript::asIScriptEngine* engine)
{
engine->SetDefaultNamespace("Ogre");

// Register the ManualObject class
engine->RegisterObjectType("ManualObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
engine->RegisterObjectMethod("ManualObject", "void begin(const string&in, RenderOperation, const string&in)", asMETHODPR(Ogre::ManualObject, begin, (const String&, Ogre::RenderOperation::OperationType, const String&), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void beginUpdate()", asMETHOD(Ogre::ManualObject, beginUpdate), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void position(const vector3&in)", asMETHODPR(Ogre::ManualObject, position, (const Ogre::Vector3&), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void normal(const vector3&in)", asMETHODPR(Ogre::ManualObject, normal, (const Ogre::Vector3&), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void textureCoord(float, float)", asMETHODPR(Ogre::ManualObject, textureCoord, (float, float), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void textureCoord(const vector2&in)", asMETHODPR(Ogre::ManualObject, textureCoord, (const Ogre::Vector2&), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void textureCoord(const vector3&in)", asMETHODPR(Ogre::ManualObject, textureCoord, (const Ogre::Vector3&), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void colour(const color&in)", asMETHODPR(Ogre::ManualObject, colour, (const Ogre::ColourValue&), void), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void index(uint32)", asMETHOD(Ogre::ManualObject, index), asCALL_THISCALL);
engine->RegisterObjectMethod("ManualObject", "void end()", asMETHOD(Ogre::ManualObject, end), asCALL_THISCALL);

engine->SetDefaultNamespace("");
}
1 change: 1 addition & 0 deletions source/main/scripting/bindings/TerrainAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void RoR::RegisterTerrain(asIScriptEngine* engine)

// > Landscape
result = engine->RegisterObjectMethod("TerrainClass", "bool isFlat()", asMETHOD(RoR::Terrain,isFlat), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("TerrainClass", "float getHeightAt(float x, float z)", asMETHOD(RoR::Terrain,GetHeightAt), asCALL_THISCALL); ROR_ASSERT(result>=0);

// > Gameplay
result = engine->RegisterObjectMethod("TerrainClass", "vector3 getSpawnPos()", asMETHOD(RoR::Terrain,getSpawnPos), asCALL_THISCALL); ROR_ASSERT(result>=0);
Expand Down

0 comments on commit 9e7cfa3

Please sign in to comment.