greenhouse
a creative coding toolkit for spatial interfaces

Scene Graph methods for Node objects

Greenhouse employs a fairly standard scene graph for easy organization and management of objects in your application. You construct your scene graph by appending one Node as the Kid of another Node (which becomes the parent). Every Node can have many children, but every Node can only have a single Parent.

Notes

  • Kids automatically inherit and extend their parent’s scale, rotation, position, and color. So Kids are translated in space, for example, with relation to their Parent. Kid transformations are applied after parent transformations.

  • Parents keep a list of all their Kids, and kids store a pointer to their Parent, which makes it easy to traverse the scene graph up or down.

  • Kid/parent attachment is a one-time operation in Greenhouse. Once you call AppendKid () and attach a Node to a parent, it can’t be re-attached to some other parent.

  • AppendKid ()
  • RemoveKid ()
  • KidCount ()
  • NthKid ()
  • Parent ()
  • Find ()
  • As ()
  • Name ()
  • SetName ()

Example

#include "Greenhouse.h"

const Str holder_text ("I'm the Parent.\nDrag me or my kids.");

//  KidHolder is a Text object, but is also acts as a container for
//  other objects.  It handles click-n-drag user events.
struct KidHolder  :  public Text
{ KidHolder ()  :  Text (holder_text)
  { SetName ("Humphrey Chimpden Earwicker"); }

  void PointingInsideHarden (PointingEvent *e)
  { //  If already currently tracking (heeding) some other mouse or
    //  pointer source, we'd ignore this one.
    if (IsHeedless ())
      { Heed (e);
        SetString ("heeding '" + e -> Provenance () + "'");
        SetAdjColor (Color (1.0, 0.0, 0.0));  //  turn red
        INFORM ("I, " + Name () + ", have " + ToStr(KidCount ()) + " kids");
      }
  }

  void PointingMove (PointingEvent *e)
  { //  If this move event is from the same source we're already heeding,
    //  move along with it.
    //  We only start heeding on harden events.
    //  We only stop heeding on soften events.
    if (IsHeeding (e))
      { //  IntersectionDiff tells us how far between
        //     (a) the place where this pointing event intersects the plane
        //     (b) the last such point from the same source
        //  Optional 3rd argument is the plane; here, the main feld
        //  Loc() is the location of this, the KidHolder object
        Vect delta = IntersectionDiff (e, Loc ());
        IncTranslation (delta);
      }
  }

  void PointingSoften (PointingEvent *e)
  { if (IsHeeding (e))
      { StopHeeding ();
        SetString (holder_text);
        SetAdjColor (Color (1.0, 1.0, 1.0));
        Find ("") -> Heartbeat ();
        // or,
        //Find <Image> ("heart") -> Heartbeat ();
        // or,
        //Find ("heart") -> As <Image> () -> Heartbeat ();
      }
  }

  void PointerWithinKid (PointingEvent *e, Node *kid)
  { if (kid -> As <Text> () -> Name () == "clickable-kid")
      { if (IsHardened (e))
          PointingInsideHarden (e);
        else
          PointingSoften (e);
      }
    else if (kid -> As <Text> () -> Name () == "deletable-kid")
      { if (IsHardened (e))
          RemoveKid (kid);
      }
  }

  void Travail ()
    { INFORM ("Heart's over is " + ToStr (Find ("heart") -> PhysOver ()));
    }
};


void Setup ()
{ KidHolder *kh = new KidHolder ();
  //  put at the same location as the Feld('s center), & match its orientation
  kh -> SlapOnFeld ();
  kh -> IncRotation (Vect (0, 1, 0), PI/4);

  //  Make some generic text objects and add them as kids of KidHolder
  Text *kidA = new Text ("I'm the clickable kid");
  kidA -> SetName ("clickable-kid");
  kidA -> SetFontSize (Feld () -> Height () / 40.0);
  kidA -> IncTranslation (Vect (Feld () -> Width () * -0.25,
                                Feld () -> Height () * -0.25, 0));
  kh -> AppendKid (kidA);

  Text *kidB = new Text ("I'm the deletable kid");
  kidB -> SetName ("deletable-kid");
  kidB -> SetFontSize (Feld () -> Height () / 40.0);
  kidB -> IncTranslation (Vect (Feld () -> Width () * 0.25,
                                Feld () -> Height () * -0.25, 0));
  kh -> AppendKid (kidB);

  Text *kidC = new Text ("I'm just a kid");
  kidC -> SetFontSize (Feld () -> Height () / 40.0);
  kidC -> IncTranslation (Vect (0.0, Feld () -> Height () * -0.25, 0));
  kh -> AppendKid (kidC);

  Image *grandkid = new Image ("images/heart.png");
  grandkid -> SetName ("heart");
  grandkid -> SetSize (20.0);
  kh -> NthKid (1) -> AppendKid (grandkid);
  grandkid -> IncTranslation (Vect (0, Feld () -> Height () * -0.1, 0));

  INFORM ("My parent's name is " + kidB -> Parent () -> As <KidHolder> () -> Name ());
  INFORM ("My over is " + ToStr (kidB -> Over ()));
}