9. The Auto-Arrange Function
In this chapter, we will write the auto-arrange function using the bounding box information for each node in the model tree.
- Working with node bounding boxes
- Arranging nodes by size
Perhaps you would like to give the capability to arrange each leaf node of your model tree in an orderly manner on the printing plane. You could perhaps just have the user interact with your handle operator to drag things around and position them, but a smarter way would be to use the model information to automate the placement of nodes. Let's add a member function to our transformOperator class called arrangeOnPlane, that takes a single argument – the size of the plane boundary.
It is important to remember that the model tree nodes are affected by their parents above them, so to avoid any odd behavior from the parents, you should reset their transforms back to the identity matrix. Starting with the absolute root node of the model, we will traverse the tree recursively, storing the nodes without children (leaves) in an array, and resetting the parent objects back to their identity matrix. Note that we are not overwriting the nodes default matrix, since we are providing false for the set default argument in our setNodeMatrix function. With all the parent nodes at identity, the leaf nodes can be arranged without any unexpected net matrix calculations. You can declare and initialize a few variables within your arrangeOnPlane function, but the recursive tree traversal should be abstracted into its own function, called _gatherLeavesAndClearMats.
Many of the HOOPS Communicator functions you will use return promises, so it is good to wait for all these promises to resolve before making the next HOOPS Communicator call. To do this, you can store the returned promises in an array and use Promise.all() to return a single promise that resolves when all provided promises have resolved.
With all the nodes matrices reset, you can begin to gather the node bounding box information, which you will use to arrange the nodes. We can query the bounding box information by using the getNodesBounding function. The getNodesBounding function takes an array of node IDs as input and returns a promise with a resulting value of type Communicator.Box. Again, it is good to wait for all these promises to resolve before continuing.
Now that the bounding boxes have been obtained, you can use that information to gather an idea of the size and spacing needed between nodes.
We will start by looping through each nodes X and Y bounding box extent to determine the largest space in the X and Y direction. This will provide us numbers to define the amount of offset we need between elements in the arrangement. Next, we can define the area of the plane that we want the objects to be arranged to. You may set this as you wish, but I will be using 70% of the plane surface. The idea is that we will query each bounding box space, increment our starting X, Y position (we will start in the [-X,-Y] quadrant) by the spacing amount (plus some padding), and update the X and Y positions for the next part. Once we have the X and Y positions, we can set the node matrix. Again, since this is a returned promise, we will store all the returned promises in an array and return one promise using Promise.all() to finally resolve the function.
The last step is to hook up this new code with our UI skeleton. Return back to app.js and find the arrange-button callback. We will call the arrangeOnPlane function, which operates on our main viewer, then call our syncHelper object to sync the nodes in the attached viewer.
Looking back at your application and running the auto-arrange function, you should now see this: