.. _source-geo-tutorial:

SourceGeo tutorial
==================

This tutorial will walk you through implementing a minimal op which creates 3D
geometry by subclassing ``DD::Image::SourceGeo``. The Op creates a triangle
which you can manipulate the corners of in the viewer.

It demonstrates how to:

- create an object
- add points to it
- create a primitive which references the points
- assign UVs to the vertices
- calculate geometry hashes
- use the rebuild flags to skip unnecessary work


Basic setup: includes and namespaces
------------------------------------

We'll be using assert statements in our code to catch coding errors, so we need
the definition of the ``assert()`` function::

  #include <cassert>


Next, we'll need to include the relevant parts of the NDK::

  #include <DDImage/SourceGeo.h>
  #include <DDImage/Scene.h>
  #include <DDImage/Triangle.h>

We need ``DDImage/SourceGeo.h`` because that's where our base class is defined.
It includes most of the other headers we'll need for our plugin. The other
include we'll need is ``DDImage/Triangle.h``, which declares the specific type
of primitive we'll be creating.

Most of the NDK classes are in the ``DD::Image`` namespace. To save us having
to prefix every reference to them, we provide a ``using`` statement::

  using namespace DD::Image;

It's good practice to put your own code inside a namespace as well. This helps
prevent conflicts with symbols defined elsewhere. For this example we'll use a
namespace called ``tri``::

  namespace tri {
    
    // All futher code in this tutorial will go inside this block.

  } // namespace tri


Constants
---------

All NUKE ops need to provide a class name. Following general good programming
practice, we declare that as a constant::

  static const char* kTriangleClass = "Triangle";

That's the only constant we need for this example.


Declarations
------------

Now we get to the more important part, where we declare the Op class and it's
members. We'll call the class ``TriangleOp``, since it will be creating a
triangle. There's a class in the NDK which is already called ``Triangle``, so
adding the ``Op`` suffix makes it easier to distinguish between the two.

The declaration looks like this::

  class TriangleOp : public SourceGeo {
  public:
    TriangleOp(Node* node);

    virtual const char* Class() const;
    virtual void knobs(Knob_Callback f);

    static Op* Build(Node* node);

    static const Op::Description description;

  protected:
    virtual void create_geometry(Scene& scene, GeometryList& out);
    virtual void get_geometry_hash();

  private:
    Vector3 _aPos; // Position of corner A of the triangle
    Vector3 _bPos; // Position of corner B of the triangle
    Vector3 _cPos; // Position of corner C of the triangle
  };

The first thing we declare is the constructor; Ops need to have a constructor
which takes a pointer to a Node. The ``Class()`` method is required on every
custom Op class. We'll also be adding some knobs to the node, so we need to
override the ``knobs()`` method.

The static ``Build`` method is what NUKE will use to create instances of our
plugin when (e.g.) you add a Triangle node to the DAG. This method can actually
be called anything you like, so long as it keeps the same signature. It can
also be a standalone function instead of a static method, but having it as a
static method is recommended for the sake of clarity in your code.

The static ``description`` member tells NUKE what the Op is called and how to
create it. This is required on every custom Op class. As well as a class name
string for the Op, it also stores a pointer to the function for creating the
op (the ``Build`` function mentioned above, in our case).

The standard ``SourceGeo`` method for creating geometry is called
``create_geometry``; we'll be overriding that to create our triangle. The
geometry we create will depend on the values of our knobs, so we need to
override the ``get_geometry_hash`` method as well.

Finally, we declare three member variables: ``_aPos``, ``_bPos`` and
``_cPos``. These are the locations of the triangle's corners. We'll be
creating knobs which use these as storage, so that we can manipulate them
inside NUKE.


The easy bits
-------------

The implementations of the constructor, ``Class()`` and ``Build()`` methods are
fairly straightforward so we won't spend much time on them::

  TriangleOp::TriangleOp(Node* node) :
    SourceGeo(node),
    _aPos(0, 0, 0),
    _bPos(1, 0, 0),
    _cPos(0, 1, 0)
  {}


  const char* TriangleOp::Class() const
  {
    return kTriangleClass;
  }


  Op* TriangleOp::Build(Node* node)
  {
    return new TriangleOp(node);
  }


The constructor simply calls the base class constructor, passing through the
node, and initialises the data members. As an aside: the DD::Image::Vector3
class doesn't do any initialisation in its no-arg constructor; assuming that it
zeroes the vector is a common mistake.

The ``Class()`` method just returns the constant we defined earlier. And the
``Build()`` method creates a new TriangleOp instance and returns it.

