const bool DEFAULT_SHOW_NAMES_FOR_ALL = false; const int ORIGIN_STEP_UPDATE = 10; const int NAMES_SIZE = 11; const StringHash ORIGIN_NODEID_VAR("OriginNodeID"); const Color ORIGIN_COLOR(1.0f,1.0f,1.0f,1.0f); const Color ORIGIN_COLOR_SELECTED(0.0f,1.0f,1.0f,1.0f); const Color ORIGIN_COLOR_DISABLED(1.0f,0.0f,0.0f,1.0f); const Color ORIGIN_COLOR_TEXT(1.0f,1.0f,1.0f,0.3f); const Color ORIGIN_COLOR_SELECTED_TEXT(1.0f,1.0f,1.0f,1.0f); const IntVector2 ORIGIN_ICON_SIZE(14,14); const IntVector2 ORIGIN_ICON_SIZE_SELECTED(18,18); const float ORIGINS_VISIBLITY_RANGE = 32.0f; const IntVector2 ORIGINOFFSETICON(8,8); const IntVector2 ORIGINOFFSETICONSELECTED(10,8); bool showNamesForAll = DEFAULT_SHOW_NAMES_FOR_ALL; bool EditorOriginShow = false; bool rebuildSceneOrigins = true; bool isOriginsHovered = false; int EditorOriginUITimeToUpdate = 0; int EditorOriginUITimeToSceneNodeRead = 0; int prevSelectedID; int selectedNodeInfoState = 0; int originHoveredIndex = -1; UIElement@ EditorOriginUIContainer = null; Text@ selectedNodeName = null; BorderImage@ selectedNodeOrigin = null; Array selectedNodeOriginChilds; Array selectedNodeNameChilds; Array originsNodes; Array originsIcons; Array originsNames; void CreateOriginsContainer() { if (editorScene is null) return; EditorOriginUIContainer = UIElement(); EditorOriginUIContainer.position = IntVector2(0,0); EditorOriginUIContainer.size = IntVector2(graphics.width,graphics.height); EditorOriginUIContainer.priority = -1000; EditorOriginUIContainer.focusMode = FM_NOTFOCUSABLE; EditorOriginUIContainer.bringToBack = true; EditorOriginUIContainer.name ="DebugOriginsContainer"; EditorOriginUIContainer.temporary = true; ui.root.AddChild(EditorOriginUIContainer); } void HandleOriginToggled(StringHash eventType, VariantMap& eventData) { UIElement@ origin = eventData["Element"].GetPtr(); if (origin is null) return; if (EditorPaintSelectionShow) return; if (IsSceneOrigin(origin)) { int nodeID = origin.vars[ORIGIN_NODEID_VAR].GetInt(); if (editorScene !is null) { bool goBackAndSelectNodeParent = input.qualifierDown[QUAL_CTRL]; bool multiSelect = input.qualifierDown[QUAL_SHIFT]; WeakHandle handle = editorScene.GetNode(nodeID); if (handle.Get() !is null) { Node@ selectedNodeByOrigin = handle.Get(); if (selectedNodeByOrigin !is null) { if (goBackAndSelectNodeParent) SelectNode(selectedNodeByOrigin.parent, false); else SelectNode(selectedNodeByOrigin, multiSelect); } } } } } void ShowOrigins(bool isVisible = true) { EditorOriginShow = isVisible; if (EditorOriginUIContainer is null) CreateOriginsContainer(); EditorOriginUIContainer.visible = isVisible; } void UpdateOrigins() { // Early out if Origins are disabled if (!EditorOriginShow) return; CheckKeyboardQualifers(); if (editorScene is null || EditorOriginUITimeToUpdate > time.systemTime) return; EditorOriginUIContainer = ui.root.GetChild("DebugOriginsContainer"); // Since editor not clear UIs then do loading new scenes, this creation called once per Editor's starting event // for other scenes we use the same container if (EditorOriginUIContainer is null) { CreateOriginsContainer(); } if (EditorOriginUIContainer !is null) { // Set visibility for all origins EditorOriginUIContainer.visible = EditorOriginShow; if (viewportMode!=VIEWPORT_SINGLE) EditorOriginUIContainer.visible = false; // Forced read nodes for some reason: if ((originsNodes.length < 1) || rebuildSceneOrigins) { originsNodes = editorScene.GetChildren(true); // If we are hot have free origins icons in arrays, resize x 2 if (originsIcons.length < originsNodes.length) { EditorOriginUIContainer.RemoveAllChildren(); originsIcons.Clear(); originsNames.Clear(); originsIcons.Resize(originsNodes.length * 2); originsNames.Resize(originsNodes.length * 2); if (originsIcons.length > 0) { for (int i=0; i < originsIcons.length; i++) { CreateOrigin(i, false); } } } // If this rebuild pass after new scene loading or add/delete node - reset flag to default if (rebuildSceneOrigins) rebuildSceneOrigins = false; } if (originsNodes.length > 0) { // Get selected node for feeding proper arrray's UIElements with slyte colorig and additional info on ALT Node@ selectedNode = null; if (selectedNodes.length > 0) { selectedNode = selectedNodes[0]; } else if (selectedComponents.length > 0) { selectedNode = selectedComponents[0].node; } // Update existed origins (every 10 ms) if (originsNodes.length > 0 ) { for (int i=0; i < originsNodes.length; i++) { Vector3 eyeDir = originsNodes[i].worldPosition - cameraNode.worldPosition; float distance = (eyeDir).length; eyeDir.Normalize(); Vector3 cameraDir = (cameraNode.worldRotation * Vector3(0.0f, 0.0f, 1.0f)).Normalized(); float angleCameraDirVsDirToNode = eyeDir.DotProduct(cameraDir); // if node in range and in camera view (clip back sibe) if (distance < ORIGINS_VISIBLITY_RANGE && angleCameraDirVsDirToNode > 0.7f) { // turn on origin and move MoveOrigin(i, true); if (isThisNodeOneOfSelected(originsNodes[i])) { ShowSelectedNodeOrigin(originsNodes[i], i); originsNames[i].visible = true; } else { if (showNamesForAll || (isOriginsHovered && originHoveredIndex == i)) originsNames[i].text = NodeInfo(originsNodes[i], selectedNodeInfoState); } } else { // turn-off origin VisibilityOrigin(i, false); } } // Hide non used origins for (int j=originsNodes.length; j < originsIcons.length; j++) { VisibilityOrigin(j, false); } } } } EditorOriginUITimeToUpdate = time.systemTime + ORIGIN_STEP_UPDATE; } bool isThisNodeOneOfSelected(Node@ node) { if (selectedNodes.length < 1) return false; for (int i = 0; i < selectedNodes.length; i++) { if (node is selectedNodes[i]) return true; } return false; } void ShowSelectedNodeOrigin(Node@ node, int index) { if (node !is null) { // just keep node's text and node's origin icon position in actual view Viewport@ vp = activeViewport.viewport; Vector2 sp = activeViewport.camera.WorldToScreenPoint(node.worldPosition); //originsIcons[index].position = IntVector2(10+int(vp.rect.left + sp.x * vp.rect.right), -5 + int(vp.rect.top + sp.y* vp.rect.bottom)); originsIcons[index].position = IntVector2(int(vp.rect.left + sp.x * vp.rect.right) - ORIGINOFFSETICONSELECTED.x, int(vp.rect.top + sp.y* vp.rect.bottom) - ORIGINOFFSETICONSELECTED.y); originsNames[index].color = ORIGIN_COLOR_SELECTED_TEXT; if (originsNodes[index].enabled) originsIcons[index].color = ORIGIN_COLOR_SELECTED; else originsIcons[index].color = ORIGIN_COLOR_DISABLED; originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE_SELECTED.x,ORIGIN_ICON_SIZE_SELECTED.y); // if selected node chaged, reset some vars if (prevSelectedID != node.id) { prevSelectedID = node.id; selectedNodeInfoState = 0; originsIcons[index].vars[ORIGIN_NODEID_VAR] = node.id; } // We always update to keep and feed alt-info with actual info about node components Array components = node.GetComponents(); Array componentsShortInfo; Array componentsDetailInfo; componentsShortInfo.Resize(components.length); componentsDetailInfo.Resize(components.length); // Add std info node name + tags originsNames[index].text = NodeInfo(node, selectedNodeInfoState) + "\n"; } } void CreateOrigin(int index, bool isVisible = false) { if (originsIcons.length < index) return; originsIcons[index] = BorderImage("Icon"); originsIcons[index].temporary = true; originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE.x,ORIGIN_ICON_SIZE.y); originsIcons[index].texture = cache.GetResource("Texture2D", "Textures/Editor/EditorIcons.png"); originsIcons[index].imageRect = IntRect(0,0,14,14); originsIcons[index].priority = -1000; originsIcons[index].color = ORIGIN_COLOR; originsIcons[index].bringToBack = true; originsIcons[index].enabled = true; originsIcons[index].selected = true; originsIcons[index].visible = isVisible; EditorOriginUIContainer.AddChild(originsIcons[index]); originsNames[index] = Text(); originsNames[index].visible = false; originsNames[index].SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), NAMES_SIZE); originsNames[index].color = ORIGIN_COLOR_TEXT; //originsNames[index].textEffect = TE_STROKE; originsNames[index].temporary = true; originsNames[index].bringToBack = true; originsNames[index].priority = -1000; originsNames[index].enabled = false; EditorOriginUIContainer.AddChild(originsNames[index]); } void MoveOrigin(int index, bool isVisible = false) { if (originsIcons.length < index) return; if (originsIcons[index] is null) return; if (originsNodes[index].temporary) { originsIcons[index].visible = false; originsNames[index].visible = false; return; } Viewport@ vp = activeViewport.viewport; Vector2 sp = activeViewport.camera.WorldToScreenPoint(originsNodes[index].worldPosition); originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE.x,ORIGIN_ICON_SIZE.y); if (originsNodes[index].enabled) originsIcons[index].color = ORIGIN_COLOR; else originsIcons[index].color = ORIGIN_COLOR_DISABLED; originsIcons[index].position = IntVector2(int(vp.rect.left + sp.x * vp.rect.right) - ORIGINOFFSETICON.x, int(vp.rect.top + sp.y* vp.rect.bottom) - ORIGINOFFSETICON.y); originsIcons[index].visible = isVisible; originsIcons[index].vars[ORIGIN_NODEID_VAR] = originsNodes[index].id; originsNames[index].position = IntVector2(10+int(vp.rect.left + sp.x * vp.rect.right), -5 + int(vp.rect.top + sp.y* vp.rect.bottom)); if (isOriginsHovered && originHoveredIndex == index) { originsNames[index].visible = true; originsNames[index].color = ORIGIN_COLOR_SELECTED_TEXT; } else { originsNames[index].visible = showNamesForAll ? isVisible : false; originsNames[index].color = ORIGIN_COLOR_TEXT; } } void VisibilityOrigin(int index, bool isVisible = false) { originsIcons[index].visible = isVisible; originsNames[index].visible = isVisible; } bool IsSceneOrigin(UIElement@ element) { if (originsIcons.length < 1) return false; for (int i=0; i < originsIcons.length; i++) { if (element is originsIcons[i]) { originHoveredIndex = i; return true; } } originHoveredIndex = -1; return false; } void CheckKeyboardQualifers() { // if pressed alt we inc state for info bool showAltInfo = input.keyPress[KEY_ALT]; if (showAltInfo) if (selectedNodeInfoState < 3) selectedNodeInfoState += 1; // if pressed ctrl we reset info state bool hideAltInfo = input.qualifierDown[QUAL_CTRL]; if (hideAltInfo) selectedNodeInfoState = 0; bool showNameForOther = false; // In-B.mode Key_Space are busy by quick menu, so we use other key for B.mode if (hotKeyMode == HOTKEYS_MODE_BLENDER) showNameForOther = (input.keyPress[KEY_TAB] && ui.focusElement is null); else showNameForOther = (input.keyPress[KEY_SPACE] && ui.focusElement is null); if (showNameForOther) showNamesForAll =!showNamesForAll; } String NodeInfo(Node& node, int st) { String result = ""; if (node !is editorScene) { if (node.name.empty) result = "Node"; else result = node.name; // Add node's tags if wey are exist if (st > 0 && node.tags.length > 0) { result = result + "\n["; for (int i=0;i