Friday, 13 March 2009

Constrain Null To ICE Particle's Transform

This script constrains a null's position and rotation to an ICE particle via a SCOP. Tag some points in your point cloud, run the script and each tagged point will now be followed by a Null. Get's very slow however with multiple Nulls on a cloud.


Plot ICE Particles To Curves

This script plots tagged ICE particles to curves. For each point cloud it creates a null at the scene root and places the curves under that null. To use, just tag points and run the script. A dialogue will let you select the frame range and step size.


Thursday, 5 March 2009

Neighbour Points Plus 1.6

Neighbour Points Plus 1.6 expands on the PointNeighbors attribute that's native to ICE by letting you select 'n' levels of neighbouring points. You can select the number of levels you want, filter the results and/or exclude the original points.

Neighbour Points Plus 1.6

Big thanks to Todd Akita for cleaning up the code and resolving an issue with duplicate locations.

Deduplicate Array 1.2

This compound takes an array as input and removes any duplicates. It's broken out from my Point Neighbours Plus compound as it has some utility in its own right. Works fine with integer, scalar, location and vector arrays. Deduped elements don't have to be sequential - the compound removes any duplicate it finds.

Deduplicate Array 1.2

Index to Location

Building on the previous posts, it's easy to see how you can implement functionality in ICE that isn't there natively. One example is the limitation of the ID to Location node - it only works for particle IDs and not geometry indices.

The tree below constructs object context arrays of point locations and their indices. From then on, whatever context you're in you have access to these arrays and can query the location of any other index on the mesh. The only slight twist is that you need to use 'Find in Array' as lists of locations can't be conventionally sorted.

Repeat Loops And Redundant Attribute Declaration

The Repeat loop in the image below iterates 20 times and creates a column of particles - the counter increments the Y height by 1 unit each time. In Python, I'd normally create the array for the resultant positions and the counter itself outside of the loop and, for a while, this is how I constructed loops in ICE:

It turns out that ICE is much cleverer than that. Put plainly, you can 'Get' an attribute before you 'Set' an attribute. I know this doesn't sound too earth shattering but it confused me for a while. In a conventional scripting language you can't usually work with a variable/attribute before you've declared it. In ICE, however, you can. If I remove the initial Set Data on both the Counter and the Array in the tree:

The tree still works even though, ostensibly, for the very first iteration of the loop it looks like you're requesting Counter and Array before you've assigned any value to them. It seems that ICE pre-processes attributes before the tree is evaluated and initialises them to a default value - in this case on the very first iteration of the loop ICE knows that array is an array of 3D vectors and inserts a default vector value of (0,0,0) and Counter is a scalar and inserts 0.

Saves a lot of unnecessary tree clutter.

Wednesday, 4 March 2009

Traversing Contexts with Get Element ID and Sort Array With Key

In the previous post I looked at how you can create arrays of data in object context and then access that data in an element context. The trick to doing that is the Get Element ID node. Using Get Element ID you can a) sort the array in object context when using Sort Array with Key and b) locate the position of your element in the array when you're in element context. There are, however, a few tricks.

As an example, I will construct an array of all point positions in object context and then sort that array by Index. Using Raf's trick we know that Get Closest Points without any Cutoff distance will give us a list of all the point locations on the geometry (unordered).

It would be great if you could simply pump that array of locations into the Get Element Index node but you can't. XSI returns an array with a single value. Grahame Fuller at Softimage pointed out on the Mailing List that Get Element Index returns the Index associated with the element's context. Since, in this example, the element is in object context it's only going to return a single value.

You need to 'force' Get Element ID into per point context by feeding in a per point attribute (e.g. self.PointPositions) and then store that ID on the point as a custom attribute. Once you've done that you can query that custom attribute back in object context (without using Get Element ID and thus bypassing the problem of contexts with Get Element ID). Here's the tree:

Using Sort Array with key, it's then quite straightforward to pump the index array into the sort and have the Point Positions sorted in Index order. (In the example below the 'extra' sort array is there simply to display the sorted indices).

Scope and Breaking Down The Walls Between Data

In a traditional scripting environment using the Softimage SDK it would be a relatively simple task to peform operations on individual vertices and cross-reference those vertices with others on the geometry. You have full and unimpeded access to the vertex arrays and associated data. However deep you burrow into the data there's always a mechanism available to access some completely unrelated data elsewhere. Your data network can be as arbitrarily complex as you like.

In ICE the situation is different - access to data is carefully marshalled. The more you dig down into the different element contexts (point, edge, sample etc), the less you can see of the rest of the geometry data. If you're in per point context, for example, you can't see any information related to non-adjacent points, edges, uvs etc. To get at any data at this level you usually have to use a Geometry Query. This is a huge difference. Effectively, the more you dig down the blinder you become - the only chink of light visible is data at the object level back where you came from. Everything else is invisible.

Let's illustrate this whole thing with an example. You need to store the lengths to adjacent points on a linear curve's points i.e. you need some mechanism to say: 'at this point the length to the point to my left is x and the length to the point on my right is y' and store those x, y values on each point of the curve for later processing (see image below).

There is no native attribute in ICE for finding the neighbouring points of a curve's individual points when you're in Per Point context. It's a great example of how the further you dig down into the granular levels of data in ICE the more 'locked out' you are from other pieces of data. A Geometry Query might give you the two closest points but who's to say these are the closest connected points?

So, how do you break down these walls? The most elegant way I've seen so far is Ahmidou
Lyazidi's compound on the community site. But his mechanism, which uses the fact that linear curve points have an even distribution of U doesn't really help illustrate our point. So I'll explore another route. In this scenario you need to somehow prepare your data in Object context since it's the only other context visible. And yet, on the surface there's simply no mechanism in ICE to allow you to access the whole array of Point Positions in one go as a single array - to get at point positions you usually need to be in Per Point context where you only have access at any one time to the position of the point you're working on. Thanks to a trick by Raffaele Fragapane, however, you can get the whole array of Point Locations in one go in an Object context - by using the Get Closest Points node.

Armed with an unordered list of Point Locations corresponding to each point on the curve you can then extract the PointU of each location which is a normalised value between 0 and 1 corresponding to the position along the curve the point sits. A sort of this array will give you an array of all the PointUs on the curve corresponding to each point in index order.

Because of the hierarchical nature of the scope in ICE, as you burrow down into data you can still see data back at a higher level e.g. when you're in per point context it is still possible to see any data in object context. So, in our example, if we now switch to per point context and get the PointU for each point we still have access to an array at the object level which can tell us the PointU for the points adjacent to us (the array we've just created using Closest Points). By using Get Element ID as we traverse the PointUs at a per point level we can index into the PointU array at object level and can then construct data at a per point level which pulls data from outside the scope of the individual point. The image below illustrates the tree for doing just that...

So, even though ICE marshalls data very strictly and enforces pre-defined routes for collecting data, it's still possible in some cases to break out of the constraints that the different contexts enforce. In this case, by storing the locations of the 'neighbours' in a per point context it's possible to get ICE to 'see' data that would not normally be within an individual point's scope.