/// <summary> /// Recursively adds <see cref="ColoredLine"/> objects to the <see cref="Lines"/> list for all of a given nodes children /// </summary> /// <param name="node">A node which will recursively have its children added to the Lines list</param> public static void AddChildrenToList(NodeObject node) { for (int i = 0; i < node.Children.Count; i++) { Lines.Add(new ColoredLine(node.Position, ((NodeObject)node.Children[i]).Position, LineDraw.lineColors[node.Depth % LineDraw.lineColors.Length])); AddChildrenToList((NodeObject)node.Children[i]); } }
/// <summary> /// Changes the current node to be the parent of the current node, if it has a parent /// </summary> public void SelectParentNode() { if (CurrentNode.Parent != null) { CurrentNode = (NodeObject)CurrentNode.Parent; LineDraw.SelectNode(CurrentNode); TreeUIController.DisplayNodeInfo(CurrentNode); } }
/// <summary> /// Changes the currently selected node to be a child of the currently selected node /// </summary> /// <param name="childIndex">The child index of the new selected node</param> public void SelectChildNode(int childIndex) { if (CurrentNode.Children.Count > childIndex) { CurrentNode = (NodeObject)CurrentNode.Children[childIndex]; LineDraw.SelectNode(CurrentNode); TreeUIController.DisplayNodeInfo(CurrentNode); } }
/// <summary> /// Sets the position of a <see cref="NodeObject"/> in the world, and all its children, recursively <para/> /// This method is an <see cref="IEnumerator"/> so the tree is given time to be created, instead of the program freezing whilst it creates the tree in one frame /// </summary> /// <param name="node">The starting node to set the position of</param> IEnumerator SetNodePosition(NodeObject node) { node.SetPosition(visualisationType); nodesGenerated++; foreach (Node child in node.Children) { yield return(new WaitForSeconds(.1f)); StartCoroutine(SetNodePosition((NodeObject)child)); } }
/// <summary> /// If the user has started running MCTS, then display information about it to the UI whilst it generates <para/> /// When the MCTS has finished generating, set the position of each <see cref="NodeObject"/> so that they can be rendered on-screen <para/> /// When each nodes position has been set, switch to the tree navigation UI /// </summary> void Update() { //Don't do anything until the user has started running the MCTS if (mcts == null) { return; } //While the MCTS is still running, display progress information about the time remaining and the amounts of nodes created to the user if (!mcts.Finished) { timeLeft -= Time.deltaTime; if (timeLeft <= 0) { mcts.Finish(); } TreeUIController.UpdateProgressBar((1 - (timeLeft / timeToRunFor)) / 2, "Running MCTS " + mcts.UniqueNodes + " nodes " + timeLeft.ToString("0.0") + "s/" + timeToRunFor.ToString("0.0") + "s"); } //Return if the MCTS has not finished being created if (!mcts.Finished) { return; } //If the MCTS has finished being computed, start to create gameobjects for each node in the tree if (!startedVisualisation) { rootNodeObject = (NodeObject)mcts.Root; rootNodeObject.SetPosition(visualisationType); StartCoroutine(SetNodePosition(rootNodeObject)); startedVisualisation = true; } //Display information on the progress bar about how many node objects have been created, until every node in the tree has its own gameobject if (!allNodesGenerated) { if (nodesGenerated < mcts.UniqueNodes) { TreeUIController.UpdateProgressBar(0.5f + ((float)nodesGenerated / mcts.UniqueNodes / 2), "Creating node objects: " + nodesGenerated + "/" + mcts.UniqueNodes); } else if (nodesGenerated == mcts.UniqueNodes) { //If every node has had a gameobject created for it, then switch to the navigation UI and start to render the game tree TreeUIController.SwitchToNavigationUI(); Camera.main.GetComponent <LineDraw>().linesVisible = true; Camera.main.GetComponent <TreeCameraControl>().CurrentNode = rootNodeObject; TreeUIController.DisplayNodeInfo(mcts.Root); allNodesGenerated = true; LineDraw.SelectNode(rootNodeObject); } } }
/// <summary> /// Selects the provided node, replacing the contents of the <see cref="Lines"/> list with only lines relevant to the select nodes related nodes /// </summary> /// <param name="node">The node to select</param> public static void SelectNode(NodeObject node) { //Clear the list Lines = new List <ColoredLine>(); NodeObject currentNode = node; //Back-track up the tree, drawing lines from the root node to the selected node while (currentNode.Parent != null) { Lines.Add(new ColoredLine(((NodeObject)currentNode.Parent).Position, currentNode.Position, LineDraw.lineColors[currentNode.Parent.Depth % LineDraw.lineColors.Length])); currentNode = (NodeObject)currentNode.Parent; } //Recursively add all the children of the selected node to the Lines list AddChildrenToList(node); }
/// <summary> /// Gives this <see cref=" NodeObject"/> a position in world space depending on its position in the game tree /// </summary> public void SetPosition(VisualisationType visualisationType) { NodeObject parentObject = (NodeObject)Parent; //Play around with this value to change the structure of the tree depending on depth float depthMul = 60; if (visualisationType == VisualisationType.Standard3D) { #region Standard 3D node placement //Root node, automatically starts at origin, does not require additional initialisation if (Depth == 0) { return; } if (Depth == 1) //If at depth 1, use the Fibonacci sphere algorithm to evenly distribute all depth 1 nodes in a sphere around the root node { #region Fibbonacci Sphere algorithm int samples = Parent.Children.Capacity + 1; float offset = 2f / samples; float increment = Mathf.PI * (3 - Mathf.Sqrt(5)); float y = ((parentObject.ChildPositionsSet * offset) - 1) + (offset / 2); float r = Mathf.Sqrt(1 - Mathf.Pow(y, 2)); float phi = ((parentObject.ChildPositionsSet + 1) % samples) * increment; float x = Mathf.Cos(phi) * r; float z = Mathf.Sin(phi) * r; #endregion Position = new Vector3(x, y, z) * depthMul; } else //If at any other depth, position the new node a set distance away from its parent node { Position = parentObject.Position; Position += parentObject.LocalPosition.normalized * depthMul * 2; Vector3 rotationPoint = Position; Vector3 normal = parentObject.LocalPosition; Vector3 tangent; Vector3 t1 = Vector3.Cross(normal, Vector3.forward); Vector3 t2 = Vector3.Cross(normal, Vector3.up); if (t1.magnitude > t2.magnitude) { tangent = t1; } else { tangent = t2; } if (Parent.Children.Count != 1) { Position += tangent.normalized * depthMul * 0.25f; Position = Position.RotateAround(rotationPoint, rotationPoint - parentObject.Position, (360 / Parent.Children.Capacity) * parentObject.ChildPositionsSet); } } #endregion } else if (visualisationType == VisualisationType.Disk2D) { #region Disk 2D node placement //Root node, automatically starts at origin, does not require additional initialisation if (Depth == 0) { ArcAngle = 360; return; } if (Depth == 1) //If at depth 1, place the new node on the edge of a circle around the origin { Position = new Vector3(60, 0, 0); Position = Position.RotateAround(Vector3.zero, Vector3.up, parentObject.ChildPositionsSet * (parentObject.ArcAngle / parentObject.Children.Count)); } else //If at any other depth, position the new node a set distance away from its parent node { Position = parentObject.Position; Position += (parentObject.LocalPosition.normalized * depthMul); Position = Position.RotateAround(parentObject.Position, Vector3.up, -(parentObject.ArcAngle / 2) + (parentObject.ChildPositionsSet * (parentObject.ArcAngle / parentObject.Children.Count))); } ArcAngle = parentObject.ArcAngle / parentObject.Children.Count; #endregion } else if (visualisationType == VisualisationType.Cone) { #region Cone node placement //Root node, automatically starts at origin, does not require additional initialisation if (Depth == 0) { Radius = 1000; return; } Position = parentObject.Position; if (parentObject.Children.Count > 1) { Position += new Vector3(parentObject.Radius, 0, 0); Position = Position.RotateAround(parentObject.Position, Vector3.up, parentObject.ChildPositionsSet * (360 / parentObject.Children.Count)); Vector3 a = parentObject.Position + new Vector3(parentObject.Radius, 0, 0); Vector3 b = a.RotateAround(parentObject.Position, Vector3.up, 360 / parentObject.Children.Count); Radius = (a - b).magnitude / 2; } else { Radius = parentObject.Radius; } Position += new Vector3(0, -150, 0); #endregion } else { throw new System.Exception("Unknown visualisation type: " + visualisationType.ToString() + " encountered"); } parentObject.ChildPositionsSet++; }