Thursday, June 5, 2014

Done!! & Belated Conclusions

I've been meaning to upload the final presentation (apologies for the delay). Now that things have settled down a bit, here are some notes on the finished plug-in. Here's the GitHub repo for those who are interested in the code:

On the whole, HydroTerrain is mostly feature complete for river generation, and missing some features for terrain generation. Below are some screen shots of the plug-in running just river generation functionality.

From the postmortem, the task division for the plug-in is listed below. 

1. Build Framework
1.1. Implement user interface in MEL [1d]
1.2. Create source images (river slope, terrain slope) [1d]
1.3. Implement command plug­in framework in C++ [1d]
1.3.1. Create code stubs for plug­in initialization, compute, etc.   
2. River network generation
2.1. Create data structures  
2.1.1. Individual river nodes with priority index (Horton­Strahler’s number),  position and elevation [3d]
2.1.2. River node graph [1d]
2.2. Convert source images to usable data [2d]
2.3. Implement L­system expansion with initial candidate nodes for hierarchical drainage network growth. [6d]  
2.4. Implement probabilistic rule application to determine river branch types [4d]
2.5. Add compatibility checks for newly created nodes [3d]
2.6. Integrate voronoi partitioning library [3d]
2.7. Classify river type for each river node and edge (dependant on 3.1) [3d]
3.3. Create River primitives [3d]

3. Terrain system generation
3.1. Take expanded nodes and implement voronoi partitioning [4d]
3.1.1. Connect the points for each voronoi cell to get a patch.  
3.2. Assign height values to voronoi cells [1d]  
3.3.1. Define river junction algorithm
3.3.2. Connect river junctions
3.4. Implement Poisson distribution to cover terrain patch with terrain primitives. [4d]
4. CSG­Tree like datastructure (Cheng) [7d]  
4.1. Implement tree structure. [1­2 d]
4.2. Define different terrain features (hills, valleys, etc.) [1­2 d]
4.3. Implement blending, adding, subtracting, carving. [1­3 d]

CSG (constructive solid geometry) data structure and terrain primitives are currently incomplete, as my partner was not able to implement them in time. Since it is the minimum requirement for carving a basic river from the terrain, we were only able to demo river generation. You can see some attempts at CSG intersections at the end of the video where the terrain is interspersed with cylinders. 

Despite some setbacks, I'm quite happy with how the rest of the project turned out. The RiverNetworkNode updates/regenerates in real time, so every time the river contour (CV curve) is changed, all the nodes are recalculated, enabling the artist to make changes on the fly. You'll notice in the video that vertically lowering a river mouth results in more rivers branches flowing towards it, which adheres to the natural phenomenon as water always flows downwards. 

I was able to learn a lot about writing plug-in's for Maya through this project (not least of which is that it's sometimes a painful and tedious process, hah). I also picked up workarounds for using CGAL with Maya—for example, a node containing the CGAL library will only load properly in Maya when it has been compiled in release, rather than debug, mode. It will compile absolutely fine in either mode, which can throw a lot of people off. Might I note that coming to this discovery was a long and frustrating process. 

All in all, I'm glad I got to work on HydroTerrain. While I don't plan on continuing this particular project, I gained a lot from the experience. From a technical point of view, I learned about the procedural process of generating river networks as well as a bit about image processing and STL structures through the CImg and tree.hpp libraries. In terms of project management and planning, I learned how to divide a large task into discrete modular chunks and stick to a development schedule. In terms of Maya integration, I learned how to take general algorithms and translate them into Maya-specific functions. If anyone wants to actually run the plug-in, I'm happy to get in touch and go over the setup instructions (it's quite involved with CGAL installations and path variables which is why I'm not including it here). Cheers!

Saturday, April 19, 2014

Gradient look-up

