diff --git a/resources/scripts/example_ImGui_devDocLinksOffline.as b/resources/scripts/example_ImGui_devDocLinksOffline.as new file mode 100644 index 0000000000..543ab7e147 --- /dev/null +++ b/resources/scripts/example_ImGui_devDocLinksOffline.as @@ -0,0 +1,244 @@ +/// \title QUICK SCRIPT DOC UI (Offline) +/// \brief Hand-crafted offline script docs with links to +/// The local metadata are stored in 2 arrays: +/// * array gDataTypes_ALL ~ Covers value/ref types + singleton names. +/// * arrat gFuncs_ALL ~ Covers global funcs + methods. +// =================================================== + + +// Window [X] button handler +#include "imgui_utils.as" +imgui_utils::CloseWindowPrompt closeBtnHandler; + +//#region Metadata (DB) + +const int TYPEFLAG_asOBJ_REF = 1 << 0; +const int TYPEFLAG_asOBJ_NOCOUNT = 1 << 1; +const int TYPEFLAG_BUILTIN = 1 << 2; +array gDataTypes_ALL = array(); // < Filled by DataType constructor, do not push manually. + +array gFuncs_ALL = array(); // < Filled by FuncDef constructor, do not push manually. + +// Primitives +DataType@ gDataTypes_string = DataType("string", "http://www.angelcode.com/angelscript/sdk/docs/manual/doc_script_stdlib_string.html", "built-in string", TYPEFLAG_BUILTIN); +DataType@ gDataTypes_bool = DataType("bool", "http://www.angelcode.com/angelscript/sdk/docs/manual/doc_datatypes_primitives.html#bool", "primitive bool", TYPEFLAG_BUILTIN); +DataType@ gDataTypes_int = DataType("int", "http://www.angelcode.com/angelscript/sdk/docs/manual/doc_datatypes_primitives.html#int", "primitive int", TYPEFLAG_BUILTIN); +DataType@ gDataTypes_dictionary = DataType("dictionary", "http://www.angelcode.com/angelscript/sdk/docs/manual/doc_datatypes_dictionary.html", "built-in dictionary", TYPEFLAG_BUILTIN); + +// General +DataType@ gDataTypes_BeamClass = DataType("BeamClass", "https://developer.rigsofrods.org/d8/da5/class_script2_game_1_1_beam_class.html", "A loaded 'actor' (vehicle/load/machine)", TYPEFLAG_asOBJ_REF); +DataType@ gDataTypes_TerrainClass = DataType("TerrainClass", "https://developer.rigsofrods.org/d8/deb/class_script2_game_1_1_terrain_class.html", "A loaded terrain", TYPEFLAG_asOBJ_REF); +DataType@ gDataTypes_CVarClass = DataType("CVarClass", "https://developer.rigsofrods.org/d8/de9/class_script2_game_1_1_c_var_class.html", "Console variable, from RoR.cfg or created by user/script", TYPEFLAG_asOBJ_REF | TYPEFLAG_asOBJ_NOCOUNT); +DataType@ gDataTypes_CacheEntryClass = DataType("CacheEntryClass", "", "Installed mod entry", TYPEFLAG_asOBJ_REF); + +// Singletons (with methods) +DataType@ gDataTypes_GameScriptClass = DataType("GameScriptClass", "https://developer.rigsofrods.org/dc/d63/class_script2_game_1_1_game_script_class.html", "Game events, message queue, vehicle and terrain data", TYPEFLAG_asOBJ_REF | TYPEFLAG_asOBJ_NOCOUNT, "game"); +Func@ gFuncDefs_getCurrentTruck = Func(gDataTypes_GameScriptClass, "getCurrentTruck", {}, gDataTypes_BeamClass, "Get player's current vehicle, returns if on foot."); +Func@ gFuncs_game_getTerrain = Func(gDataTypes_GameScriptClass, "getTerrain", {}, gDataTypes_TerrainClass, "Get currently loaded terrain, returns if in main menu."); + +DataType@ gDataTypes_ConsoleClass = DataType("ConsoleClass", "https://developer.rigsofrods.org/d3/d4f/class_script2_game_1_1_console_class.html", "Console variables - read and write", TYPEFLAG_asOBJ_REF | TYPEFLAG_asOBJ_NOCOUNT, "console"); +Func@ gFunc_console_cVarGet = Func(gDataTypes_ConsoleClass, "cVarGet", { FuncArg(gDataTypes_string, "name", 0), FuncArg(gDataTypes_int, "flags", 0) }, gDataTypes_CVarClass, "Gets or creates a console var"); +Func@ gFunc_console_cVarFinds = Func(gDataTypes_ConsoleClass, "cVarFind", { FuncArg(gDataTypes_string, "name", 0) }, gDataTypes_CVarClass, "Gets an existing console var, or returns "); + +DataType@ gDataTypes_InputEngineClass = DataType("InputEngineClass", "https://developer.rigsofrods.org/d2/d8d/class_script2_game_1_1_input_engine_class.html", "Input keybinds and keystates",TYPEFLAG_asOBJ_REF | TYPEFLAG_asOBJ_NOCOUNT,"inputs"); +Func@ gFuncs_inputs_getEventCommandTrimmed = Func(gDataTypes_InputEngineClass, "getEventCommandTrimmed", { FuncArg(gDataTypes_int, "eventID", 0) }, gDataTypes_string, "Get keybind for input event"); + +DataType@ gDataTypes_CacheSystemClass = DataType("CacheSystemClass", "", "Installed mods - query or create",TYPEFLAG_asOBJ_REF | TYPEFLAG_asOBJ_NOCOUNT, "modcache"); + Func@ gFuncs_modcache_findEntryByFilename = Func(gDataTypes_CacheSystemClass, "findEntryByFilename", { FuncArg(gDataTypes_int, "type", 0), FuncArg(gDataTypes_bool, "partial", 0), FuncArg(gDataTypes_string, "name", 0) }, gDataTypes_CacheEntryClass, "Find single mod by fulltext"); + +//#endregion + +//#region DETAIL: class DataType +class DataType +{ + string name; + string description; + string singleton_name; //!< The global var name, only selected data types + string url; + int typeflags; + + DataType(string _n, string _u, string _desc, int _typeflags, string _singleton = "") + { + name = _n; + url = _u; + description = _desc; + typeflags = _typeflags; + singleton_name = _singleton; + + gDataTypes_ALL.insertLast(this); + } + void drawClassInfo() + { + ImGui::Bullet(); + if (url != "") + { + imgui_utils::ImHyperlink( url, name, false); + } + else // oops, undocumented stuff!! + { + ImGui::Text(name); + } + if (typeflags & TYPEFLAG_asOBJ_REF != 0) + { + ImGui::SameLine(); ImGui::Text("[ref]"); + } + if (typeflags & TYPEFLAG_asOBJ_NOCOUNT != 0) + { + ImGui::SameLine(); ImGui::Text("[nocount]"); + } + ImGui::SameLine(); ImGui::TextDisabled(description); + } + void drawSingletonInfo() + { + bool expanded = ImGui::TreeNode("##"+name+url); + ImGui::SameLine(); + ImGui::SameLine(); + if (url != "") + { + imgui_utils:: ImHyperlink( url, singleton_name, false); + } + else // oops, undocumented stuff!! + { + ImGui::Text(singleton_name); + } + if (expanded) + { + for (uint i = 0; i < gFuncs_ALL.length(); i++) + { + Func@ func = gFuncs_ALL[i]; + if (@func.obj == @this) + { + func.drawFunc(); + } + } + ImGui::TreePop(); + } + else + { + ImGui::SameLine(); ImGui::TextDisabled(description); + } + } + void drawAsArg(string arg_name) + { + imgui_utils:: ImHyperlink( url, name, false); + ImGui::SameLine(); ImGui::Text(arg_name); + + } + void drawAsRetval() + { + imgui_utils:: ImHyperlink( url, name, false); + } +} ; + +//#endregion (DETAIL: class DataType) + +//#region DETAIL: class Func + + +class Func +{ + DataType@ obj; // null if it's a global function + DataType@ returns; + string name; + string description; + array args; + + Func(DataType@ _obj, string _n, array _args, DataType@ _ret, string _desc) + { + @obj = @_obj; + name = _n; + args = _args; + @returns = @_ret; + description = _desc; + gFuncs_ALL.insertLast(this); + } + void drawFunc() + { + ImGui::Text(" ." + name + "("); + for (uint i = 0; i < args.length(); i++) + { + FuncArg@ farg = args[i]; + ImGui::SameLine(); farg.type.drawAsArg(farg.name); + } + ImGui::SameLine(); ImGui::Text(") -> "); + ImGui::SameLine(); returns.drawAsRetval(); + ImGui::SameLine(); ImGui::TextDisabled(description); + } +}; + +class FuncArg +{ + DataType@ type; + string name; + int flags = 0; + FuncArg(DataType@ _t, string _n, int _flags) + { + name = _n; + @type = @_t; + flags = _flags; + } + +} ; +//#endregion (DETAIL: class Func) + +//#region UI drawing +bool gTypesShowAll = false; +void drawScriptQuickLinksUI() +{ + ImGui::TextDisabled(" ::: G L O B A L S ::: "); + for (uint i = 0; i < gDataTypes_ALL.length(); i++) + { + if ( gDataTypes_ALL[i].singleton_name != "") + { + gDataTypes_ALL[i].drawSingletonInfo(); + } + } + ImGui::Text(""); + + ImGui::TextDisabled(" ::: T Y P E S ::: "); + ImGui::SameLine(); ImGui::Checkbox("Show all", gTypesShowAll); + for (uint i = 0; i < gDataTypes_ALL.length(); i++) + { + DataType@ datatype = gDataTypes_ALL[i]; + // by default do not draw builtins and singleton-classes + const bool builtin = (datatype.typeflags & TYPEFLAG_BUILTIN) != 0; + const bool singleton = datatype.singleton_name != ""; + if ( (!builtin && !singleton) || gTypesShowAll ) + { + gDataTypes_ALL[i].drawClassInfo(); + } + } + ImGui::Text(""); + + + + // `modcache` + // ImGui::Text(" .findEntryByFilename(/*loaderType*/LOADER_TYPE_ALLBEAM, /*partial:*/false, actor.getTruckFileName());"); + +} + +// ~~~ Script quick links ~~~ HELPERS ~~~ // + +void drawFunc(string name, string ret_obj, string ret_url_local) +{ + ImGui::Text(name + "()"); + ImGui::SameLine(); +} + + + +//#endregion (UI drawing) + +// `frameStep()` runs every frame; `dt` is delta time in seconds. +void frameStep(float dt) +{ + + if (ImGui::Begin("Scripting - quick links UI", closeBtnHandler.windowOpen, 0)) + { + ImGui::TextDisabled(" * * OFFLINE EDITION * * (Only popular APIs) * * "); + ImGui::Separator(); + drawScriptQuickLinksUI(); + ImGui::End(); + } +} + diff --git a/resources/scripts/example_ImGui_hotkeyHighlight.as b/resources/scripts/example_ImGui_hotkeyHighlight.as new file mode 100644 index 0000000000..60e5baebcd --- /dev/null +++ b/resources/scripts/example_ImGui_hotkeyHighlight.as @@ -0,0 +1,91 @@ +// TERRAIN EDITOR HITS UI +// =================================================== + +// Window [X] button handler +#include "imgui_utils.as" +imgui_utils::CloseWindowPrompt closeBtnHandler; + +const color TITLEART_COLOR (0.7f, 0.9f, 0.8f, 1.f); +const color ACTIVE_KEYBIND_COL(0.8f, 1.0f, 0.2f, 1.f); +CVarClass@ cvar_sim_state = console.cVarFind("sim_state"); // built in + +// `frameStep()` runs every frame; `dt` is delta time in seconds. +void frameStep(float dt) +{ + + + ImGui::Begin("terrnEditorUI *!* ALPHA> *!*", closeBtnHandler.windowOpen, 0); + ImGui::Dummy(vector2(400, 1)); + drawTerrnEditTitleArt( ); + + ImGui::Separator(); + + drawTerrnEditHelpUI(); + + ImGui::End(); //"terrnEditorUI" + +} + +// #region TerrnEditor title art +void drawTerrnEditTitleArt() +{ + string title=" T E R R N E D I T O R "; + string art=" ^ ^ ^ ~ ^ ^ ^ ^ ~ ^ ^ ^ "; + vector2 title_size = ImGui::CalcTextSize(title); + vector2 art_size = ImGui::CalcTextSize(art); + + vector2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,-4), TITLEART_COLOR, art); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,0), TITLEART_COLOR, art); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,4), TITLEART_COLOR, art); + ImGui::SetCursorPos(ImGui::GetCursorPos() + vector2(art_size.x, 0)); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,-3), TITLEART_COLOR, title); + ImGui::SetCursorPos(ImGui::GetCursorPos() + vector2( title_size.x + 5, 0)); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,-4), TITLEART_COLOR, art); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,0), TITLEART_COLOR, art); + ImGui::GetWindowDrawList().AddText(pos+vector2(0,4), TITLEART_COLOR, art); + ImGui::Text("");//spacer +} +//#endregion title art + +// #region TerrnEditor info +void drawTerrnEditHelpUI() +{ + if (cvar_sim_state.getInt() != 3) + { + ImGui::Text("Activate terrain editing mode with"); + ImGui::SameLine(); + ImDrawEventKeybindHighlighted(EV_COMMON_TOGGLE_TERRAIN_EDITOR); + } + else + { + ImGui::TextDisabled(" ^ GLOBAL HOTKEYS ^"); + ImGui::AlignTextToFramePadding(); + ImGui::Bullet(); + ImGui::SameLine(); ImDrawEventKeybindHighlighted(EV_COMMON_TOGGLE_TERRAIN_EDITOR); + ImGui::SameLine(); ImGui::Text("Exit terrain editor"); + } +} +//#endregion TerrnEditor info + +//#region imgui_utils < -- ImDrawEventKeybindHighlighted(inputEvents ev) +void ImDrawEventKeybindHighlighted(inputEvents ev) +{ + bool active = inputs.getEventBoolValue(ev); + string keybind = "" + inputs.getEventCommandTrimmed(ev); + if (active) + { + ImGui::PushStyleColor(ImGuiCol_Text, ACTIVE_KEYBIND_COL); + } + ImGui::BeginChildFrame(ev, ImGui::CalcTextSize(keybind) + vector2(8,6)); + ImGui::Text(keybind); + ImGui::EndChildFrame(); + if (active) + { + ImGui::PopStyleColor(); // ImGuiCol_Text + } +} +//#endregion draw ev keybind highlighted + diff --git a/resources/scripts/example_RigEditor_alpha.as b/resources/scripts/example_actor_RigEditorAlpha.as similarity index 70% rename from resources/scripts/example_RigEditor_alpha.as rename to resources/scripts/example_actor_RigEditorAlpha.as index 008db3cee8..c46836c677 100644 --- a/resources/scripts/example_RigEditor_alpha.as +++ b/resources/scripts/example_actor_RigEditorAlpha.as @@ -4,8 +4,19 @@ /// Written and auto-indented using script_editor.as! // =================================================== +// Window [X] button handler #include "imgui_utils.as" +imgui_utils::CloseWindowPrompt closeBtnHandler; + +// node/beam drawing #include "gridviewer_utils.as" +gridviewer_utils::GridViewer viewer_x; +gridviewer_utils::GridViewer viewer_y; +gridviewer_utils:: GridViewer viewer_z; + +// genericDoc editor +#include "genericdoc_utils.as" +genericdoc_utils::GenericDocEditor gdEditor; // ----- config ----- @@ -14,19 +25,16 @@ int m_statusbar_height_pixels = 25; // ---- variables ----- GenericDocumentClass@ m_displayed_document = null; -int m_hovered_token_pos = -1; -int m_focused_token_pos = -1; + string m_error_str; string m_project_creation_pending; // name of the truck file which will become available in modcache. CacheEntryClass@ m_project_entry; -gridviewer_utils::GridViewer viewer_x; -gridviewer_utils::GridViewer viewer_y; -gridviewer_utils:: GridViewer viewer_z; + dictionary@ m_modcache_results = null; CacheEntryClass@ m_awaiting_load_bundle_entry = null; color node_color = color(0.8, 0.9, 0.2, 1.0); float node_radius = 1.f; -imgui_utils::CloseWindowPrompt closeBtnHandler; // Window [X] button handler + // ---- functions ---- @@ -112,16 +120,16 @@ void drawWindow() { if (@m_displayed_document != null) { - drawDocumentBody(); + + gdEditor.drawDocumentBody(); } } ImGui::EndChild(); // "docBody" - if ( m_focused_token_pos > -1) - { - ImGui::Separator(); - drawTokenEditPanel(); - } + + ImGui::Separator(); + gdEditor.drawTokenEditPanel(); + } ImGui::EndChild(); // "leftPane" ImGui::SameLine(); @@ -311,6 +319,7 @@ void loadDocument() return; } @m_displayed_document = doc; + gdEditor.setDocument(m_displayed_document); } void loadAndFixupDocument() @@ -348,114 +357,13 @@ void loadAndFixupDocument() if (!ctx.endOfFile(2)) { ctx.setTokFloat(2, 8990); // special "Project" category } + + gdEditor.setDocument(m_displayed_document); } // #endregion -// #region Tokenized document drawing - -void drawDocumentBody() -{ - ImGui::PushID("docBody"); - bool hover_found = false; - - GenericDocContextClass reader(m_displayed_document); - while (!reader.endOfFile()) - { - - switch (reader.tokenType()) - { - // These tokens are always at start of line - case TOKEN_TYPE_KEYWORD: { - ImGui::TextColored(tokenColor(reader), reader.getTokKeyword()); - break; - } - case TOKEN_TYPE_COMMENT: { - ImGui::TextColored(tokenColor(reader), ";" + reader.getTokComment()); - break; - } - - // Linebreak is implicit in DearIMGUI, no action needed - case TOKEN_TYPE_LINEBREAK: { - if (reader.getPos() != 0 && reader.tokenType(-1) != TOKEN_TYPE_LINEBREAK) - { - ImGui::SameLine(); - } - ImGui::TextColored(tokenColor(reader), "
"); - // ImGui::SameLine(); ImGui::Text(""); // hack to fix highlight of last token on line. - break; - } - - // Other tokens come anywhere - delimiting logic is needed - default: { - if (reader.getPos() != 0 && reader.tokenType(-1) != TOKEN_TYPE_LINEBREAK) - { - ImGui::SameLine(); - // string delimiter = (reader.tokenType(-1) == TOKEN_TYPE_KEYWORD) ? " " : ", "; - // ImGui::Text(delimiter); - // ImGui::SameLine(); - } - } - - switch (reader.tokenType()) - { - case TOKEN_TYPE_STRING: { - ImGui::TextColored(tokenColor(reader), "\"" + reader.getTokString() + "\""); - break; - } - case TOKEN_TYPE_NUMBER: { - ImGui::TextColored(tokenColor(reader), "" + reader.getTokFloat()); - break; - } - case TOKEN_TYPE_BOOL: { - ImGui::TextColored(tokenColor(reader), ""+reader.getTokBool()); - break; - } - } - } - - if (ImGui::IsItemHovered()) { - m_hovered_token_pos = reader.getPos() ; - hover_found = true; - } - - if (ImGui::IsItemClicked(0)) - { - m_focused_token_pos = reader.getPos(); - } - - reader.moveNext(); - - } - - if (!hover_found) - { - m_hovered_token_pos = -1; - } - - ImGui::PopID(); // "docBody" -} - -void drawTokenEditPanel() -{ - GenericDocContextClass reader(m_displayed_document); - while (!reader.endOfFile() && (reader.getPos() != uint(m_focused_token_pos))) - { - reader.moveNext(); - } - - if (reader.endOfFile()) - { - ImGui::Text("EOF!!"); - } - else - { - ImGui::TextDisabled("Token pos: "); ImGui::SameLine(); ImGui::Text("" + reader.getPos()); - ImGui::TextDisabled("Token type: "); ImGui::SameLine(); ImGui::Text(tokenTypeStr(reader.tokenType())); - } -} - -// #endregion + // #region GridViewer node drawing @@ -502,45 +410,7 @@ void drawNodes(gridviewer_utils::GridViewer @viewer) // #endregion -// #region Token drawing helpers -string tokenTypeStr(TokenType t) -{ - switch (t) - { - case TOKEN_TYPE_NUMBER: return "Number"; - case TOKEN_TYPE_STRING: return "String"; - case TOKEN_TYPE_BOOL: return "Boolean"; - case TOKEN_TYPE_COMMENT: return "Comment"; - case TOKEN_TYPE_LINEBREAK: return "Line break"; - case TOKEN_TYPE_KEYWORD: return "Keyword"; - } - return "?"; -} - -color tokenColor(GenericDocContextClass@ reader) -{ - if (m_focused_token_pos > -1 && reader.getPos() == uint(m_focused_token_pos)) - { - return color(0.9f, 0.1f, 0.1f, 1.f); - } - - if (m_hovered_token_pos > -1 && reader.getPos() == uint(m_hovered_token_pos)) - { - return color(0.1f, 1.f, 0.1f, 1.f); - } - - switch (reader.tokenType()) - { - case TOKEN_TYPE_KEYWORD: return color(1.f, 1.f, 0.f, 1.f); - case TOKEN_TYPE_COMMENT: return color(0.5f, 0.5f, 0.5f, 1.f); - case TOKEN_TYPE_STRING: return color(0.f, 1.f, 1.f, 1.f); - case TOKEN_TYPE_NUMBER: return color(0.9, 0.9, 0.9, 1.f); - case TOKEN_TYPE_BOOL: return color(1.f, 0.f, 1.f, 1.f); - case TOKEN_TYPE_LINEBREAK: return color(0.66f, 0.55f, 0.33f, 1.f); - } - return color(0.9, 0.9, 0.9, 1.f); -} -// #endregion + // #region Event handling or polling void checkForCreatedProject() diff --git a/resources/scripts/example_game_shockTuning.as b/resources/scripts/example_actor_shockTuning.as similarity index 100% rename from resources/scripts/example_game_shockTuning.as rename to resources/scripts/example_actor_shockTuning.as diff --git a/resources/scripts/example_game_gridSpawn.as b/resources/scripts/example_game_gridSpawn.as new file mode 100644 index 0000000000..6e5cd44c1a --- /dev/null +++ b/resources/scripts/example_game_gridSpawn.as @@ -0,0 +1,71 @@ +// BUS STACKING SCRIPT for performance testing +// requested from Discord#dev: +// https://discord.com/channels/136544456244461568/189904947649708032/1242063394601828503 +// =================================================== + +// Window [X] button handler +#include "imgui_utils.as" +imgui_utils::CloseWindowPrompt closeBtnHandler; + +const vector3 Y_AXIS(0,1,0); + +int cfgXCount = 4; +int cfgYCount = 2; // up axis +int cfgZCount = 4; +float cfgXStepMeters = 12.2f; +float cfgYStepMeters = 3.5f; // up axis +float cfgZStepMeters = 4.f; + +void frameStep(float dt) +{ + if (ImGui::Begin("::: GRID SPAWN :::", closeBtnHandler.windowOpen, 0)) + { + // Draw the "Terminate this script?" prompt on the top (if not disabled by config). + closeBtnHandler.draw(); + + drawMainPanel(); + + ImGui::End(); + } +} + +void drawMainPanel() +{ + +if (ImGui::InputInt("X count", cfgXCount)) { if (cfgXCount < 1) {cfgXCount=1;} } +if (ImGui::InputInt("Y count (up)", cfgYCount)) { if (cfgYCount < 1) {cfgYCount=1;} } +if (ImGui::InputInt("Z count", cfgZCount)) { if (cfgZCount < 1) {cfgZCount=1;} } + ImGui::InputFloat("X step (m)", cfgXStepMeters); + ImGui::InputFloat("Y (up) step (m)", cfgYStepMeters); + ImGui::InputFloat("Z step (m)", cfgZStepMeters); + + ImGui::Separator(); + if (ImGui::Button("stack busses! (total "+cfgXCount*cfgYCount*cfgZCount+")")) + { + stackBusses(); + } +} + +void stackBusses() +{ + bool freePosition = false; + quaternion gridRot(game.getPersonRotation(), Y_AXIS); + for (int iy = 0; iy < cfgYCount; iy++) + { + for (int ix = 0; ix < cfgXCount; ix++) + { + for (int iz = 0; iz < cfgZCount; iz++) + { + vector3 gridPos( ix * cfgXStepMeters, iy * cfgYStepMeters, iz * cfgZStepMeters); + vector3 spawnPos = game.getPersonPosition() + (gridRot * gridPos); + + game.pushMessage(MSG_SIM_SPAWN_ACTOR_REQUESTED, { + {'filename', '95bbUID-agoras.truck'}, + {'free_position', freePosition}, + {'position', spawnPos}, + {'rotation', quaternion(game.getPersonRotation(), Y_AXIS)} + }); + } + } + } +} diff --git a/resources/scripts/example_ogre_terrnBatcher.as b/resources/scripts/example_ogre_terrnBatcher.as index 24683bf38a..9b0811cf68 100644 --- a/resources/scripts/example_ogre_terrnBatcher.as +++ b/resources/scripts/example_ogre_terrnBatcher.as @@ -18,7 +18,7 @@ #include "imgui_utils.as" imgui_utils::CloseWindowPrompt closeBtnHandler; -TerrnBatcherUI tbUI; + void main() { @@ -27,764 +27,766 @@ void main() void frameStep(float dt) { - if (ImGui::Begin("TERRN BATCHER [ALPHA]", closeBtnHandler.windowOpen, ImGuiWindowFlags_AlwaysAutoResize)) + if ( ImGui::Begin("TERRN BATCHER [ALPHA]", closeBtnHandler.windowOpen, ImGuiWindowFlags_AlwaysAutoResize)) { + // Draw the "Terminate this script?" prompt on the top (if not disabled by config). closeBtnHandler.draw(); - tbUI.draw(); - ImGui::End(); + + drawMainPanel(); + + ImGui::End(); } } -class TerrnBatcherUI // Based on Inspector UI, can pick scenenodes and prepare Schedule for the batching. + +// dictionary> tbuiSelection; ~ Used by the Pick buttons. +// * Keeping scenenode pointers is dangerous - they aren't refcounted. +// * We keep a dictionary "scenenode name" -> child node indices +dictionary tbuiSelection; + +// Checkboxes in 'batch controls' +bool tbuiHideOriginal = true; +bool tbuiShowNew = true; + +// Checkboxes in 'inspector' +bool tbuiShowDumpButton = false; + +// Counters +uint tbuiNumBatchesCreated = 0; +uint tbuiNumMeshesDumped = 0; + +// Constants +string tbuiOutputsNodeName = "TerrnBatcher outputs"; + +//#region Draw UI - main +void drawMainPanel() { - // dictionary> tbuiSelection; ~ Used by the Pick buttons. - // * Keeping scenenode pointers is dangerous - they aren't refcounted. - // * We keep a dictionary "scenenode name" -> child node indices - dictionary tbuiSelection; - // Checkboxes in 'batch controls' - bool tbuiHideOriginal = true; - bool tbuiShowNew = true; + ImGui::Text("shows how you can traverse scene and merge meshes/textures together for better FPS"); + ImGui::Text("Note that all element names are just simple strings, even if they contain '/'."); - // Checkboxes in 'inspector' - bool tbuiShowDumpButton = false; + TerrainClass@ terrain = game.getTerrain(); + if (@terrain == null) + { + ImGui::TextDisabled("ERROR No terrain loaded"); + return; + } - // Counters - uint tbuiNumBatchesCreated = 0; - uint tbuiNumMeshesDumped = 0; + Ogre::Root@ root = Ogre::Root::getSingleton(); + if (@root == null) + { + ImGui::TextDisabled("ERROR Cannot retrieve OGRE `Root` object"); + return; + } - // Constants - string tbuiOutputsNodeName = "TerrnBatcher outputs"; - //#region Draw UI - main - void draw() + Ogre::SceneManager@ sceneMgr = findSceneManager(root, "main_scene_manager"); + if (@sceneMgr == null) { - - ImGui::Text("shows how you can traverse scene and merge meshes/textures together for better FPS"); - ImGui::Text("Note that all element names are just simple strings, even if they contain '/'."); - - TerrainClass@ terrain = game.getTerrain(); - if (@terrain == null) - { - ImGui::TextDisabled("ERROR No terrain loaded"); - return; - } - - Ogre::Root@ root = Ogre::Root::getSingleton(); - if (@root == null) - { - ImGui::TextDisabled("ERROR Cannot retrieve OGRE `Root` object"); - return; - } - - - Ogre::SceneManager@ sceneMgr = this.findSceneManager(root, "main_scene_manager"); - if (@sceneMgr == null) - { - ImGui::TextDisabled("ERROR Cannot retrieve scene manager"); - return; - } - - Ogre::SceneNode@ rootNode = sceneMgr.getRootSceneNode(); - if (@rootNode == null) - { - ImGui::TextDisabled("ERROR Cannot retrieve root scene node"); - return; - } - - Ogre::SceneNode@ terrnNode = this.findChildSceneNode(rootNode, "Terrain: " + terrain.getTerrainName()); - if (@terrnNode==null) - { - ImGui::TextDisabled("ERROR Cannot retrieve terrain's grouping scene node"); - return; - } - - Ogre::SceneNode@ outputsNode = this.findChildSceneNode(terrnNode, tbuiOutputsNodeName); - if (@outputsNode==null) - { - terrnNode.createChildSceneNode(tbuiOutputsNodeName); - } - - ImGui::Separator(); - ImGui::TextDisabled(" S E L E C T I O N :"); - this.drawSelectionBasket(terrnNode); - this.drawSelectionBatchControls(terrnNode); - - ImGui::Separator(); - ImGui::TextDisabled(" S C E N E G R A P H :"); - ImGui::Checkbox("Enable mesh dumping (write .mesh file to logs directory)", tbuiShowDumpButton); - this.drawTreeNodeOgreSceneNodeRecursive(terrnNode); + ImGui::TextDisabled("ERROR Cannot retrieve scene manager"); + return; } - //#endregion Draw UI - main - //#region Draw UI - schedule - void drawSelectionBatchControls(Ogre::SceneNode@ terrnNode) + Ogre::SceneNode@ rootNode = sceneMgr.getRootSceneNode(); + if (@rootNode == null) { - if (ImGui::Button(" > > > B A T C H < < < ")) - { - this.batchSelectedMeshes(terrnNode); - } - ImGui::SameLine(); - ImGui::Checkbox("hide origial", /*[inout]*/ tbuiHideOriginal); - ImGui::SameLine(); - ImGui::Checkbox("show new", /*[inout]*/ tbuiShowNew); + ImGui::TextDisabled("ERROR Cannot retrieve root scene node"); + return; + } + + Ogre::SceneNode@ terrnNode = findChildSceneNode(rootNode, "Terrain: " + terrain.getTerrainName()); + if (@terrnNode==null) + { + ImGui::TextDisabled("ERROR Cannot retrieve terrain's grouping scene node"); + return; + } + + Ogre::SceneNode@ outputsNode = findChildSceneNode(terrnNode, tbuiOutputsNodeName); + if (@outputsNode==null) + { + terrnNode.createChildSceneNode(tbuiOutputsNodeName); } - void drawSelectionBasket(Ogre::SceneNode@ terrnNode) + ImGui::Separator(); + ImGui::TextDisabled(" S E L E C T I O N :"); + drawSelectionBasket(terrnNode); + drawSelectionBatchControls(terrnNode); + + ImGui::Separator(); + ImGui::TextDisabled(" S C E N E G R A P H :"); + ImGui::Checkbox("Enable mesh dumping (write .mesh file to logs directory)", tbuiShowDumpButton); + drawTreeNodeOgreSceneNodeRecursive(terrnNode); +} +//#endregion Draw UI - main + +//#region Draw UI - schedule +void drawSelectionBatchControls(Ogre::SceneNode@ terrnNode) +{ + if (ImGui::Button(" > > > B A T C H < < < ")) { - array tobjNodes = tbuiSelection.getKeys(); + batchSelectedMeshes(terrnNode); + } + ImGui::SameLine(); + ImGui::Checkbox("hide origial", /*[inout]*/ tbuiHideOriginal); + ImGui::SameLine(); + ImGui::Checkbox("show new", /*[inout]*/ tbuiShowNew); +} + +void drawSelectionBasket(Ogre::SceneNode@ terrnNode) +{ + array tobjNodes = tbuiSelection.getKeys(); + + if (tobjNodes.length() == 0) + { + ImGui::Text("nothing selected. Use 'pick' buttons in tree below."); + return; + } + + // totals + dictionary materialSet; // material name => int numHits (-1 means not trivial - not supported) + +dictionary uniqueSubmeshSet; // dictionary { mesh name + ':' + submesh index => uint numVerts } +dictionary uniqueSubmeshSetHits; // dictionary { mesh name + ':' + submesh index => int numHits } + + + for (uint i=0; i "); - if (tobjNodes.length() == 0) + Ogre::SceneNode@ tobjNode = findChildSceneNode(terrnNode,tobjNodes[i]); + if (@tobjNode == null) { - ImGui::Text("nothing selected. Use 'pick' buttons in tree below."); - return; + ImGui::SameLine(); + ImGui::Text("ERROR - not found in scene graph!"); } - - // totals - dictionary materialSet; // material name => int numHits (-1 means not trivial - not supported) - - dictionary uniqueSubmeshSet; // dictionary { mesh name + ':' + submesh index => uint numVerts } - dictionary uniqueSubmeshSetHits; // dictionary { mesh name + ':' + submesh index => int numHits } - - - for (uint i=0; i "); - - Ogre::SceneNode@ tobjNode = this.findChildSceneNode(terrnNode,tobjNodes[i]); - if (@tobjNode == null) + ImGui::SameLine(); + array@ nodeIndices = cast>(tbuiSelection[tobjNodes[i]]); + if (@nodeIndices == null) { - ImGui::SameLine(); - ImGui::Text("ERROR - not found in scene graph!"); + ImGui::Text("ERROR cast failed"); } else { - ImGui::SameLine(); - array@ nodeIndices = cast>(tbuiSelection[tobjNodes[i]]); - if (@nodeIndices == null) + ImGui::Text(nodeIndices.length() + " nodes selected"); + + for (uint j=0; j materialKeys = materialSet.getKeys(); + TerrainClass@ terrain = game.getTerrain(); + for (uint i = 0; i materialKeys = materialSet.getKeys(); - TerrainClass@ terrain = game.getTerrain(); - for (uint i = 0; i " + tex.getName() + " ("+tex.getWidth()+" x " + tex.getHeight() + ") ["+matNumHits+" hits]"); - } - else - { - ImGui::SameLine(); - ImGui::Text("TBD: non trivial materials are not supported yet"); - } + ImGui::SameLine(); + ImGui::Text("ERROR-could not find material in MaterialManager"); } - - // now traverse set of unique submeshes - ImGui::TextDisabled("(by submesh):"); - array submeshKeys = uniqueSubmeshSet.getKeys(); - for (uint i = 0; i "+numVerts+" verts ["+uint(uniqueSubmeshSetHits[submeshKeys[i]])+" hits]"); + Ogre::TexturePtr tex = getTrivialCaseMaterialTexture(mat); + ImGui::SameLine(); + ImGui::Text(" --> " + tex.getName() + " ("+tex.getWidth()+" x " + tex.getHeight() + ") ["+matNumHits+" hits]"); + } + else + { + ImGui::SameLine(); + ImGui::Text("TBD: non trivial materials are not supported yet"); } } - // helper for `drawSelectionBasket()` - void basketProcessSingleInput(dictionary&inout materialSet, dictionary&inout subMeshSet, dictionary&inout subMeshSetHits, Ogre::SceneNode@ tobjNode, int childIndex) + // now traverse set of unique submeshes + ImGui::TextDisabled("(by submesh):"); + array submeshKeys = uniqueSubmeshSet.getKeys(); + for (uint i = 0; i@ nodeIndices = cast>(tbuiSelection[tobjNode.getName()]); - if (nodeIndices[childIndex] >= children.length()) - { - ImGui::Text("ERROR: '"+tobjNode.getName()+"' has only "+children.length()+"children, requested index"+childIndex); - return; - } - - // traverse Entities/subentities and collect materials. - Ogre::SceneNode@ pickedNode = cast(children[nodeIndices[childIndex]]); - Ogre::MovableObjectArray@ movables = pickedNode.getAttachedObjects(); - if (movables.length() == 1 && movables[0].getMovableType() == "ManualObject") - { - // Trivial case - single attached manual object (probably previous terrnbatcher result) - ImGui::Text("ManualObject="+movables[0].getName()); - } - else if (movables.length() == 1 && movables[0].getMovableType() == "Entity") + uint numVerts = uint(uniqueSubmeshSet[submeshKeys[i]]); + ImGui::BulletText(submeshKeys[i] + " --> "+numVerts+" verts ["+uint(uniqueSubmeshSetHits[submeshKeys[i]])+" hits]"); + } +} + +// helper for `drawSelectionBasket()` +void basketProcessSingleInput(dictionary&inout materialSet, dictionary&inout subMeshSet, dictionary&inout subMeshSetHits, Ogre::SceneNode@ tobjNode, int childIndex) +{ + Ogre::ChildNodeArray@ children = tobjNode.getChildren(); + array@ nodeIndices = cast>(tbuiSelection[tobjNode.getName()]); + if (nodeIndices[childIndex] >= children.length()) + { + ImGui::Text("ERROR: '"+tobjNode.getName()+"' has only "+children.length()+"children, requested index"+childIndex); + return; + } + + // traverse Entities/subentities and collect materials. + Ogre::SceneNode@ pickedNode = cast(children[nodeIndices[childIndex]]); + Ogre::MovableObjectArray@ movables = pickedNode.getAttachedObjects(); + if (movables.length() == 1 && movables[0].getMovableType() == "ManualObject") + { + // Trivial case - single attached manual object (probably previous terrnbatcher result) + ImGui::Text("ManualObject="+movables[0].getName()); + } + else if (movables.length() == 1 && movables[0].getMovableType() == "Entity") + { + // trivial case - single attached entity + Ogre::Entity@ ent = cast(movables[0]); + Ogre::SubEntityArray@ subEntities = ent.getSubEntities(); + for (uint k=0; k(movables[0]); - Ogre::SubEntityArray@ subEntities = ent.getSubEntities(); - for (uint k=0; k "+index); - return; - } - if (!tbuiSelection.exists(key)) - { - tbuiSelection[key] = array(); + game.log("DBG pickSceneNode(): this is already picked: "+key+" --> "+index); + return; + } + if (!tbuiSelection.exists(key)) + { + tbuiSelection[key] = array(); + } + array@ arr = cast>(tbuiSelection[key]); + arr.insertLast(index); + game.log("DBG picked "+key+" --> "+index); +} + +void unPickSceneNode(string key, uint index) +{ + if (!isSceneNodePicked(key, index)) + { + game.log("DBG unPickSceneNode(): this is not picked: "+key+" --> "+index); + return; + } + array@ arr = cast>(tbuiSelection[key]); + for (uint i=0; i "+index); + return; } - array@ arr = cast>(tbuiSelection[key]); - arr.insertLast(index); - game.log("DBG picked "+key+" --> "+index); + } +} + +bool isSceneNodePicked(string key, uint index) +{ +if (!tbuiSelection.exists(key)) { return false; } + array@ arr = cast>(tbuiSelection[key]); + for (uint i=0; i sceneMgrNames = sceneManagers.getKeys(); + for (uint i = 0; i < sceneManagers.getSize(); i++) { - if (!this.isSceneNodePicked(key, index)) - { - game.log("DBG unPickSceneNode(): this is not picked: "+key+" --> "+index); - return; - } - array@ arr = cast>(tbuiSelection[key]); - for (uint i=0; i "+index); - return; - } - } + return sceneManagers[sceneMgrNames[i]]; + } } - - bool isSceneNodePicked(string key, uint index) + return null; +} + +Ogre::SceneNode@ findChildSceneNode(Ogre::SceneNode@ snode, string subject) +{ + Ogre::ChildNodeArray@ children = snode.getChildren(); + for (uint i = 0; i < children.length(); i++) { - if (!tbuiSelection.exists(key)) { return false; } - array@ arr = cast>(tbuiSelection[key]); - for (uint i=0; i(children[i]); + if (@child != null && child.getName() == subject) { - if (arr[i]==index) { return true; } + return child; } - return false; } + return null; +} + +//#endregion Searching helpers + +//#region Inspector tree nodes +void drawTreeNodeOgreSceneNodeRecursive(Ogre::SceneNode@ snode, uint indexUnderParent=0) +{ + // Start with all nodes folded (root node can have hundreds...) + ImGui::SetNextItemOpen(false, ImGuiCond_Once); - // #endregion Pick helpers - - //#region Searching helpers + Ogre::ChildNodeArray@ children = snode.getChildren(); + Ogre::MovableObjectArray@ movables = snode.getAttachedObjects(); - Ogre::SceneManager@ findSceneManager(Ogre::Root@ root, string subject) + // The `__getUniqueName()` 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. + ImGui::PushID(snode.__getUniqueName()); + string treeNodeCaption = snode.getName(); + if (treeNodeCaption == "") { - Ogre::SceneManagerInstanceDict@ sceneManagers = root.getSceneManagers(); - - array sceneMgrNames = sceneManagers.getKeys(); - for (uint i = 0; i < sceneManagers.getSize(); i++) - { - if (sceneMgrNames[i] == subject) - { - return sceneManagers[sceneMgrNames[i]]; - } - } - return null; + treeNodeCaption = "["+(indexUnderParent+1)+"]"; } - Ogre::SceneNode@ findChildSceneNode(Ogre::SceneNode@ snode, string subject) + if (ImGui::TreeNode(treeNodeCaption)) { - Ogre::ChildNodeArray@ children = snode.getChildren(); + // Tree node open, draw children recursively + ImGui::TextDisabled("Ogre::Node ["+children.length()+"]"); for (uint i = 0; i < children.length(); i++) { Ogre::SceneNode@ child = cast(children[i]); - if (@child != null && child.getName() == subject) + if (@child != null) { - return child; + drawTreeNodeOgreSceneNodeRecursive(child, i); } } - return null; + + // Draw attached movable objects + ImGui::TextDisabled("Ogre::MovableObject [" + movables.length() + "]"); + for (uint i = 0; i < movables.length(); i++) + { + drawTreeNodeOgreMovableObject(movables[i]); + } + + ImGui::TreePop(); } - - //#endregion Searching helpers - - //#region Inspector tree nodes - void drawTreeNodeOgreSceneNodeRecursive(Ogre::SceneNode@ snode, uint indexUnderParent=0) + else { - // Start with all nodes folded (root node can have hundreds...) - ImGui::SetNextItemOpen(false, ImGuiCond_Once); - Ogre::ChildNodeArray@ children = snode.getChildren(); - Ogre::MovableObjectArray@ movables = snode.getAttachedObjects(); - // The `__getUniqueName()` 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. - ImGui::PushID(snode.__getUniqueName()); - string treeNodeCaption = snode.getName(); - if (treeNodeCaption == "") + // Tree node closed, draw info (context-sensitive) + ImGui::SameLine(); + if (children.length() == 0 && movables.length() == 1) + { + drawInlineSceneNodeControlsToInspector(snode, indexUnderParent); + } + else { - treeNodeCaption = "["+(indexUnderParent+1)+"]"; + ImGui::Text("("+children.length()+" children, "+movables.length()+" movables)"); } - if (ImGui::TreeNode(treeNodeCaption)) + + } + ImGui::PopID(); //snode.__getUniqueName() +} + +void drawTreeNodeOgreMovableObject(Ogre::MovableObject@ movable) +{ + if (ImGui::TreeNode(movable.__getUniqueName())) + { + bool visible = movable.isVisible(); + if (ImGui::Checkbox("Visible", visible)) + { + movable.setVisible(visible); + } + + bool castShadows = movable.getCastShadows(); + if (ImGui::Checkbox("Cast shadows", castShadows)) + { + movable.setCastShadows(castShadows); + } + + if (movable.getMovableType() == "Entity") { - // Tree node open, draw children recursively - ImGui::TextDisabled("Ogre::Node ["+children.length()+"]"); - for (uint i = 0; i < children.length(); i++) + Ogre::Entity@ entity = cast(movable); + Ogre::AnimationStateSet@ stateSet = entity.getAllAnimationStates(); + if (@stateSet != null) { - Ogre::SceneNode@ child = cast(children[i]); - if (@child != null) + Ogre::AnimationStateDict@ stateDict = stateSet.getAnimationStates(); + ImGui::TextDisabled("Ogre::AnimationState [" + stateDict.getSize() + "]"); + array stateNames = stateDict.getKeys(); + for (uint i = 0; i < stateDict.getSize(); i++) { - drawTreeNodeOgreSceneNodeRecursive(child, i); + drawTreeNodeOgreAnimationState(stateDict[stateNames[i]]); } } - // Draw attached movable objects - ImGui::TextDisabled("Ogre::MovableObject [" + movables.length() + "]"); - for (uint i = 0; i < movables.length(); i++) + // +TerrnBatcher - subEntities + Ogre::SubEntityArray@ subEntities = entity.getSubEntities(); + ImGui::TextDisabled("Ogre::SubEntity [" + subEntities.length() + "]"); + for (uint i = 0; i < subEntities.length(); i++) { - drawTreeNodeOgreMovableObject(movables[i]); + drawTreeNodeOgreSubEntity(subEntities, i); } - - ImGui::TreePop(); - } - else - { - - - // Tree node closed, draw info (context-sensitive) - ImGui::SameLine(); - if (children.length() == 0 && movables.length() == 1) - { - this. drawInlineSceneNodeControlsToInspector(snode, indexUnderParent); - } - else - { - ImGui::Text("("+children.length()+" children, "+movables.length()+" movables)"); - } - - + // END +TerrnBatcher } - ImGui::PopID(); //snode.__getUniqueName() + + ImGui::TreePop(); + } +} + +void drawTreeNodeOgreAnimationState(Ogre::AnimationState@ anim) +{ + ImGui::BulletText('"' + anim.getAnimationName() + '"'); + ImGui::SameLine(); + ImGui::Text("(" + formatFloat(anim.getLength(), "", 0, 2) + " sec)"); + if (anim.getEnabled()) + { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, vector2(0.f, 0.f)); + ImGui::ProgressBar(anim.getTimePosition() / anim.getLength()); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding + } +} +//#endregion Inspector tree nodes + +// #region TerrnBatcher controls for inspector + +void drawInlineSceneNodeControlsToInspector(Ogre::SceneNode@ snode, uint indexUnderParent) +{ + //the most usual case - a node with a single entity. Draw name and controls inline + // ------------------------------------------------ + Ogre::MovableObjectArray@ movables = snode.getAttachedObjects(); + ImGui::TextDisabled("-->"); + ImGui::SameLine(); + ImGui::Text(movables[0].getName()); + ImGui::SameLine(); + + // Inspector: go to + if (ImGui::SmallButton("go to")) + { + game.setPersonPosition(snode.getPosition()); + } + + // +TerrnBatcher - visibility + ImGui::SameLine(); + bool visible = movables[0].isVisible(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, vector2(2,0)); + if (ImGui::Checkbox("Visible", visible)) + { + movables[0].setVisible(visible); } + ImGui::PopStyleVar(); //ImGuiStyleVar_FramePadding - void drawTreeNodeOgreMovableObject(Ogre::MovableObject@ movable) + // +terrnBatcher - selection + ImGui::SameLine(); + if (isSceneNodePicked(snode.getParentSceneNode().getName(), indexUnderParent)) { - if (ImGui::TreeNode(movable.__getUniqueName())) + if(ImGui::SmallButton("-UnPick")) { - bool visible = movable.isVisible(); - if (ImGui::Checkbox("Visible", visible)) - { - movable.setVisible(visible); - } - - bool castShadows = movable.getCastShadows(); - if (ImGui::Checkbox("Cast shadows", castShadows)) - { - movable.setCastShadows(castShadows); - } - - if (movable.getMovableType() == "Entity") - { - Ogre::Entity@ entity = cast(movable); - Ogre::AnimationStateSet@ stateSet = entity.getAllAnimationStates(); - if (@stateSet != null) - { - Ogre::AnimationStateDict@ stateDict = stateSet.getAnimationStates(); - ImGui::TextDisabled("Ogre::AnimationState [" + stateDict.getSize() + "]"); - array stateNames = stateDict.getKeys(); - for (uint i = 0; i < stateDict.getSize(); i++) - { - this.drawTreeNodeOgreAnimationState(stateDict[stateNames[i]]); - } - } - - // +TerrnBatcher - subEntities - Ogre::SubEntityArray@ subEntities = entity.getSubEntities(); - ImGui::TextDisabled("Ogre::SubEntity [" + subEntities.length() + "]"); - for (uint i = 0; i < subEntities.length(); i++) - { - this.drawTreeNodeOgreSubEntity(subEntities, i); - } - // END +TerrnBatcher - } - - ImGui::TreePop(); + unPickSceneNode(snode.getParentSceneNode().getName(), indexUnderParent); } } - - void drawTreeNodeOgreAnimationState(Ogre::AnimationState@ anim) + else { - ImGui::BulletText('"' + anim.getAnimationName() + '"'); - ImGui::SameLine(); - ImGui::Text("(" + formatFloat(anim.getLength(), "", 0, 2) + " sec)"); - if (anim.getEnabled()) + if (ImGui::SmallButton("+Pick!")) { - ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, vector2(0.f, 0.f)); - ImGui::ProgressBar(anim.getTimePosition() / anim.getLength()); - ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding + + pickSceneNode(snode.getParentSceneNode().getName(), indexUnderParent); } } - //#endregion Inspector tree nodes - - // #region TerrnBatcher controls for inspector - void drawInlineSceneNodeControlsToInspector(Ogre::SceneNode@ snode, uint indexUnderParent) + // +TerrnBatcher - mesh serialization + if (tbuiShowDumpButton) { - //the most usual case - a node with a single entity. Draw name and controls inline - // ------------------------------------------------ - Ogre::MovableObjectArray@ movables = snode.getAttachedObjects(); - ImGui::TextDisabled("-->"); ImGui::SameLine(); - ImGui::Text(movables[0].getName()); - ImGui::SameLine(); - - // Inspector: go to - if (ImGui::SmallButton("go to")) - { - game.setPersonPosition(snode.getPosition()); - } - - // +TerrnBatcher - visibility - ImGui::SameLine(); - bool visible = movables[0].isVisible(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, vector2(2,0)); - if (ImGui::Checkbox("Visible", visible)) - { - movables[0].setVisible(visible); - } - ImGui::PopStyleVar(); //ImGuiStyleVar_FramePadding - - // +terrnBatcher - selection - ImGui::SameLine(); - if (this.isSceneNodePicked(snode.getParentSceneNode().getName(), indexUnderParent)) - { - if(ImGui::SmallButton("-UnPick")) - { - this.unPickSceneNode(snode.getParentSceneNode().getName(), indexUnderParent); - } - } - else - { - if (ImGui::SmallButton("+Pick!")) - { - - this.pickSceneNode(snode.getParentSceneNode().getName(), indexUnderParent); - } - } - - // +TerrnBatcher - mesh serialization - if (tbuiShowDumpButton) + if (ImGui::SmallButton("Dump")) { - ImGui::SameLine(); - if (ImGui::SmallButton("Dump")) + if (dumpMesh(movables[0])) { - if (this.dumpMesh(movables[0])) - { - game.log("TerrnBatcher: Mesh dumped successfully"); - } + game.log("TerrnBatcher: Mesh dumped successfully"); } } } - - bool dumpMesh(Ogre::MovableObject@ movable) +} + +bool dumpMesh(Ogre::MovableObject@ movable) +{ + // Save to 'logs' directory - Make sure filenames dont clash + // ---------------------------------------------------------- + string fileName = "TerrnBatcher(NID" + thisScript + ")_Dump" + (tbuiNumMeshesDumped++) + "_"; + Ogre::MeshPtr mesh; + if (movable.getMovableType() == "Entity") { - // Save to 'logs' directory - Make sure filenames dont clash - // ---------------------------------------------------------- - string fileName = "TerrnBatcher(NID" + thisScript + ")_Dump" + (tbuiNumMeshesDumped++) + "_"; - Ogre::MeshPtr mesh; - if (movable.getMovableType() == "Entity") - { - Ogre::Entity@ ent = cast(movable); - mesh = ent.getMesh(); - fileName += mesh.getName(); - } - else if (movable.getMovableType() == "ManualObject") - { - Ogre::ManualObject@ mo = cast(movable); - mesh = mo.convertToMesh(fileName, "Logs"); - fileName += "batch.mesh"; - } - else - { - game.log("ERROR dumpMesh(): unrecognized MovableObject type '" + movable.getMovableType() + "'"); - return false; - } - - return game.serializeMeshResource(fileName, "Logs", mesh); + Ogre::Entity@ ent = cast(movable); + mesh = ent.getMesh(); + fileName += mesh.getName(); } - - // #endregion TerrnBatcher controls for inspector - - // #region Material helpers - - Ogre::TexturePtr getTrivialCaseMaterialTexture(Ogre::MaterialPtr mat) + else if (movable.getMovableType() == "ManualObject") { - if (mat.isNull()) return Ogre::TexturePtr(); // null - Ogre::TechniqueArray@ techs = mat.getTechniques(); - if (techs.length() != 1) return Ogre::TexturePtr(); // null - Ogre::PassArray@ passes = techs[0].getPasses(); - if (passes.length() != 1) return Ogre::TexturePtr(); // null - Ogre::TextureUnitStateArray@ tuss = passes[0].getTextureUnitStates(); - if (tuss.length() != 1) return Ogre::TexturePtr(); // null - return tuss[0]._getTexturePtr(); + Ogre::ManualObject@ mo = cast(movable); + mesh = mo.convertToMesh(fileName, "Logs"); + fileName += "batch.mesh"; } - - bool isMaterialTrivialCase(Ogre::MaterialPtr mat) + else { - return !this.getTrivialCaseMaterialTexture(mat).isNull(); + game.log("ERROR dumpMesh(): unrecognized MovableObject type '" + movable.getMovableType() + "'"); + return false; } - // #endregion Material helpers + return game.serializeMeshResource(fileName, "Logs", mesh); +} + +// #endregion TerrnBatcher controls for inspector + +// #region Material helpers + +Ogre::TexturePtr getTrivialCaseMaterialTexture(Ogre::MaterialPtr mat) +{ + if (mat.isNull()) return Ogre::TexturePtr(); // null + Ogre::TechniqueArray@ techs = mat.getTechniques(); + if (techs.length() != 1) return Ogre::TexturePtr(); // null + Ogre::PassArray@ passes = techs[0].getPasses(); + if (passes.length() != 1) return Ogre::TexturePtr(); // null + Ogre::TextureUnitStateArray@ tuss = passes[0].getTextureUnitStates(); + if (tuss.length() != 1) return Ogre::TexturePtr(); // null + return tuss[0]._getTexturePtr(); +} + +bool isMaterialTrivialCase(Ogre::MaterialPtr mat) +{ + return !getTrivialCaseMaterialTexture(mat).isNull(); +} + +// #endregion Material helpers + +void drawTreeNodeOgreSubEntity(Ogre::SubEntityArray@ subEntities, uint idx) +{ + if (subEntities.length() <= idx) return; // sanity check - void drawTreeNodeOgreSubEntity(Ogre::SubEntityArray@ subEntities, uint idx) + Ogre::SubEntity@ subEntity = subEntities[idx]; + Ogre::MaterialPtr mat = subEntity.getMaterial(); + if (!mat.isNull()) { - if (subEntities.length() <= idx) return; // sanity check - - Ogre::SubEntity@ subEntity = subEntities[idx]; - Ogre::MaterialPtr mat = subEntity.getMaterial(); - if (!mat.isNull()) + Ogre::TechniqueArray@ techs = mat.getTechniques(); + ImGui::Text("|> Material: \"" + mat.getName() + "\" ("+techs.length()+" techniques)"); + for (uint i = 0; i < techs.length(); i++) { - Ogre::TechniqueArray@ techs = mat.getTechniques(); - ImGui::Text("|> Material: \"" + mat.getName() + "\" ("+techs.length()+" techniques)"); - for (uint i = 0; i < techs.length(); i++) + Ogre::PassArray@ passes = techs[i].getPasses(); + ImGui::Text("|> > Technique: \"" + techs[i].getName()+"\" ("+passes.length()+" passes)"); + for (uint j=0; j < passes.length(); j++) { - Ogre::PassArray@ passes = techs[i].getPasses(); - ImGui::Text("|> > Technique: \"" + techs[i].getName()+"\" ("+passes.length()+" passes)"); - for (uint j=0; j < passes.length(); j++) + Ogre::TextureUnitStateArray@ tuss = passes[j].getTextureUnitStates(); + ImGui::Text("|> > > Pass: \"" + passes[j].getName() + "\" (" + tuss.length() + " texture units)"); + for (uint k=0; k > > Pass: \"" + passes[j].getName() + "\" (" + tuss.length() + " texture units)"); - for (uint k=0; k > > > Texture unit: \"" + tuss[k].getName() + "\""); + Ogre::TexturePtr tex = tuss[k]._getTexturePtr(); + if (!tex.isNull()) { - ImGui::Text("|> > > > Texture unit: \"" + tuss[k].getName() + "\""); - Ogre::TexturePtr tex = tuss[k]._getTexturePtr(); - if (!tex.isNull()) - { - ImGui::SameLine(); - ImGui::Text("Texture=\"" + tex.getName()+"\""); - } + ImGui::SameLine(); + ImGui::Text("Texture=\"" + tex.getName()+"\""); } } } } - else + } + else + { + ImGui::Text("no material"); + } +} + +// #region The actual magic - merging meshes +void addSingleSubMeshToBatch(Ogre::ManualObject@ mo, Ogre::SubMesh@ subMesh, vector3 pos, quaternion rot, vector3 scale) +{ + // Proof of concept: make it a world-space mesh. Assume tri-list type mesh with 1 UV (=texcoords) layer + // ------------------------------------------------------------------------ + + + uint indexBase = mo.getCurrentVertexCount(); + + array vertPos = subMesh.__getVertexPositions(); + array vertUVs = subMesh.__getVertexTexcoords(0); + for (uint iVert = 0; iVert < vertPos.length(); iVert++) + { + mo.position((rot * vertPos[iVert]) * scale + pos); + mo.textureCoord(vertUVs[iVert]); + } + + if (subMesh.__getIndexType() == Ogre::IndexType::IT_16BIT) + { + array indexBuf = subMesh.__getIndexBuffer16bit(); + for (uint iIndex = 0; iIndex < indexBuf.length(); iIndex++) { - ImGui::Text("no material"); + mo.index(indexBase + indexBuf[iIndex]); } } - - // #region The actual magic - merging meshes - void addSingleSubMeshToBatch(Ogre::ManualObject@ mo, Ogre::SubMesh@ subMesh, vector3 pos, quaternion rot, vector3 scale) + else { - // Proof of concept: make it a world-space mesh. Assume tri-list type mesh with 1 UV (=texcoords) layer - // ------------------------------------------------------------------------ - - - uint indexBase = mo.getCurrentVertexCount(); - - array vertPos = subMesh.__getVertexPositions(); - array vertUVs = subMesh.__getVertexTexcoords(0); - for (uint iVert = 0; iVert < vertPos.length(); iVert++) + game.log("ERROR appendMeshInstanceToManualObject(): mesh is not supported - not 16-bit indexed"); + } +} + +void addSceneNodeAttachedMeshesToBatch(Ogre::ManualObject@ mo, Ogre::SceneNode@ pickedNode, string&inout foundMatName) +{ + Ogre::MovableObjectArray@ movables = pickedNode.getAttachedObjects(); + for (uint iMovas = 0; iMovas < movables.length(); iMovas++) + { + //game.log("DBG addSceneNodeAttachedMeshesToBatch(): iMovas="+iMovas+"/"+movables.length()); + if (movables[iMovas].getMovableType() != "Entity") { - mo.position((rot * vertPos[iVert]) * scale + pos); - mo.textureCoord(vertUVs[iVert]); + game.log("DBG batchSelectedMeshes(): skipping movable of type '"+movables[iMovas].getMovableType()+"' - not suported!"); + continue; } + Ogre::Entity @ent = cast(movables[iMovas]); - if (subMesh.__getIndexType() == Ogre::IndexType::IT_16BIT) + Ogre::SubEntityArray@ subEntities = ent.getSubEntities(); + for (uint iSubent=0; iSubent indexBuf = subMesh.__getIndexBuffer16bit(); - for (uint iIndex = 0; iIndex < indexBuf.length(); iIndex++) + //game.log("DBG addSceneNodeAttachedMeshesToBatch(): iSubent="+iSubent+"/"+subEntities.length()); + Ogre::MaterialPtr mat = subEntities[iSubent].getMaterial(); + if (mat.isNull()) { - mo.index(indexBase + indexBuf[iIndex]); + game.log("DBG batchSelectedMeshes(): skipping subEntity - material is NULL"); + continue; } - } - else - { - game.log("ERROR appendMeshInstanceToManualObject(): mesh is not supported - not 16-bit indexed"); - } - } - - void addSceneNodeAttachedMeshesToBatch(Ogre::ManualObject@ mo, Ogre::SceneNode@ pickedNode, string&inout foundMatName) - { - Ogre::MovableObjectArray@ movables = pickedNode.getAttachedObjects(); - for (uint iMovas = 0; iMovas < movables.length(); iMovas++) - { - //game.log("DBG addSceneNodeAttachedMeshesToBatch(): iMovas="+iMovas+"/"+movables.length()); - if (movables[iMovas].getMovableType() != "Entity") + if (foundMatName == "") { - game.log("DBG batchSelectedMeshes(): skipping movable of type '"+movables[iMovas].getMovableType()+"' - not suported!"); - continue; + foundMatName = mat.getName(); + mo.begin(foundMatName, Ogre::OT_TRIANGLE_LIST, game.getTerrain().getHandle().getTerrainFileResourceGroup()); + game.log ("DBG addSceneNodeAttachedMeshesToBatch(): Manual object initialized with material '"+foundMatName+"'"); } - Ogre::Entity @ent = cast(movables[iMovas]); - - Ogre::SubEntityArray@ subEntities = ent.getSubEntities(); - for (uint iSubent=0; iSubent tobjNodes = tbuiSelection.getKeys(); + if (tobjNodes.length() == 0) + { + game.log("ERROR batchSelectedMeshes(): nothing selected. Use 'pick' buttons in tree below."); + return; + } + + game.log("DBG batchSelectedMeshes(): num selected nodes='"+tobjNodes.length()+"'"); + for (uint iNode=0; iNode@ nodeIndices = cast>(tbuiSelection[tobjNodes[iNode]]); - array tobjNodes = tbuiSelection.getKeys(); - if (tobjNodes.length() == 0) - { - game.log("ERROR batchSelectedMeshes(): nothing selected. Use 'pick' buttons in tree below."); - return; - } + //game.log("DBG batchSelectedMeshes(): processing node "+uint(iNode+1)+"/"+tobjNodes.length()+" ("+tobjNodes[iNode]+") - "+nodeIndices.length()+" nodes picked"); - game.log("DBG batchSelectedMeshes(): num selected nodes='"+tobjNodes.length()+"'"); - for (uint iNode=0; iNode= children.length()) { - game.log("DBG batchSelectedMeshes(): '"+tobjNodes[iNode]+"' - not found in scene graph!"); + game.log("ERROR: '"+tobjNode.getName()+"' has only "+children.length()+"children, requested index"+nodeIndices[iChild]); continue; - } + } - // Loop through list of scene nodes selected under this grouping node - array@ nodeIndices = cast>(tbuiSelection[tobjNodes[iNode]]); + // Process the entities attached to this scene node + Ogre::SceneNode@ childNode = cast(children[nodeIndices[iChild]]); - //game.log("DBG batchSelectedMeshes(): processing node "+uint(iNode+1)+"/"+tobjNodes.length()+" ("+tobjNodes[iNode]+") - "+nodeIndices.length()+" nodes picked"); - - for (uint iChild=0; iChild(children[nodeIndices[iChild]]); - - //game.log("DBG batchSelectedMeshes(): iChild="+iChild+"/"+nodeIndices.length()+", uniqueName()="+childNode.__getUniqueName()); - this.addSceneNodeAttachedMeshesToBatch(mo, childNode, /*[inout]*/foundMatName); - if (tbuiHideOriginal) - { - childNode.setVisible(false); - } - } // END loop (list of picked nodes) - } // END loop (grouping nodes) + childNode.setVisible(false); + } + } // END loop (list of picked nodes) + } // END loop (grouping nodes) + + if (foundMatName != "") + { + mo.end(); - if (foundMatName != "") - { - mo.end(); - - Ogre::SceneNode@ outputsNode = this.findChildSceneNode(terrnNode, tbuiOutputsNodeName); - Ogre::SceneNode@ snode = outputsNode.createChildSceneNode(moName+"-node"); - snode.attachObject(cast(mo)); - snode.setVisible(tbuiShowNew); - // unlike the ManualObject example, we don't position the node - verts are already in world position. - } - else - { - game.log("DBG batchSelectedMeshes(): material not found, mesh not generated"); - } + Ogre::SceneNode@ outputsNode = findChildSceneNode(terrnNode, tbuiOutputsNodeName); + Ogre::SceneNode@ snode = outputsNode.createChildSceneNode(moName+"-node"); + snode.attachObject(cast(mo)); + snode.setVisible(tbuiShowNew); + // unlike the ManualObject example, we don't position the node - verts are already in world position. } - - // #endregion The actual magic - merging meshes - - // #region Generic helpers - string composeUniqueId(string name) + else { - // to avoid clash with leftover scene nodes created before, we include the NID in the name - using automatic global var `thisScript`. - return "TerrnBatcher(NID:"+thisScript+"): "+name; + game.log("DBG batchSelectedMeshes(): material not found, mesh not generated"); } - // #endregion Generic helpers } +// #endregion The actual magic - merging meshes + +// #region Generic helpers +string composeUniqueId(string name) +{ + // to avoid clash with leftover scene nodes created before, we include the NID in the name - using automatic global var `thisScript`. + return "TerrnBatcher(NID:"+thisScript+"): "+name; +} +// #endregion Generic helpers + + diff --git a/resources/scripts/example_terrn_odefBrowser.as b/resources/scripts/example_terrn_odefBrowser.as new file mode 100644 index 0000000000..7189932a96 --- /dev/null +++ b/resources/scripts/example_terrn_odefBrowser.as @@ -0,0 +1,691 @@ +// TERRAIN OBJECT (*.odef) BROWSER prototype +// Lists .odef files from 'resources/meshes.zip' and sorts/displays/spawns/deletes them. +// =================================================== + +// Window [X] button handler +#include "imgui_utils.as" +imgui_utils::CloseWindowPrompt closeBtnHandler; + +const float FLT_MIN = 1.17549435e-38F; + + +//#region C O N F I G +int cfgGizmoNumSeg=10; // num segments for drawing circles on gizmos +color cfgGrabGizmoColBg = color(0,0,0,1); +color cfgGrabGizmoColFg = color(1,1,1,1); +float cfgGizmoScale = 1.f; +color cfgGizmoUiHoverColor = color(1,0.7,0.3,1); +color cfgGizmoSceneHoverColor = color(1,1,0,1); +int cfgGizmoHoverDistMax = 10; // screen-space distance in pixels +float cfgAxisGizmoPad = 0.7f; // world - meters +float cfgAxisGizmoLen = 1.2f; // world - meters +color cfgAxisGizmoColBg = color(0,0,0,1); +array cfgAxisGizmoColFg = { color(1,0,0,1), color(0,1,0,1), color(0,0,1,1) }; +float cfgHeightBiasRail = 0.4f; +float cfgHeightBiasMisc = 0.f; +color cfgGizmoGroundMarkerColBg = color(0,0,0,1); +color cfgGizmoGroundMarkerColFg = color(0,1,1,1); +color cfgObjSelectionHighlightCol = color(1,0,1,1); + +void drawConfigPanel() +{ + ImGui::SliderFloat("gizmo scale", cfgGizmoScale, 0.1f, 10.f); + ImGui::ColorEdit3("grab gizmo background", cfgGrabGizmoColBg); + ImGui::ColorEdit3("grab gizmo foreground", cfgGrabGizmoColFg); + ImGui::ColorEdit3("gizmo ui hover", cfgGizmoUiHoverColor); + ImGui::ColorEdit3("gizmo scene hover", cfgGizmoSceneHoverColor); + ImGui::SliderFloat("axis gizmo pad", cfgAxisGizmoPad, 0.1f, 5.f); + ImGui::SliderFloat("axis gizmo len", cfgAxisGizmoLen, 0.1f, 5.f); + ImGui::Separator(); + ImGui::InputFloat("height bias Rail", cfgHeightBiasRail); + ImGui::InputFloat("height bias misc.", cfgHeightBiasMisc); +} +//#endregion + + +//#region .ODEFs L I S T I N G +array@ odefs = null; // fileinfo +string odefsError = ""; +array odefnamesAirport; +array odefnamesAirportTaxiway; +array odefnamesAirportRunway; +array odefnamesRail; +array odefnamesRailPlatform; +array odefnamesRoad; +array odefnamesRoadSign; +array odefnamesOther; + +void refreshODEFs() +{ + //FIXME: should reset all `odefnames*` arrays here + @odefs = game.findResourceFileInfo("MeshesRG", "*.odef"); + if (@odefs == null) + { + odefsError="Could not load ODEFs, check RoR.log"; + } + else + { + sortOdefs(); + } +} + +void sortOdefs() +{ + for (uint iOdef=0; iOdef < odefs.length(); iOdef++) + { + string filename = string(odefs[iOdef]["basename"]); + string odefname = filename.substr(0, filename.length() - 5); //cut off ".odef" suffix + + // order matters! (i.e. 'sign-roadnarrows' must hit 'sign-', not 'road') + if (odefname.findFirst("taxiway") >=0 || odefname.findFirst("taxient") >=0) + { + odefnamesAirportTaxiway.insertLast(odefname); + } + else if (odefname.findFirst("heading") >=0 || odefname.findFirst("runway")>=0) + { + odefnamesAirportRunway.insertLast(odefname); + } + else if (odefname.findFirst("airport") >=0 || odefname.findFirst("localizer") >= 0 ) + { + odefnamesAirport.insertLast(odefname); + } + else if (odefname.findFirst("railplatform")>=0) + { + odefnamesRailPlatform.insertLast(odefname); + } + else if (odefname.findFirst("rail")>=0) + { + odefnamesRail.insertLast(odefname); + } + else if (odefname.findFirst("sign-")>=0) + { + odefnamesRoadSign.insertLast(odefname); + } + else if (odefname.findFirst("road")>=0) + { + odefnamesRoad.insertLast(odefname); + } + else + { + odefnamesOther.insertLast(odefname); + } + } +} + +void drawOdefTree() +{ + if (ImGui::TreeNode("Airport ("+odefnamesAirport.length()+")")) + { + drawOdefNames(odefnamesAirport, cfgHeightBiasMisc); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Airport Taxiway ("+odefnamesAirportTaxiway.length()+")")) + { + drawOdefNames(odefnamesAirportTaxiway, cfgHeightBiasMisc); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Airport Runway ("+odefnamesAirportRunway.length()+")")) + { + drawOdefNames(odefnamesAirportRunway, cfgHeightBiasMisc); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rail ("+odefnamesRail.length()+")")) + { + drawOdefNames(odefnamesRail, cfgHeightBiasRail); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rail Platform ("+odefnamesRailPlatform.length()+")")) + { + drawOdefNames(odefnamesRailPlatform, cfgHeightBiasMisc); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Road ("+odefnamesRoad.length()+")")) + { + drawOdefNames(odefnamesRoad, cfgHeightBiasMisc); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Road Sign ("+odefnamesRoadSign.length()+")")) + { + drawOdefNames(odefnamesRoadSign, cfgHeightBiasMisc); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Other ("+odefnamesOther.length()+")")) + { + drawOdefNames(odefnamesOther, cfgHeightBiasMisc); + ImGui::TreePop(); + } +} + +void drawOdefNames(array@ arr, float hBias) +{ + // helper for `drawOdefTree()` - draws odef names and spawn button + // -------------------- + for (uint i = 0; iObjInstanceInfo +uint spawnedObjSequentialNum=0; +string manualSpawnPanelOdefName = ""; + +string composeUniqueInstanceName(string odefname) +{ + // To avoid conflict with leftover objects created before, we include the "script unit ID" (aka NID) to the instance name. + // Format: s#c#n# (S=sequential number, C=instance count, N=NID) + // ------------------------------- + return odefname +"~s"+(spawnedObjSequentialNum++)+ "c" + objInstances.getKeys().length() + "n"+thisScript; +} + +void spawnObjectAtRorbot(string odefname, float hBias) +{ + vector3 objectRot = vector3(0,-game.getPersonRotation().valueDegrees(),0); + vector3 objectPos = game.getPersonPosition() + vector3(0,hBias,0); + spawnObjectManually(odefname, objectPos, objectRot); +} + +void spawnObjectManually(string odefname, vector3 objectPos, vector3 objectRot) +{ + string instanceName = composeUniqueInstanceName(odefname); + game.spawnObject(odefname, instanceName, objectPos, objectRot, "", false); + ObjInstanceInfo@ instInfo = ObjInstanceInfo (instanceName, odefname, objectPos, objectRot); + @objInstances[instanceName] = @instInfo; +} + +void drawSpawnedObjectsPanel() +{ + ImGui::PushID("spawnedObjs"); + array@ spawnedObjectInstanceIds = objInstances.getKeys(); + + ImGui::TextDisabled("spawned objects ("+spawnedObjectInstanceIds.length()+"): "); + for (uint iInst = 0; iInst < spawnedObjectInstanceIds.length(); iInst++) + { + ImGui::PushID(iInst); + ObjInstanceInfo@ instInfo = cast(objInstances[spawnedObjectInstanceIds[iInst]]); + + //selection checkbox + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, vector2(2,0)); + ImGui::Checkbox("Sel", instInfo.inSelection); + ImGui::PopStyleVar(); + ImGui::SameLine(); + + // instance name + if (instInfo.uiHovered) ImGui::PushStyleColor(ImGuiCol_Text, cfgGizmoUiHoverColor); + if (instInfo.sceneHovered && instInfo.sceneHoveredAxis == -1) ImGui::PushStyleColor(ImGuiCol_Text, cfgGizmoGroundMarkerColFg); + if (instInfo.sceneHovered && instInfo.sceneHoveredAxis == 0) ImGui::PushStyleColor(ImGuiCol_Text, cfgAxisGizmoColFg[0]); + if (instInfo.sceneHovered && instInfo.sceneHoveredAxis == 1) ImGui::PushStyleColor(ImGuiCol_Text, cfgAxisGizmoColFg[1]); + if (instInfo.sceneHovered && instInfo.sceneHoveredAxis == 2) ImGui::PushStyleColor(ImGuiCol_Text, cfgAxisGizmoColFg[2]); + if (instInfo.inSelection) ImGui::PushStyleColor(ImGuiCol_Text, cfgObjSelectionHighlightCol); + ImGui::BulletText(instInfo.name); + if (instInfo.uiHovered) ImGui::PopStyleColor(1); //cfgGizmoUiHoverColor + if (instInfo.sceneHovered) ImGui::PopStyleColor(1); // cfgGizmoSceneHoverColor || cfgAxisGizmoColF + if (instInfo.inSelection) ImGui::PopStyleColor(1); // cfgObjSelectionHighlightCol + + bool bulletHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + if (ImGui::SmallButton("Destroy!")) + { + game.destroyObject(instInfo.name); + objInstances.delete(instInfo.name); + } + bool dBtnHovered = ImGui::IsItemHovered(); + + instInfo.uiHovered = bulletHovered || dBtnHovered; + + ImGui::PopID(); //iInst + } + ImGui::PopID(); // spawnedObjs +} +//#endregion + + + +//#region G I Z M O S +int gizmoClosestDist = INT_MAX; +string gizmoClosestObj = ""; +int gizmoClosestObjAxis = -1; // -1=none, 0=x, 1=y, 2=z +string gizmoFocusedObj = ""; // Mouse is down - grab is active +int gizmoFocusedObjAxis = -1; +vector2 gizmoPrevMouseScreenPos(0,0); + + + +void drawSingleObjectGizmos(string instName, ObjInstanceInfo@ instInfo) +{ + ImDrawList @drawlist = imgui_utils::ImGetDummyFullscreenWindow("ODEF browser gizmos"); + bool grabHovered = (instInfo.inSelection || instInfo.uiHovered || (instInfo.sceneHovered && instInfo.sceneHoveredAxis == -1)); + + color hColor = color(0,0,0,1); +if (instInfo.uiHovered) { hColor = cfgGizmoUiHoverColor ; } +else if (instInfo.inSelection) {hColor = cfgObjSelectionHighlightCol; } +else { hColor = cfgGizmoSceneHoverColor; } + + + // draw object origin visualization - fake knob, not actually hoverable (empty param `instName` disables hover) + if (drawGizmoMouseKnob(drawlist, "", instInfo.pos, -1, cfgGrabGizmoColFg, cfgGrabGizmoColBg, /*hovered:*/false, hColor)) + { + // object is visible on screen ~ draw axes gizmos + drawAxisGizmo(drawlist, instName, instInfo, 0); + drawAxisGizmo(drawlist, instName, instInfo, 1); + drawAxisGizmo(drawlist, instName, instInfo, 2); + + // draw elevation marker = actual grab knob + vector3 groundPoint(instInfo.pos.x, game.getGroundHeight(instInfo.pos), instInfo.pos.z); + drawGizmoMouseKnob(drawlist, instName, groundPoint, -1, cfgGizmoGroundMarkerColFg, cfgGizmoGroundMarkerColBg, grabHovered, hColor); + } +} + + + +bool drawGizmoMouseKnob(ImDrawList @drawlist, string instName, vector3 wPos, int axis, color colFg, color colBg, bool hovered, color colHover) // Helper for multiple gizmo funcs +{ + vector2 originScreenPos; + bool onScreen = game.getScreenPosFromWorldPos(wPos, /*out:*/originScreenPos); + if (onScreen) + { + // background filled circle with a circle within + drawlist.AddCircleFilled(originScreenPos, 8*cfgGizmoScale, colFg, cfgGizmoNumSeg); + drawlist.AddCircle(originScreenPos, 5*cfgGizmoScale, colBg, cfgGizmoNumSeg, 2*cfgGizmoScale); + + if (instName != "") // empty param `instName` disables hover + { + + // hover highlight + if (hovered) + { + drawlist.AddCircle(originScreenPos, 10*cfgGizmoScale, colHover, cfgGizmoNumSeg, 3*cfgGizmoScale); + } + + // seeking hovered element + + int dist = getMouseShortestDistance(game.getMouseScreenPosition(), originScreenPos); + if (dist < gizmoClosestDist) + { + gizmoClosestDist = dist; + gizmoClosestObj = instName; + gizmoClosestObjAxis = axis; + } + } + } + return onScreen; +} + +vector3 getAxisGizmoPoint(vector3 base, int axis, float offset) // Helper for `drawAxisGizmo()` +{ + return base + vector3( (axis==0?offset:0.f), (axis==1?offset:0.f), (axis==2?offset:0.f) ); +} + +void drawAxisGizmo(ImDrawList @drawlist, string instName, ObjInstanceInfo@ instInfo, int axis) // Helper for `drawSingleObjectGizmos()` +{ + vector3 axStartWorldPos = getAxisGizmoPoint(instInfo.pos, axis, cfgAxisGizmoPad); + vector3 axEndWorldPos = getAxisGizmoPoint(axStartWorldPos, axis, cfgAxisGizmoLen); + vector2 axStartScreenPos, axEndScreenPos; + if (game.getScreenPosFromWorldPos(axStartWorldPos, /*[out]*/axStartScreenPos) + && game.getScreenPosFromWorldPos(axEndWorldPos, /*[out]*/axEndScreenPos)) + { + drawlist.AddLine(axStartScreenPos, axEndScreenPos, cfgAxisGizmoColBg, 8*cfgGizmoScale); + drawlist.AddLine(axStartScreenPos, axEndScreenPos, cfgAxisGizmoColFg[axis], 4*cfgGizmoScale); + + bool axKnobHovered = (instInfo.uiHovered || (instInfo.sceneHovered && (instInfo.sceneHoveredAxis == axis || instInfo.sceneHoveredAxis == -1))); + vector3 axKnobPos = axStartWorldPos + ((axEndWorldPos - axStartWorldPos)/2); + color axHovColor = (axKnobHovered && instInfo.uiHovered) ? cfgGizmoUiHoverColor : cfgGizmoSceneHoverColor; + drawGizmoMouseKnob(drawlist, instName, axKnobPos, axis, cfgAxisGizmoColBg, cfgAxisGizmoColFg[axis], axKnobHovered, axHovColor); + } +} + +void drawAllObjectsGizmos() +{ + // reset vars for mouse hover seeking + if (gizmoFocusedObj == "") + { + gizmoClosestDist = INT_MAX; + gizmoClosestObj = ""; + } + + // Looop over all gizmos + array@ spawnedObjectInstanceIds = objInstances.getKeys(); + for (uint iInst = 0; iInst < spawnedObjectInstanceIds.length(); iInst++) + { + string instName = spawnedObjectInstanceIds[iInst]; + ObjInstanceInfo@ instInfo = null; + if (objInstances.get(instName, @instInfo)) + { + + drawSingleObjectGizmos(instName, instInfo); + instInfo.sceneHovered=false; + } + } +} + +void updateInputEvents() +{ + + // evaluate mouse hover + // ------ + if (gizmoFocusedObj == "") + { + if (gizmoClosestDist <= (cfgGizmoHoverDistMax * cfgGizmoScale)) + { + ObjInstanceInfo@ instInfo = null; + if (objInstances.get(gizmoClosestObj, @instInfo)) + { + instInfo.sceneHovered=true; + instInfo.sceneHoveredAxis = gizmoClosestObjAxis; + if (ImGui::IsMouseDown(0)) + { + gizmoFocusedObj = gizmoClosestObj; + gizmoFocusedObjAxis = gizmoClosestObjAxis; + } + } + } + } + else + { + ObjInstanceInfo@ instInfo = null; + if (ImGui::IsMouseDown(0) && objInstances.get(gizmoFocusedObj, @instInfo)) + { + if ( gizmoFocusedObjAxis == -1) + { + vector3 tMouse; + if (game.getMousePositionOnTerrain(/*[out]*/ tMouse) ) + { + + instInfo.pos = vector3(tMouse.x, instInfo.pos.y, tMouse.z); + game.moveObjectVisuals(gizmoFocusedObj, instInfo.pos); + } + } + else if (gizmoFocusedObjAxis>=0 && gizmoFocusedObjAxis <= 2) + { + vector3 axStartWorldPos = getAxisGizmoPoint(instInfo.pos, gizmoFocusedObjAxis, cfgAxisGizmoPad); + vector3 axEndWorldPos = getAxisGizmoPoint(axStartWorldPos, gizmoFocusedObjAxis, cfgAxisGizmoLen); + vector2 axStartScreenPos, axEndScreenPos; + if (game.getScreenPosFromWorldPos(axStartWorldPos, /*[out]*/axStartScreenPos) + && game.getScreenPosFromWorldPos(axEndWorldPos, /*[out]*/axEndScreenPos)) + { + vector2 axScreenVec = axEndScreenPos - axStartScreenPos; + vector2 mScreenVec = game.getMouseScreenPosition() - gizmoPrevMouseScreenPos; + float axRatioXtoY = fabs(axScreenVec.x) / fabs(axScreenVec.y); + // watch out for dividing by zero + float axDistX = (axScreenVec.x == 0.f) ? 0.f : (mScreenVec.x / axScreenVec.x) * axRatioXtoY; + float axDistY = (axScreenVec.y == 0.f) ? 0.f : (mScreenVec.y / axScreenVec.y) * (1.f - axRatioXtoY); + float axDist = (axDistX + axDistY); + vector3 translation; + setAxisVal(translation, gizmoFocusedObjAxis, axDist*cfgAxisGizmoLen); + instInfo.pos += translation; + game.moveObjectVisuals(gizmoFocusedObj, instInfo.pos); + } + } + } + else + { + if (objInstances.get(gizmoFocusedObj, @instInfo)) + { + // respawn the object to update collisions + game.destroyObject(instInfo.name); + game.spawnObject(instInfo.odefName, instInfo.name, instInfo.pos, instInfo.rot, "", false); + } + gizmoFocusedObj = ""; + } + } + + + gizmoPrevMouseScreenPos = game.getMouseScreenPosition(); + +} +//#endregion + + +//#region U I T O O L S + +float manualSpawnPanelHBias = 0.f; +vector3 manualSpawnPanelObjPos = vector3(0,0,0); +vector3 manualSpawnPanelObjRot = vector3(0,0,0); + +void drawManualSpawnPanel() +{ + ImGui::PushID("manualSpawn"); + + ImGui::TextDisabled("Specify parameters by hand"); + + ImGui::InputText("ODEF", manualSpawnPanelOdefName); + if (manualSpawnPanelOdefName != "") + { + ImGui::SameLine(); + if (ImGui::SmallButton("clear")) + { + manualSpawnPanelOdefName = ""; + } + } + ImGui::Text("Position (remember Y is up):"); ImGui::SameLine(); + if (ImGui::SmallButton("<-- Fill from character")) + { + manualSpawnPanelObjPos = game.getPersonPosition() + vector3(0,manualSpawnPanelHBias,0); + } + ImGui::Dummy(vector2(25, 0)); ImGui::SameLine(); + ImGui::SetNextItemWidth(75.f); ImGui::InputFloat("X", manualSpawnPanelObjPos.x); ImGui::SameLine(); + ImGui::SetNextItemWidth(75.f); ImGui::InputFloat("Y", manualSpawnPanelObjPos.y); ImGui::SameLine(); + ImGui::SetNextItemWidth(75.f); ImGui::InputFloat("Z", manualSpawnPanelObjPos.z); + + + ImGui::Text("Rotation: (degrees):"); ImGui::SameLine(); + if (ImGui::SmallButton("<-- Fill from character")) + { + manualSpawnPanelObjRot = vector3(0,-game.getPersonRotation().valueDegrees(),0); + } + ImGui::Dummy(vector2(25, 0)); ImGui::SameLine(); + ImGui::SetNextItemWidth(75.f); ImGui::InputFloat("X", manualSpawnPanelObjRot.x); ImGui::SameLine(); + ImGui::SetNextItemWidth(75.f); ImGui::InputFloat("Y", manualSpawnPanelObjRot.y); ImGui::SameLine(); + ImGui::SetNextItemWidth(75.f); ImGui::InputFloat("Z", manualSpawnPanelObjRot.z); + + if ( manualSpawnPanelOdefName != "" && ImGui::Button("Spawn manually!")) + { + spawnObjectManually(manualSpawnPanelOdefName, manualSpawnPanelObjPos, manualSpawnPanelObjRot); + } + + ImGui::PopID(); // "manualSpawn" +} + +void drawSelectionUtilsPanel() +{ + // Tools that affect current selection: alignment, spacing etc... + // -------------------------------------------------------------- + + // gather statistics + int statNumObjs = 0; + vector3 statExtentMin (FLT_MAX, FLT_MAX, FLT_MAX); + + + + vector3 statExtentMax (FLT_MIN, FLT_MIN, FLT_MIN); + + array@ objInstancesKeys = objInstances.getKeys(); + for (uint iKey = 0; iKey < objInstancesKeys.length(); iKey++) + { + string iInst = objInstancesKeys[iKey]; + ObjInstanceInfo@ instInfo = cast(objInstances[iInst]); + if (instInfo.inSelection) + { + // statNumObjects++; + + statExtentMin.x = fmin(instInfo.pos.x, statExtentMin.x); + statExtentMin.y = fmin(instInfo.pos.y, statExtentMin.y); + statExtentMin.z = fmin(instInfo.pos.z, statExtentMin.z); + + statExtentMin.x = fmax(instInfo.pos.x, statExtentMax.x); + statExtentMin.y = fmax(instInfo.pos.y, statExtentMax.y); + statExtentMin.x = fmax(instInfo.pos.z, statExtentMax.z); + } + } +} +//#endregion + + +//#region G A M E C A L L B A C K S + +void frameStep(float dt) +{ + drawAllObjectsGizmos(); + updateInputEvents(); + if ( (@odefs == null && odefsError == "")) + { + refreshODEFs(); + } + + if (ImGui::Begin("ODEF browser", closeBtnHandler.windowOpen, 0)) + { + closeBtnHandler.draw(); + + drawMainPanel(); + + ImGui::End(); + } +} + +void drawMainPanel() +{ + + + ImGui::TextDisabled("T H E T O O L"); + if (ImGui::CollapsingHeader("Status (diag)")) + { + ImGui::Text("Focused obj: " + gizmoFocusedObj); + ImGui::Text("Focused obj axis: " + gizmoFocusedObjAxis); + ImGui::Text("IsMouseDown(0 = LMB): "+ImGui::IsMouseDown(0)); + ImGui::Text("IsMouseDown(1 = RMB): "+ImGui::IsMouseDown(1)); + ImGui::Text("Is left ALT key down: " + inputs.isKeyDown(KC_LALT)); + ImGui::Text("Is right ALT key down: " + inputs.isKeyDown(KC_RALT)); + ImGui::Text("ODEFs loading error: " + odefsError); + } + if (ImGui::CollapsingHeader("Config")) + { + drawConfigPanel(); + } + + ImGui::Separator(); + ImGui::TextDisabled("O B J D E F S"); + + if (ImGui::CollapsingHeader("Full ODEF listing")) + { + if (ImGui::Button("Refresh")) + { + ImGui::Button("refresh ODEFs"); + } + for (uint iOdef=0; iOdef < odefs.length(); iOdef++) + { + ImGui::BulletText(string(odefs[iOdef]["basename"])); + } + } + else + { + ImGui::SameLine(); + ImGui::TextDisabled(" Total loaded: "+odefs.length()); + } + + if (ImGui::CollapsingHeader("ODEFs by category")) + { + drawOdefTree(); + } + + ImGui::Separator(); + ImGui::TextDisabled("O B J E C T S"); + + + if (ImGui::CollapsingHeader("Manual spawn")) + { + drawManualSpawnPanel(); + } + drawSpawnedObjectsPanel(); + + +} +//#endregion + + +//#region H E L P E R S + + +int clamp(int minval, int val, int maxval) +{ + return max(minval, min(val, maxval)); +} + +int min(int a, int b) +{ + return (a < b) ? a : b; +} + +int max(int a, int b) +{ + return (a > b) ? a : b; +} + +float fmax(float a, float b) +{ + return (a > b) ? a : b; +} + +float fmin(float a, float b) +{ + return (a < b) ? a : b; +} + +int abs(int a) +{ + return (a < 0) ? -a : a; +} + +float fabs(float f) +{ + return (f<0.f)?-f:f; +} + + + +int getMouseShortestDistance(vector2 mouse, vector2 target) +{ + int dx = abs(int(mouse.x) - int(target.x)); + int dy = abs(int(mouse.y) - int(target.y)); + return max(dx, dy); +} + +void setAxisVal(vector3&inout vec, int axis, float val) { + switch(axis) { + case 0: vec.x=val; break; + case 1: vec.y=val; break; + case 2: vec.z=val; break; + default: break; + } +} + +//#endregion diff --git a/resources/scripts/example_terrn2_raceConverter.as b/resources/scripts/example_terrn_raceConverter.as similarity index 100% rename from resources/scripts/example_terrn2_raceConverter.as rename to resources/scripts/example_terrn_raceConverter.as diff --git a/resources/scripts/example_terrn_railBuilder.as b/resources/scripts/example_terrn_railBuilder.as new file mode 100644 index 0000000000..0525a6cb96 --- /dev/null +++ b/resources/scripts/example_terrn_railBuilder.as @@ -0,0 +1,168 @@ +// RAIL BUILDER EXPERIMENT +// This script contains a datasheet of ODEF railroad pieces with info how to snap them. +// +// =================================================== + +// Window [X] button handler +#include "imgui_utils.as" +imgui_utils::CloseWindowPrompt closeBtnHandler; + +// #region Datasheet of available pieces, Key = odef name, Value = ObjPieceInfo +class ObjPieceSpec +{ + array snapPoints; + float heightBias; +ObjPieceSpec( float _hBias, array _snapPoints) { heightBias=_hBias; snapPoints=_snapPoints;} +}; +dictionary objPieces = { +{'rail1t1mstrt', ObjPieceSpec(0.4f, array = {vector3(1,1,1), vector3(-1,-1,-1)}) }, +{'rail1t5mstrt', ObjPieceSpec(0.4f, array = {vector3(1,1,1), vector3(-1,-1,-1)}) } +}; + +// #endregion + +// #region Spawned object instances +class ObjInstanceInfo +{ + string name; // Unique ID for this instance + string pieceName; + vector3 pos; + vector3 rot; +ObjInstanceInfo(string _name, vector3 _pos, vector3 _rot) {name=_name; pos=_pos; rot=_rot;} +}; +dictionary objInstances; // instanceName ->ObjInstanceInfo +uint spawnedObjSequentialNum = 0; +// #endregion + + +string nextPiece = 'rail1t1mstrt'; +string selectedPieceInst = ''; // instance name of selected piece; '' means 'use rorbot's position' +int selectedPieceSnapPoint = -1; // snap point index; -1 means none selected, use rorbot's position + +void addPieceBySelection() +{ + // spawn a piece selected on UI; determine placement and record info for drawing and editing + // -------------------------------------------------------- + ObjPieceSpec@ nextPieceSpec = cast(objPieces[nextPiece]); +if (@nextPieceSpec == null) { game.log("ERROR addPieceBySelection(): nextPieceSpec null!"); return; } + + vector3 instPos, instRot; + if (selectedPieceInst == '') + { + instPos = game.getPersonPosition() ; + instRot = vector3(0, -game.getPersonRotation().valueDegrees(), 0); + } + else + { + ObjInstanceInfo@ selPieceInfo = cast(objInstances[selectedPieceInst]); + if (@selPieceInfo == null) { game.log("ERROR addPieceBySelection(): selPieceInfo null!"); return; } + instPos = selPieceInfo.pos; + instRot = selPieceInfo.rot; + } + instPos.y += nextPieceSpec.heightBias; + string instName = nextPiece+"_S"+(spawnedObjSequentialNum++)+"N"+thisScript; + game.spawnObject(nextPiece, instName, instPos, instRot, "", false); + ObjInstanceInfo@ instInfo = ObjInstanceInfo (instName, instPos, instRot); + instInfo.pieceName = nextPiece; + game.log("DBG addPieceBySelection(): setting instance info for key '"+instName+"' (is null: "+(instInfo is null)+")"); + @objInstances[instName] = @instInfo; + // paranoid dbg test + + ObjInstanceInfo@ instInfo2 =cast(@objInstances[instName]); + if (@instInfo2 == null) + { + game.log("ERR addPieceBySelection(): inst info null test failed"); + } + // paranoid n.2 + ObjInstanceInfo@ instInfo3 = null; + bool instInfo3valid=objInstances.get(instName, @instInfo3); + game.log("DBG addPieceBySelection(): instInfo3valid="+instInfo3valid+" (isnull:"+(instInfo3 is null)+")"); + // END paranoid + selectedPieceInst = instName; +} + +void drawInstanceHandles(ObjInstanceInfo@ instInfo) +{ + // draw on-screen handles for origin-point and snap-points of a single rail piece + // --------------------------------------- + ImDrawList@ drawlist = imgui_utils::ImGetDummyFullscreenWindow("rail builder handles"); + + vector2 originScreenPos; + if (game.getScreenPosFromWorldPos(instInfo.pos, /*out:*/originScreenPos)) + { + drawlist.AddCircleFilled(originScreenPos, 4, color(1,0,0,1)); + } + + // Then look up piece spec and draw snap-points + ObjPieceSpec@ pieceSpec = cast(objPieces[instInfo.pieceName]); + if (@pieceSpec == null) + { + ImGui::Text("DBG piece spec null!"); + } + else + { + ImGui::Text("DBG num snappoints:"+pieceSpec.snapPoints.length()); + for (uint i=0; i@ keys = objInstances.getKeys(); + for (uint iKey = 0; iKey < keys.length(); iKey++) + { + ImGui::PushID(iKey); + + ObjInstanceInfo@ instInfo = cast(objInstances[keys[iKey]]); + ImGui::Bullet(); ImGui::SameLine(); ImGui::TextDisabled(""+iKey+'/'+keys.length()+": "); ImGui::SameLine(); + if (@instInfo == null) + { + ImGui::Text("ERR instance info null "); + } + else + { + ImGui::Text('"'+instInfo.name+'" ~ position X="'+instInfo.pos.x+" Y(up)="+instInfo.pos.y+" Z="+instInfo.pos.z); + drawInstanceHandles(instInfo); + } + + ImGui::PopID(); //iKey + } +} + +void frameStep(float dt) +{ + //drawInstancesAll(); + if (ImGui::Begin("~ ~ ~ R A I L B U I L D E R ~ ~ ~", closeBtnHandler.windowOpen, 0)) + { + // Draw the "Terminate this script?" prompt on the top (if not disabled by config). + closeBtnHandler.draw(); + + drawInstancesAll(); + ImGui::Text("Next piece: '" + nextPiece + "'"); + ImGui::Text("Selected piece: '" + selectedPieceInst + "' (Snap# "+selectedPieceSnapPoint+")"); + ImGui::SameLine(); + if (ImGui::SmallButton("Reset##selection")) + { + selectedPieceInst = ''; + selectedPieceSnapPoint = -1; + } + if (ImGui::Button('Add piece!')) + { + addPieceBySelection(); + } + + ImGui::End(); + } +}