Skip to content

Commit

Permalink
AngelScript: added OGRE Node+SceneNode bindings.
Browse files Browse the repository at this point in the history
AngelScript API changes:
+ `ImGuiCond` enum
+ `ImGui::SetNextItemOpen()` func
+ `Ogre::Node` object
+ `Ogre::SceneNode` object

Code changes:
* CReadonlyScriptDictView moved to ScriptUtils.h
* created CReadonlyScriptArrayView in ScriptUtils.h

ogre_demo.as changes:
* codechange: added class OgreInspector, draw funcs made methods.
* ability to traverse scene node graph (using scenemanager.getRootSceneNode() and scenenode.getChildren())
  • Loading branch information
ohlidalp committed Mar 24, 2023
1 parent e5491f7 commit cb9b7bb
Show file tree
Hide file tree
Showing 4 changed files with 374 additions and 133 deletions.
134 changes: 104 additions & 30 deletions resources/scripts/ogre_demo.as
Original file line number Diff line number Diff line change
@@ -1,51 +1,125 @@
void main()
{
game.log("EXPERIMENTAL! This script tests the new direct bindings of OGRE rendering framework");
game.log("CAUTION: Proof of concept, very experimental!");
game.log("This script tests the new direct bindings of OGRE rendering framework");
}

OgreInspector inspector;

void frameStep(float dt)
{
// Begin drawing window
ImGui::Begin("OGRE inspector", /*open:*/true, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::TextDisabled("See official OGRE API reference");
ImGui::TextDisabled("https://ogrecave.github.io/ogre/api/latest/");
ImGui::Separator();
ImGui::Begin("OGRE demo script", /*open:*/true, ImGuiWindowFlags_AlwaysAutoResize);

inspector.draw();

Ogre::Root@ root = Ogre::Root::getSingleton();
if (@root != null)
// End window
ImGui::End();
}

class OgreInspector
{
void draw()
{
drawTreeNodeOgreRoot(root);
ImGui::TextDisabled("Scene inspector");
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
this.drawInspectorTooltipBody();
ImGui::EndTooltip();
}
ImGui::Separator();

Ogre::Root@ root = Ogre::Root::getSingleton();
if (@root != null)
{
drawTreeNodeOgreRoot(root);
}
else
{
ImGui::TextDisabled("Cannot retrieve OGRE `Root` object");
}
}
else

void drawInspectorTooltipBody()
{
ImGui::TextDisabled("Cannot retrieve OGRE `Root` object");
ImGui::Text("Traverses the OGRE scene hierarchy, as stored in memory");
ImGui::Text("Note that all element names are just simple strings,");
ImGui::Text("even if they have '/' in them, like 'Ogre/SceneRoot'.");
ImGui::Separator();
ImGui::TextDisabled("See official OGRE API reference");
ImGui::TextDisabled("https://ogrecave.github.io/ogre/api/latest/");
}

// End window
ImGui::End();
}
void drawTreeNodeOgreRoot(Ogre::Root@ root)
{
if (ImGui::TreeNode("(The OGRE Root)"))
{
// Scenemanagers
Ogre::SceneManagerInstanceDict@ sceneManagers = root.getSceneManagers();
ImGui::TextDisabled("Ogre::SceneManager [" + sceneManagers.getSize() + "]");
array<string> sceneMgrNames = sceneManagers.getKeys();
for (uint i = 0; i < sceneManagers.getSize(); i++)
{
drawTreeNodeOgreSceneManager(sceneManagers[sceneMgrNames[i]]);
}

ImGui::TreePop();
}
}

void drawTreeNodeOgreRoot(Ogre::Root@ root)
{
if (ImGui::TreeNode("Ogre::Root"))
void drawTreeNodeOgreSceneManager(Ogre::SceneManager@ sceneMgr)
{
// Scenemanagers
Ogre::SceneManagerInstanceDict@ sceneManagers = root.getSceneManagers();
ImGui::TextDisabled("Ogre::SceneManager [" + sceneManagers.getSize() + "]");
array<string> sceneMgrNames = sceneManagers.getKeys();
for (uint i = 0; i < sceneManagers.getSize(); i++)
if (ImGui::TreeNode('"'+sceneMgr.getName()+'"'))
{
drawTreeNodeOgreSceneManager(sceneManagers[sceneMgrNames[i]]);
}
// Scene nodes
Ogre::SceneNode@ rootNode = sceneMgr.getRootSceneNode();
if (@rootNode != null)
{
ImGui::TextDisabled("Ogre::SceneNode [1]");
this.drawTreeNodeOgreSceneNodeRecursive(rootNode);
}
else
{
ImGui::TextDisabled("Ogre::SceneNode [0]");
}

ImGui::TreePop();
ImGui::TreePop();
}
}
}

