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

AngelScript: new script 'example_ImGui_nodeHighlight.as' #3155

Merged
merged 3 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 16 additions & 2 deletions doc/angelscript/Script2Game/BeamClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,24 @@ class BeamClass

/**
* Returns the position of the node
* @param the nuber of the node
* @param the number of the node (counts from 0, see `getNodeCount()`)
* @return vector3 of the world position for the node
*/
vector3 getNodePosition(int nodeNumber);
vector3 getNodePosition(int nodeNumber);

/**
* Is node marked as wheel rim? Note some wheel models use only tire nodes. See https://docs.rigsofrods.org/vehicle-creation/fileformat-truck/#wheels
* @param the number of the node (counts from 0, see `getNodeCount()`)
* @return True if the node is a wheel rim.
*/
vector3 isNodeWheelRim(int nodeNumber);

/**
* Is node marked as wheel tire? Note some wheel models use only tire nodes. See https://docs.rigsofrods.org/vehicle-creation/fileformat-truck/#wheels
* @param the number of the node (counts from 0, see `getNodeCount()`)
* @return True if this is a tire node.
*/
vector3 isNodeWheelTire(int nodeNumber);

/**
* Gets the total amount of nodes of the wheels of the truck.
Expand Down
5 changes: 5 additions & 0 deletions doc/angelscript/Script2Game/GameScriptClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ class GameScriptClass
* Gets screen size in pixels.
*/
vector2 getDisplaySize();

/**
* Gets mouse position in pixels.
*/
Ogre::Vector2 getMouseScreenPosition();

/// @}

Expand Down
272 changes: 272 additions & 0 deletions resources/scripts/example_ImGui_nodeHighlight.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/// \title NODE HIGHLIGHT DEMO
/// \brief How to draw nodes and detect/highligt mouse interaction
///
/// File structure (#region-s)
/// * GAME CALLBACKS - funcs invoked by game
/// * CONFIG - vars and UI funcs
/// * NODE INTERACTION - vars and drawing/UI funcs
/// * HELPERS - funcs
/// ===================================================

//#region GAME CALLBACKS:

// `main()` runs once when script is loaded.
void main()
{
game.registerForEvent(SE_TRUCK_ENTER);
prepareNodeSelectionForNewActor(game.getCurrentTruck());
}

// `frameStep()` runs every frame; `dt` is delta time in seconds.
void frameStep(float dt)
{
ImGui::TextDisabled("::: NODE HIGHLIGHT DEMO:::");
ImGui::Separator();
BeamClass@ actor = game.getCurrentTruck();
if (@actor == null)
{
ImGui::Text("you are on foot");
}
else
{
ImGui::Text("Driving '"+actor.getTruckName()+"' with total "+actor.getNodeCount()+" nodes ("+actor.getWheelNodeCount()+" wheel nodes)");

drawNodeHighlights(actor);

// Left mouse button click = flip the selection state of the
if (mouseHoveredNodeID != -1 && ImGui::IsMouseClicked(0))
{
nodeSelectedStates[mouseHoveredNodeID] = !nodeSelectedStates[mouseHoveredNodeID];
}


drawNodeSelectionUI();

if (ImGui::CollapsingHeader("Config"))
{
drawConfigUI();
}
}
}

// Handles events registered using `game.registerForEvent()`
void eventCallback(scriptEvents ev, int arg1, int arg2ex, int arg3ex, int arg4ex, string arg5ex, string arg6ex, string arg7ex, string arg8ex)
{
if (ev == SE_TRUCK_ENTER)
{
prepareNodeSelectionForNewActor(game.getTruckByNum(arg1));
}
}

//#endregion

// #region CONFIG

color cfgNodeColor = color(0.9, 0.8, 0.5, 1);
color cfgRimNodeColor = color(0.4, 0.8, 0.5, 1);
color cfgTireNodeColor = color(0.5, 0.8, 0.8, 1);
float cfgNodeRadius = 3.f;
int cfgNodeNumSegments = 5;
bool cfgShowWheelNodes = true;
int cfgNodeHoverMaxCursorDist = 10; // Only confirm the hovered state if the cursor is less than 10 pixels away from it in any direction.
color cfgNodeHoverColor = color(1,0,0,1);
float cfgNodeHoverRadius = 5.f;
float cfgNodeHoverThickness = 2.f;
color cfgSelectedNodeColor = color(0.8f,1.f,0.1f,1.f);
float cfgSelectedNodeSize = 7.f; // drawn as rectangle