Implemented gradient look-up from the river slope map this week, which lets us determine where a newly generated node should be positioned. I also added a horizontal jitter to handle the case where a parent node splits off into two children nodes with the same starting point and gradient vector. The sample river slope map I'm working off of at the moment is a simple radial gradient (lighter values designate higher elevation).

Nodes seem to be coming along nicely. Next steps are to implement geometry compatibility checks to determine when expansion should terminate. After that I'll be working on defining different river types and adding that to the final output.

Monday, April 7, 2014

Trouble with bounding boxes

Currently I'm stuck on querying the bounding box for the CV curve. This will be needed to fit the river slope image map onto the curve in order to retrieve the slope value at a specific position.

In my compute() function, I have my MFnNurbsCurve object, via
MObject curve = inputCurveData.asNurbsCurve();
MFnNurbsCurve curveFn (curve, &returnStatus); // Can also set as MItCurveCV
McheckErr(returnStatus, "ERROR creating curve function set\n");

And I've tried directly calling boundingBox() on it (which should work since MFnNurbsCurve inherits from MFnDagNode), but I get a return status code of 1 (kFailure), and internal status code of 6 (kNotImplemented), which isn't very helpful. I still don't know why it's not working.

An alternate method I tried was to query the bounding box attribute, as follows:
MObject boundingBoxObj = curveFn.attribute(MString("boundingBox"), &returnStatus); 
I get the same error with this method.

I know the bounding box is definitely queryable because the MEL command getAttr curve1.boundingBoxMin returns a valid value. Though I would rather not resort to that if I don't have to.

Per this answer, it seems like the reason why boundingbox() has been failing is because it will only work if the MFnNurbsCurve is attached to a dagNode, rather than curve data coming through the DG. I'm just going to manually calculate the bounding box.

Tuesday, April 1, 2014

Updates 4/1: Expansion Algorithm

Currently I'm still working on implementing the expansion algorithm, which is described as a grammar-like rewriting process. The system uses two non-terminal symbols to denote the candidate node and an instantiated node, and a terminal symbol that represents a node that has been added to the graph and that cannot be expanded.

While stepping through the algorithm, I realized that while the paper indicated how to calculate the elevation (y) of each newly created node, there was nothing for their (x, z) positions. I emailed one of the authors, Dr. Bene, and was told that the main direction is given by the direction to the previous node and is modified by the gradient of the river slope and other factors. I'm still using dummy constants for the river slope values, but it looks like I will either have to find an image processing library that covers gradients, or roll it out myself (which someone recommended in conjunction with using stb_img.c to read in the slope map).

Here is what one step 2.2 in the expansion chart looks like (this is with dummy slope and gradient values. You can tell by how the river pretty much aims straight to the origin)

Bird's eye view down y-axis

Breaking the next few weeks down into chunks, I have the following tasks left:

  • Implement function that reads in an image map describing the slope values in grey scale and scales it to fit the bounding box of the CV curve in order to determine the elevation of every node.
  • Calculate the gradient of the river slope map to determine expansion direction of newly instantiated nodes. 
  • Finish implementing expansion with symmetric Horton-Strahler junction creation 
  • Implement expansion with asymmetric Horton-Strahler junction creation
  • Implement expansion with just river growth
  • Implement compatibility check, which determines if a node is able to be expanded
  • Design and implement expansion loop
  • Calculate distance between edge and all other edges to avoid collisions in the graph
  • Calculate distance between a river node (point) and the contour curve
  • Check if a point lies within a curve (this is to ensure no nodes are created beyond the river contour)
  • Create a user interface in MEL

Saturday, March 15, 2014

Working Node!

Turns out changing inputData.asNurvsCurveTransformed() to inputData.asNurbsCurve() fixed the error I was getting. I now have a very basic node that puts placeholder geometry on the CV points of a curve. Next step is to implement the node expansion algorithm.

Attributes and Such