void drawTreeNodeOgreSceneManager(Ogre::SceneManager@ sceneMgr)
{
if (ImGui::TreeNode(sceneMgr.getName()))
{
ImGui::TreePop();
void drawTreeNodeOgreSceneNodeRecursive(const Ogre::SceneNode@ snode)
{
// When drawing first time, fold nodes with many children (root node can have hundreds...)
const Ogre::ChildNodeArray@ children = snode.getChildren();
ImGui::SetNextItemOpen(children.length() < 10, ImGuiCond_Once);

// The `__getUniqueId()` is a Rigs of Rods extension (that's why double leading underscores),
// because names are optional and usually not set, and imgui tree nodes require unique IDs.
if (ImGui::TreeNode(snode.__getUniqueId()))
{
// Tree node open, draw children recursively
ImGui::TextDisabled("Ogre::Node ["+children.length()+"]");
for (uint i = 0; i < children.length(); i++)
{
const Ogre::SceneNode@ child = cast<Ogre::SceneNode>(children[i]);
if (@child != null)
{
drawTreeNodeOgreSceneNodeRecursive(child);
}
}

ImGui::TreePop();
}
else
{
// Tree node closed, draw child count
ImGui::SameLine();
ImGui::Text("("+children.length()+" children)");
}
}
}


179 changes: 179 additions & 0 deletions source/main/scripting/ScriptUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,185 @@ AngelScript::CScriptArray* MapToScriptArray(std::map<T, U>& map, const std::stri
return arr;
}

// Proof of concept: a read-only Dictionary view of a `std::map`-like container.
// Assumed to always use `std::string` for key.
// Not using RefCountingObject because this is only for use in the script, not C++
// Adopted from 'scriptdictionary.cpp' bundled with AngelScript SDK
template<typename T>
class CReadonlyScriptDictView
{
public:
static const char* VALUE_DECL;

CReadonlyScriptDictView(const std::map<std::string, T>& map) : m_map(map), m_refcount(1) {}

// Gets the stored value. Returns false if the value isn't compatible with the informed typeId
bool Get(const std::string& key, void* value, int typeId) const
{
if (typeId != AngelScript::asGetActiveContext()->GetEngine()->GetTypeIdByDecl(VALUE_DECL))
return false;

auto it = m_map.find(key);
if (it == m_map.end())
return false;

*(void**)value = it->second;
return true;
}

bool Get(const std::string& key, AngelScript::asINT64& value) const
{
return Get(key, &value, AngelScript::asTYPEID_INT64);
}

bool Get(const std::string& key, double& value) const
{
return Get(key, &value, AngelScript::asTYPEID_DOUBLE);
}

bool Exists(const std::string& key) const
{
return (m_map.find(key) != m_map.end());
}

bool IsEmpty() const
{
if (m_map.size() == 0)
return true;

return false;
}

AngelScript::asUINT GetSize() const
{
return AngelScript::asUINT(m_map.size());
}

T OpIndex(const std::string& key) const
{
auto it = m_map.find(key);
if (it != m_map.end())
return it->second;
else
return T{};
}

AngelScript::CScriptArray* GetKeys() const
{
// Create the array object
AngelScript::CScriptArray* array = AngelScript::CScriptArray::Create(AngelScript::asGetActiveContext()->GetEngine()->GetTypeInfoByDecl("array<string>"), AngelScript::asUINT(m_map.size()));
long current = -1;
for (auto it = m_map.begin(); it != m_map.end(); it++)
{
current++;
*(std::string*)array->At(current) = it->first;
}

return array;
}

static void RegisterReadonlyScriptDictView(AngelScript::asIScriptEngine* engine, const char* decl)
{
using namespace AngelScript;

int r;

r = engine->RegisterObjectType(decl, sizeof(CReadonlyScriptDictView<T>), asOBJ_REF); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectBehaviour(decl, asBEHAVE_ADDREF, "void f()", asMETHOD(CReadonlyScriptDictView<T>, AddRef), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectBehaviour(decl, asBEHAVE_RELEASE, "void f()", asMETHOD(CReadonlyScriptDictView <T>, Release), asCALL_THISCALL); ROR_ASSERT(r >= 0);

r = engine->RegisterObjectMethod(decl, "bool exists(const string &in) const", asMETHOD(CReadonlyScriptDictView<T>, Exists), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod(decl, "bool isEmpty() const", asMETHOD(CReadonlyScriptDictView<T>, IsEmpty), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod(decl, "uint getSize() const", asMETHOD(CReadonlyScriptDictView<T>, GetSize), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod(decl, "array<string> @getKeys() const", asMETHOD(CReadonlyScriptDictView<T>, GetKeys), asCALL_THISCALL); assert(r >= 0);

r = engine->RegisterObjectMethod(decl, "bool get(const string &in, ?&out) const", asMETHODPR(CReadonlyScriptDictView<T>, Get, (const std::string&, void*, int) const, bool), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod(decl, "bool get(const string &in, int64&out) const", asMETHODPR(CReadonlyScriptDictView<T>, Get, (const std::string&, asINT64&) const, bool), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod(decl, "bool get(const string &in, double&out) const", asMETHODPR(CReadonlyScriptDictView<T>, Get, (const std::string&, double&) const, bool), asCALL_THISCALL); ROR_ASSERT(r >= 0);

std::string opIndexDecl = fmt::format("{}@ opIndex(const string &in)", VALUE_DECL);
r = engine->RegisterObjectMethod(decl, opIndexDecl.c_str(), asMETHOD(CReadonlyScriptDictView<T>, OpIndex), asCALL_THISCALL); ROR_ASSERT(r >= 0);
std::string opIndexConstDecl = fmt::format("const {}@ opIndex(const string &in) const", VALUE_DECL);
r = engine->RegisterObjectMethod(decl, opIndexConstDecl.c_str(), asMETHOD(CReadonlyScriptDictView<T>, OpIndex), asCALL_THISCALL); ROR_ASSERT(r >= 0);

}

// Angelscript refcounting
void AddRef() { m_refcount++; }
void Release() { m_refcount--; if (m_refcount == 0) { delete this; /* Comit suicide! This is legit in C++ and AngelScript relies on it. */ } }
private:
const std::map<std::string, T>& m_map;
int m_refcount;
};

template <typename T>
class CReadonlyScriptArrayView
{
public:
static const char* VALUE_DECL;

CReadonlyScriptArrayView(const std::vector<T>& vec) : m_vec(vec), m_refcount(1) {}

bool IsEmpty() const { return m_vec.empty(); }
unsigned GetSize() const { return m_vec.size(); }
T OpIndex(unsigned pos) { return m_vec.at(pos); }

static void RegisterReadonlyScriptArrayView(AngelScript::asIScriptEngine* engine, const char* decl)
{
using namespace AngelScript;

int r;

r = engine->RegisterObjectType(decl, sizeof(CReadonlyScriptArrayView<T>), asOBJ_REF); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectBehaviour(decl, asBEHAVE_ADDREF, "void f()", asMETHOD(CReadonlyScriptArrayView<T>, AddRef), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectBehaviour(decl, asBEHAVE_RELEASE, "void f()", asMETHOD(CReadonlyScriptArrayView <T>, Release), asCALL_THISCALL); ROR_ASSERT(r >= 0);

r = engine->RegisterObjectMethod(decl, "bool isEmpty() const", asMETHOD(CReadonlyScriptArrayView<T>, IsEmpty), asCALL_THISCALL); ROR_ASSERT(r >= 0);
r = engine->RegisterObjectMethod(decl, "uint length() const", asMETHOD(CReadonlyScriptArrayView<T>, GetSize), asCALL_THISCALL); ROR_ASSERT(r >= 0);

std::string opIndexDecl = fmt::format("{}@ opIndex(uint pos)", VALUE_DECL);
r = engine->RegisterObjectMethod(decl, opIndexDecl.c_str(), asMETHOD(CReadonlyScriptArrayView<T>, OpIndex), asCALL_THISCALL); ROR_ASSERT(r >= 0);
std::string opIndexConstDecl = fmt::format("const {}@ opIndex(uint pos) const", VALUE_DECL);
r = engine->RegisterObjectMethod(decl, opIndexConstDecl.c_str(), asMETHOD(CReadonlyScriptArrayView<T>, OpIndex), asCALL_THISCALL); ROR_ASSERT(r >= 0);

}

// Angelscript refcounting
void AddRef() { m_refcount++; }
void Release() { m_refcount--; if (m_refcount == 0) { delete this; /* Comit suicide! This is legit in C++ and AngelScript relies on it. */ } }
private:
const std::vector<T>& m_vec;
int m_refcount;
};

// Example opCast behaviour
template<class A, class B>
B* ScriptRefCast(A* a)
{
// If the handle already is a null handle, then just return the null handle
if (!a) return 0;

// Now try to dynamically cast the pointer to the wanted type
B* b = dynamic_cast<B*>(a);
if (b != 0)
{
// Since the cast was made, we need to increase the ref counter for the returned handle
b->AddRef();
}
return b;
}

// Example opCast behaviour - for objects with asOBJ_NOCOUNT
template<class A, class B>
B* ScriptRefCastNoCount(A* a)
{
// If the handle already is a null handle, then just return the null handle
if (!a) return 0;

// Now try to dynamically cast the pointer to the wanted type
return dynamic_cast<B*>(a);
}

/// @} //addtogroup Scripting

} // namespace RoR
Expand Down
7 changes: 7 additions & 0 deletions source/main/scripting/bindings/ImGuiAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ void RoR::RegisterImGuiBindings(AngelScript::asIScriptEngine* engine)
engine->RegisterEnumValue("ImGuiWindowFlags", "ImGuiWindowFlags_NoDecoration", ImGuiWindowFlags_NoDecoration);
engine->RegisterEnumValue("ImGuiWindowFlags", "ImGuiWindowFlags_NoInputs", ImGuiWindowFlags_NoInputs);

engine->RegisterEnum("ImGuiCond");
engine->RegisterEnumValue("ImGuiCond", "ImGuiCond_Always", ImGuiCond_Always);
engine->RegisterEnumValue("ImGuiCond", "ImGuiCond_Once", ImGuiCond_Once); // Set the variable once per runtime session (only the first call with succeed)
engine->RegisterEnumValue("ImGuiCond", "ImGuiCond_FirstUseEver", ImGuiCond_FirstUseEver); // Set the variable if the object/window has no persistently saved data (no entry in .ini file)
engine->RegisterEnumValue("ImGuiCond", "ImGuiCond_Appearing", ImGuiCond_Appearing); // Set the variable if the object/window is appearing after being hidden/inactive (or the first time)

// ImDrawList object (global namespace)
engine->RegisterObjectType("ImDrawList", sizeof(ImDrawList), asOBJ_REF | asOBJ_NOCOUNT);
engine->RegisterObjectMethod("ImDrawList", "void AddLine(const vector2&in p1, const vector2&in p2, const color&in col, float thickness = 1.f)", asFUNCTIONPR([](ImDrawList* drawlist, Ogre::Vector2 const& p1, Ogre::Vector2 const& p2, Ogre::ColourValue const& col, float thickness) { drawlist->AddLine(ImVec2(p1.x, p1.y), ImVec2(p2.x, p2.y), ImColor(col.r, col.g, col.b, col.a), thickness); }, (ImDrawList * , Ogre::Vector2 const& , Ogre::Vector2 const& , Ogre::ColourValue const& , float ), void), asCALL_CDECL_OBJFIRST);
Expand All @@ -103,6 +109,7 @@ void RoR::RegisterImGuiBindings(AngelScript::asIScriptEngine* engine)
engine->RegisterGlobalFunction("void PushStyleColor(int index, const color&in color)", asFUNCTIONPR([](int index, Ogre::ColourValue const& col) { ImGui::PushStyleColor(index, (ImU32)ImColor(col.r, col.g, col.b, col.a)); }, (int, Ogre::ColourValue const&), void), asCALL_CDECL);
engine->RegisterGlobalFunction("void PopStyleColor(int count)", asFUNCTIONPR(ImGui::PopStyleColor, (int), void), asCALL_CDECL);
engine->RegisterGlobalFunction("void SetNextItemWidth(float)", asFUNCTIONPR(ImGui::SetNextItemWidth, (float), void), asCALL_CDECL);
engine->RegisterGlobalFunction("void SetNextItemOpen(bool, ImGuiCond)", asFUNCTIONPR(ImGui::SetNextItemOpen, (bool, int), void), asCALL_CDECL);

engine->RegisterGlobalFunction("vector2 GetContentRegionMax()", asFUNCTIONPR([]() {
auto v = ImGui::GetContentRegionMax(); return Vector2(v.x, v.y); }, (), Vector2), asCALL_CDECL);
Expand Down

0 comments on commit cb9b7bb

Please sign in to comment.