a creative coding toolkit for spatial interfaces

Wrangling (Coordinate Transforms)

Greenhouse applications are built around a single, global 3D space. The same space – same X Y and Z axes, same units (mm), etc. – is shared by all windows and, indeed, all Greenhouse applications working together in the space. This unification around a single, literal, physical space is a powerful simplification that we’ve found very useful in building spatial and gestural applications. It cuts down on the number of concepts (and API calls) we need to hold in our heads. But we do end up leaning on math a little harder than some other APIs require; particularly 3D vectors and conversions between different coordinate spaces. Fortunately, once we’re comfortable with this math, we can use it forever.

Our global 3D space has a [0,0,0] origin, of course, generally somewhere in the room; perhaps on the floor.

(Note that in a real application, this origin point might or might not be visible on any screen. We can put objects anywhere we like in absolute space, but we have to be aware of camera position, to ensure that the viewer can actually see them. See the article on Spatial Considerations to learn about screen and feld settings, which govern camera position.)

Each object can take on its own location in this absolute 3D space. That location is the result of how the object has been translated and rotated (and maybe scaled). Let’s say we have an object that’s translated 100mm from the origin in the X direction. Imagine it’s a flat object, like street sign, and we’ll also rotate it around its central (upward-pointing, Y) axis by 45 degrees, just to make things interesting.

Image *sign = new Image ("images/sign.png");
sign -> SetTranslation (Vect (100, 0, 0));
sign -> SetRotation (Vect (0, 1, 0), PI/4);

In the global, absolute coordinate frame, the center of the object is at [100,0,0]. This is obvious.

But the object also has a local coordinate system. In the local frame of the object, [0,0,0] is the center of the sign – and the center of a whole X Y Z coordinate system. If the object has kids, the center of their parent is the center of their world. When they are rotated, translated, and scaled, it’s with respect to the local origin, and along its axes.

So now we’ll give our sign object a kid object (that is – let’s paint a dot on the sign).

  Image *dot = new Image ("images/dot.png");
  sign -> AppendKid (dot);

Kids “inherit” all the parents’ (and grandparents’, etc.) translations & rotations. So by default the dot is at the center of the sign, at [100,0,0].

What if we ask the dot its Loc ()? Loc is reported in absolute space; we’ll get [100,0,0].

INFORM ("dot -> Loc () = " + ToStr (dot -> Loc ()));
//  result: dot -> Loc () = Vect[100.000, 0.000, 0.000]

But in local terms, the dot is still sitting at its origin: at [0,0,0]. So now let’s shift it over to the right by 10mm, with a call to IncTranslation ().

  dot -> IncTranslation (Vect (10, 0, 0));

This is a local translation along the local X axis; and remember that we already rotated that local frame (the sign) by 45 degrees. So the dot will not end up at [110,0,0]. It should be poking out along the Z axis, toward the viewer. Let’s check.

  INFORM ("dot -> Loc () = " + ToStr (dot -> Loc ()));
  //  result: dot -> Loc () = Vect[107.071, 0.000, -7.071]

That looks right.

But we can also do something else; in the general case, we can convert back and forth between absolute coordinate point and local points by wrangling and unwrangling – Greenhouse lingo for transforms.

Say we are interested in a point hovering just in front of the sign, right over the dot. Where is that, in absolute terms?

INFORM ("hovering over the dot = " +
        ToStr (dot -> UnWrangleLoc (Vect (0, 0, 2))));
//  result:  hovering over the dot = Vect[108.485, 0.000, -5.657]

Everything in the local coordinate frame (such as the point [0,0,2]) has already been wrangled – it’s transformed the coordinate frame of an object that’s been translated or scaled. Unwrangling converts the point out of that frame, back into the absolute frame.

Note how we called UnWrangleLoc as a method of the dot. This is essential. The dot has its own distinct location because of its distinct set of translations and rotations.

If we unwrangle [0,0,2] using the sign’s UnWrangleLoc() method, we’ll get a different result, because we are asking about a point within the frame of the sign, not of the dot.

INFORM ("hovering over the center of the sign = " +
        ToStr (sign -> UnWrangleLoc (Vect (0, 0, 2))));
//  result: hovering over the center of the sign = Vect[101.414, 0.000, 1.414]

WrangleLoc() just does the transform in the other direction: from absolute to local coordinates. It, too, must always be called as a method of some object. We always wrangle and unwrangle with respect to some local coordinate system.

  • UnWrangleLoc ()
  • WrangleLoc ()
  • UnWrangleRay ()
  • WrangleRay ()


#include "Greenhouse.h"

class CustomText  :  public Text
{ public:

  CustomText (const Str &s, const int64 &dist, const float64 &angle)  :  Text (s)
    { SlapOnFeld ();
      IncTranslation (Feld () -> Over () * Random (dist/2, dist)
                      + Feld () -> Up () * Random (dist/2, dist)
                      + Feld () -> Norm () * Random (dist/2, dist));
      IncRotation (Feld () -> Up (), Random (angle/2, angle));
      IncRotation (Feld () -> Over (), Random (angle/2, angle));

  void TransformInform ()
    { INFORM ( "UnWrangleLoc   (): " + ToStr ( UnWrangleLoc (Vect (0, 0, 0))));
      INFORM ( "WrangleLoc (): " + ToStr ( WrangleLoc (Vect (0, 0, 0))));
      INFORM ( "UnWrangleRay   (): " + ToStr ( UnWrangleRay (Feld () -> Up ())));
      INFORM ( "WrangleRay (): " + ToStr ( WrangleRay (Feld () -> Up ())));

  void Travail ()
    { TransformInform (); }

void Setup ()
{ CustomText *ht = new CustomText ("Hello, World", 60, PI/2);
  new CustomText ("Feld () -> Loc ()", 0, 0);

  // Print out UnWrangleLoc information for the Text newt
  ht -> TransformInform ();