While experimenting with custom user interfaces and pointers for mixed reality design tools, we realized there was a need to build on top of the data provided by the native Fologram tracking components. While the Fologram tracking components all provide event based filters for accessing data (e.g. expiring on tap, release, drag etc), if we were to use this data to build more powerful input mechanisms (such as a pointer that snaps to curves, or can be projected a fixed distance from the hand/screen) we could easily end up having to duplicate parts of our definition in order to maintain these filters.
Above: Snapping a pointer onto a curve
What we want to achieve is an isolated part of our definition dedicated to creating cursor functionality. This facilitates a lot of user-customization (e.g. we might be able to switch between always snapping and snapping when within a fixed distance, or switch between surface snap and curve snap etc) and simplifies the definition by removing copy pasted components and spaghetti connections. We achieve this by working with global variables to keep track of our live-streamed cursor, and then getting these values elsewhere in our definition where and when they are required.
Above: Snapping a pointer onto all curves in the rhino document when within a tolerance and storing the result in a global variable
Now that we have a basic architecture set up for getting and setting our cursor variable, we can easily add functionality to switch between different tools for working with the cursor, without needing to change the part of our definition that does the rendering or tracks the pointer.
Above: Adding a value list and state gate to allow users to switch between different snap modes
200219 Switching Cursor Modes.gh (18.3 KB)
Back to our original challenge: Let’s say we want to build a drawing tool in which the user can draw splines using the cursor position. How do we get our current cursor (not just the output of the Track Pointer component) position when we tap in order to add a control point to our curve? We could easily change the filter on the Track Pointer component to only update with the OnPress event, but this would mean copy pasting our whole definition (undesirable). What we want to do is get the current position of our cursor when we register the OnPress event. We can achieve this using a combination of the Track State and State Gate components.
Above: Getting the cursor position during a press
The Track State component outputs the state of a given device (Press, Drag, Release) and this can be used as an input to the State Gate component to update parts of our definition only when the given state is active. Any components connected to the active output of the state gate will update once when the state is triggered, allowing the Get Global component to pull down the current position of the cursor just once for the given press event. Attaching the output of the global variable to a data recorder shows that the definition architecture is producing the expected data flow and recording the cursor position once with each press. Now to add functionality for drawing and previewing, and to dive a little deeper into how to control dynamic and static global variables.
Above: Adding control points and drawing a curve
200219 Drawing Curves With The Cursor.gh (17.6 KB)
We’ve now added two more ‘functions’ to our definition: A group of components for drawing a preview of a nurbs curve, and a group of components for adding a control point to the curve. The preview is created from the list of existing curve points and the dynamic position of our cursor. The get global component will ‘pull down’ the current value of a global variable whenever any connected downstream components expire. Because the Merge component is expiring as frequently as we are updating our cursor position, we are also pulling down the global variable storing all of our curve points. This means that when we add a point to our curve points global variable, we see our preview of the drawn curve update accordingly.
Because our cursor position may be updating more frequently than the track state component updates, we need to be very careful with our data flow control when adding control points to our curve. Specifically we need to ensure we avoid the behaviour previously mentioned, because we don’t want to accidentally add multiple points to our curve each time we tap. To avoid this, we insert a point parameter component between the Get Global component and the Merge Component. This ensures that the Merge component can expire multiple times (whenever the Cursor input forces it to update) without pulling down a new value for the CurvePoints global. We then update the CurvePoints global to append the cursor position.
Try it out - you should be able to tap around and draw in space. For Mobile users make sure you go to settings and enable cursor interaction before trying this out, as the Track Pointers component will only output data when you are dragging on screen otherwise. Any questions about this example or workflow? Want to build something similar? Join the discussion below.