I'm testing the RiverNetworkNode right now and running into a few issues. First off, I realized I don't actually know which attribute of the curve I need to connect to the node. For polygons in our previous assignments it was the .matrix attribute. I took a look through Maya's connection editor, and it seems like only borderConnections is able to connect to the node's inputCurve attribute.

RiverNetworkNode takes in a CV curve and outputs a set of points to be fed into an instancer

However, when compute runs, I get an error creating the curve function set. Currently trying to debug this; will update with findings later.

The MEL commands I'm currently using are as follows:
// Create a CV curve using the CV curve tool
createNode RiverNetworkNode
connectAttr -f curve1.borderConnections[0] RiverNetworkNode1.inputCurve;
connectAttr pCube1.matrix instancer1.inputHierarchy[0];
connectAttr RiverNetworkNode1.outputPoints instancer1.inputPoints;
EDIT: using connectAttr -f curve1.worldSpace[0] RiverNetworkNode1.inputCurve; got me further in the compute function. Now running into an error retrieving the CV points on the curve. Will investigate further tomorrow.

Friday, March 14, 2014

Pi Day Update

Happy Pi Day!

Last week I emailed our previous lab director Joe for advice on the tool implementation, as I had some questions about the Maya API as well as uncertainties on how best to approach it. I'll summarize some takeaways here:

The authoring tool will most likely be a collection of nodes and commands, where each node represents a modular portion of the overall task. This piece of advice was really helpful, since I originally envisioned the tool to be one monolithic node. He also clarified that nodes add pieces to the underlying Maya engine in the dependency graph, while commands make new features available to the command engine (namely calling nodes and issuing updates right away), so the tool will most likely require a combination of both. Previously I had thought nodes and commands were an either-or case, which it clearly is not.

I also asked about the viability of storing an overall state for a collection of nodes (for my river node network), and was told that while it could be done, the general paradigm in Maya is to propagate information down. Taking that into consideration, I'm currently working on creating a new RiverNetwork Maya Node. It will take in as input a CV curve as the river contour, and output an array of position values for the generated set of river nodes. These can be visualized with a simple sphere connected to an instancer.

One thing I'm still not sure how to handle is how to represent the parent-child relation between the generated output (this information will be needed to visualize the river graph part). To my [limited] knowledge, Maya nodes can only output objects that are MFnData, and it might take some extra work to figure out how to make one of the following data types work:

kInvalid Invalid value.
kNumeric Numeric, use MFnNumericData extract the node data.
kPlugin Plugin Blind Data, use MFnPluginData to extract the node data.
kPluginGeometry Plugin Geometry, use MFnGeometryData to extract the node data.
kString String, use MFnStringData to extract the node data.
kMatrix Matrix, use MFnMatrixData to extract the node data.
kStringArray String Array, use MFnStringArrayData to extract the node data.
kDoubleArray Double Array, use MFnDoubleArrayData to extract the node data.
kIntArray Int Array, use MFnIntArrayData to extract the node data.
kPointArray Point Array, use MFnPointArrayData to extract the node data.
kVectorArray Vector Array, use MFnVectorArrayData to extract the node data.
kComponentList Component List, use MFnComponentListData to extract the node data.
kMesh Mesh, use MFnMeshData to extract the node data.
kLattice Lattice, use MFnLatticeData to extract the node data.
kNurbsCurve Nurbs Curve, use MFnNurbsCurveData to extract the node data.
kNurbsSurface Nurbs Surface, use MFnNurbsSurfaceData to extract the node data.
kSphere Sphere, use MFnSphereData to extract the node data.
kDynArrayAttrs ArrayAttrs, use MFnArrayAttrsData to extract the node data.
kDynSweptGeometry SweptGeometry, use MFnDynSweptGeometryData to extract the node data. This data node is in OpenMayaFX which must be linked to.
kSubdSurface Subdivision Surface, use MFnSubdData to extract the node data.
kNObject nObject data, use MFnNObjectData to extract node data
kLast Last value. It does not represent real data, but can be used to loop on all possible types