void drawConfigUI()
{

ImGui::InputFloat("Node radius", cfgNodeRadius);
ImGui::InputInt("Node num segments", cfgNodeNumSegments);
//DOC: bool ColorEdit3(const string&in, color&inout)
ImGui::ColorEdit3("Node color", cfgNodeColor);
ImGui::Checkbox("Show wheel nodes", cfgShowWheelNodes);
if (cfgShowWheelNodes)
{
ImGui::ColorEdit3("Rim node color", cfgRimNodeColor);
ImGui::ColorEdit3("Tire node color", cfgTireNodeColor);
}

ImGui::ColorEdit3("Hover color", cfgNodeHoverColor);
ImGui::InputFloat("Hover radius", cfgNodeHoverRadius);
ImGui::InputFloat("Hover thickness", cfgNodeHoverThickness);
ImGui::InputInt("Hover max cursor dist.", cfgNodeHoverMaxCursorDist);

ImGui::ColorEdit3("Selected node color", cfgSelectedNodeColor);
ImGui::InputFloat("Selected node size", cfgSelectedNodeSize);
}

// #endregion

// #region NODE INTERACTION:

int mouseHoveredNodeID = -1;
array<bool> nodeSelectedStates = array<bool>(); // 1:1 with nodes, resized on `SE_TRUCK_ENTER` event

void prepareNodeSelectionForNewActor(BeamClass@ actor)
{
// should be invoked for each SE_TRUCK_ENTER event, but also in `main()`, in case this script starts when already in vehicle!
// -------------------

nodeSelectedStates.resize(0); // purge the selection array
mouseHoveredNodeID = -1;

if (@actor != null)
{

nodeSelectedStates.resize(actor.getNodeCount()); // scale the selection array, with all nodes deselected (false)
}
}

void drawNodeSelectionUI()
{
int totalSelected = 0;
string strSelected = "";
string strJoiner = "";
for (uint i=0; i < nodeSelectedStates.length(); i++)
{
if (nodeSelectedStates[i])
{
strSelected += (strJoiner + i);
strJoiner = ",";
totalSelected++;
}
}
ImGui::TextDisabled("Selected nodes (total: " + totalSelected + ")");
ImGui::Text(strSelected);
if (ImGui::Button("Select all"))
{
for (uint i=0; i < nodeSelectedStates.length(); i++)
{
nodeSelectedStates[i] = true;
}
}
ImGui::SameLine();
if (ImGui::Button("UnSelect all"))
{
for (uint i=0; i < nodeSelectedStates.length(); i++)
{
nodeSelectedStates[i] = false;
}
}
ImGui::SameLine();
if (ImGui::Button("Flip selection"))
{
for (uint i=0; i < nodeSelectedStates.length(); i++)
{
nodeSelectedStates[i] = !nodeSelectedStates[i];
}
}
}

void drawNodeHighlights(BeamClass@ actor)
{

int mouseClosestNodeID = -1;

int mouseClosestNodeDist = 999999;

ImDrawList@ drawlist = getDummyFullscreenWindow("nodeHighlights");
for (int i = 0; i < actor.getNodeCount(); i++)
{
vector3 posWorld = actor.getNodePosition(i);
color col = cfgNodeColor;
bool wheelNode = false;
if (actor.isNodeWheelRim(i)) {col=cfgRimNodeColor; wheelNode=true;}
if (actor.isNodeWheelTire(i)) {col=cfgTireNodeColor; wheelNode=true;}
if (!wheelNode || cfgShowWheelNodes)
{
vector2 pos = vector2(0,0);
bool inFrontOfCamera = game.getScreenPosFromWorldPos(posWorld, /*out:*/ pos);
if (inFrontOfCamera)
{
// Draw the node
if (nodeSelectedStates[i])
{
// DOC: void AddRectFilled(const vector2&in p_min, const vector2&in p_max, const color&in col, float rounding = 0.0f, int rounding_corners = 15)
vector2 halfSize(cfgSelectedNodeSize/2.f, cfgSelectedNodeSize/2.f);
drawlist.AddRectFilled(pos-halfSize, pos+halfSize, cfgSelectedNodeColor);
}
else
{
//DOC: void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments)
drawlist.AddCircleFilled(pos, cfgNodeRadius, col, cfgNodeNumSegments);
}

// Accumulate mouse hover info
int nodeDist = getMouseShortestDistance(game.getMouseScreenPosition(), pos);
if (nodeDist < mouseClosestNodeDist)
{
mouseClosestNodeID = i;
mouseClosestNodeDist = nodeDist;
}

// Draw mouse hover marker
if (i == mouseHoveredNodeID)
{
// DOC: void AddCircle(const vector2&in center, float radius, const color&in col, int num_segments = 12, float thickness = 1.f)
drawlist.AddCircle(pos, cfgNodeHoverRadius, cfgNodeHoverColor, cfgNodeNumSegments, cfgNodeHoverThickness);
}
}
}
}

// Finalize hover detection
ImGui::Text("<DBG drawNodeHighlights> Mouse closest node: " + mouseClosestNodeID + " (dist:" + mouseClosestNodeDist + ")");
if (mouseClosestNodeDist <= cfgNodeHoverMaxCursorDist)
{
mouseHoveredNodeID = mouseClosestNodeID;
}
else
{
mouseHoveredNodeID = -1;
}
}