Note that there's no need to define a destructor for this example. The default
one generated by the compiler is sufficient for this class.


Adding some knobs
-----------------

A node with no output controls is not particularly useful, so we override the
``knobs()`` method to add some. In this case we're adding three XYZ_Knobs
which can be used to adjust the position of each corner of the triangle
independently::

  void TriangleOp::knobs(Knob_Callback f) {
    SourceGeo::knobs(f); // Set up the common SourceGeo knobs.
    XYZ_knob(f, &_aPos[0], "point_a", "a");
    XYZ_knob(f, &_bPos[0], "point_b", "b");
    XYZ_knob(f, &_cPos[0], "point_c", "c");
  }

Note the call to the ``SourceGeo::knobs()`` method. It's important to call this
because you're likely to get crashes at runtime if you don't.


Generating the geometry
-----------------------

The ``create_geometry`` method is the meat of a SourceGeo subclass: it creates
the geometry that gets passed down through the graph. The ``out`` parameter is
where we put the geometry we create; NUKE takes care of the rest.

In our implementation we create a single ``Triangle`` primitive, then provide
the locations of the corner points and finally set the texture coordinates for
each corner::

  void TriangleOp::create_geometry(Scene& scene, GeometryList& out)
  {
    int obj = 0;

    if (rebuild(Mask_Primitives)) {
      out.delete_objects();
      out.add_object(obj);
      out.add_primitive(obj, new Triangle());
    }

    if (rebuild(Mask_Points)) {
      PointList& points = *out.writable_points(obj);
      points.resize(3);

      points[0] = _aPos;
      points[1] = _bPos;
      points[2] = _cPos;
    }

    if (rebuild(Mask_Attributes)) {
      Attribute* uv = out.writable_attribute(obj, Group_Vertices, "uv", VECTOR4_ATTRIB);
      assert(uv != NULL);
      uv->vector4(0).set(0, 0, 0, 1);
      uv->vector4(1).set(1, 0, 0, 1);
      uv->vector4(2).set(0, 1, 0, 1);
    }
  }

In order to speed things up, NUKE tries to avoid recomputing values when it
doesn't have to. As part of the processing sequence for geometry ops it will
check the hashes for each group (see the ``get_geometry_hash()`` discussion
below) and set flags to indicate which parts of the geometry need to be
rebuilt. We test these flags using the ``rebuild()`` function to see whether we
need to rebuild a specific part.

If we need to recreate primitives, the first thing we do is call
``out.delete_objects()`` to ensure that the output list is empty. Then we add a
single object to hold our triangle (an object is a collection of points,
primitives, vertices and attributes). Finally we add a single ``Triangle``
primitive. Note that we haven't specified any positions or attributes yet.

To (re)create points, we need to obtain a PointList object that we can add our
points to. The ``out.writable_points()`` method gives us this. We only ever
have three points, so we resize the list to 3 and set the values to match our
``_aPos``, ``_bPos`` and ``_cPos`` members respectively.

Texture coordinates in NUKE's 3D system are stored in a vertex attribute
(``Group_Vertices``) named "uv". If the rebuild flag for attributes is set,
we'll need to recreate them. The usual pattern for updating any attribute is:
- call ``out.writable_attribute()`` to get an object you can store the
  attribute values in. The returned attribute will already have a slot
  allocated for each item in the group.
- Use the accessor methods of the relevant type (e.g. ``uv->vector4(1)`` above)
  to set values for each item.


The ``get_geometry_hash()`` method
----------------------------------

In order to minimse the amount of recomputation when you change something in
the graph, NUKE keeps separate hashes for various aspects of the geometry and
uses them to set the appropriate rebuild flags (these are the flags we used in the
``create_geometry`` method above).

The ``get_geometry_hash()`` calculates the current value for each of these
hashes::

  void TriangleOp::get_geometry_hash()
  {
    SourceGeo::get_geometry_hash();

    // Add the three point positions to the hash for the Points group.
    _aPos.append(geo_hash[Group_Points]);
    _bPos.append(geo_hash[Group_Points]);
    _cPos.append(geo_hash[Group_Points]);
  }

The ``SourceGeo`` class provides a default implementation of this method which
incorporates hashing of the material input, so we call that first.

The three knobs will affect the point locations on the geometry we create, but
that's all, so we only need to hash them into the points group.

There's nothing else that affects the geometry that we'll produce, so our work
here is done.


The complete code
-----------------

.. literalinclude:: ../examples/Triangle.cpp