// #endregion

// #region HELPERS

// shamelessly copypasted from 'road_editor.as', line 787
ImDrawList@ getDummyFullscreenWindow(const string&in name)
{
// Dummy fullscreen window to draw to
int window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar| ImGuiWindowFlags_NoInputs
| ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus;
ImGui::SetNextWindowPos(vector2(0,0));
ImGui::SetNextWindowSize(game.getDisplaySize());
ImGui::PushStyleColor(/*ImGuiCol_WindowBg*/2, color(0.f,0.f,0.f,0.f)); // Fully transparent background!
ImGui::Begin(name, /*open:*/true, window_flags);
ImDrawList@ drawlist = ImGui::GetWindowDrawList();
ImGui::End();
ImGui::PopStyleColor(1); // WindowBg

return drawlist;
}

// shamelessly copypasted from 'road_editor.as', line 803
int getMouseShortestDistance(vector2 mouse, vector2 target)
{
int dx = iabs(int(mouse.x) - int(target.x));
int dy = iabs(int(mouse.y) - int(target.y));
return imax(dx, dy);
}

int imin(int a, int b)
{
return (a < b) ? a : b;
}

int imax(int a, int b)
{
return (a > b) ? a : b;
}

int iabs(int a)
{
return (a < 0) ? -a : a;
}

// #endregion
24 changes: 24 additions & 0 deletions source/main/physics/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4509,6 +4509,30 @@ Vector3 Actor::getNodePosition(int nodeNumber)
}
}

bool Actor::isNodeWheelRim(int nodeNumber)
{
if (nodeNumber >= 0 && nodeNumber < ar_num_nodes)
{
return ar_nodes[nodeNumber].nd_rim_node;
}
else
{
return false;
}
}

bool Actor::isNodeWheelTire(int nodeNumber)
{
if (nodeNumber >= 0 && nodeNumber < ar_num_nodes)
{
return ar_nodes[nodeNumber].nd_tyre_node;
}
else
{
return false;
}
}

void Actor::WriteDiagnosticDump(std::string const& fileName)
{
// Purpose: to diff against output from https://github.com/only-a-ptr/rigs-of-rods/tree/retro-0407
Expand Down
2 changes: 2 additions & 0 deletions source/main/physics/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class Actor : public RefCountingObject<Actor>
float getTotalMass(bool withLocked=true);
int getNodeCount() { return ar_num_nodes; }
Ogre::Vector3 getNodePosition(int nodeNumber); //!< Returns world position of node
bool isNodeWheelRim(int nodeNumber); //!< Is node marked as wheel rim? Note some wheel models use only tire nodes. See https://docs.rigsofrods.org/vehicle-creation/fileformat-truck/#wheels
bool isNodeWheelTire(int nodeNumber); //!< Is node marked as wheel tire? Note some wheel models use only tire nodes. See https://docs.rigsofrods.org/vehicle-creation/fileformat-truck/#wheels
int getWheelNodeCount() const;
float getWheelSpeed() const { return ar_wheel_speed; }
void reset(bool keep_position = false); //!< call this one to reset a truck from any context
Expand Down
2 changes: 2 additions & 0 deletions source/main/scripting/bindings/ActorAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ void RoR::RegisterActor(asIScriptEngine *engine)
result = engine->RegisterObjectMethod("BeamClass", "float getTotalMass(bool)", asMETHOD(Actor,getTotalMass), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "int getNodeCount()", asMETHOD(Actor,getNodeCount), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "vector3 getNodePosition(int)", asMETHOD(Actor,getNodePosition), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "bool isNodeWheelRim(int)", asMETHOD(Actor,isNodeWheelRim), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "bool isNodeWheelTire(int)", asMETHOD(Actor,isNodeWheelTire), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "int getWheelNodeCount()", asMETHOD(Actor,getWheelNodeCount), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "float getWheelSpeed()", asMETHOD(Actor,getWheelSpeed), asCALL_THISCALL); ROR_ASSERT(result>=0);
result = engine->RegisterObjectMethod("BeamClass", "void reset(bool)", asMETHOD(Actor,reset), asCALL_THISCALL); ROR_ASSERT(result>=0);
Expand Down