Table of Contents
Frames is a simple, expressive, language-agnostic, and extensible (2D/3D) scene graph featuring interaction, visualization and animation frameworks and supporting advanced (onscreen/offscreen) rendering techniques, such as view frustum culling.
Frames is meant to be coupled with third party real and non-real time renderers. Our current release supports all major Processing desktop renderers: 2D and 3D PGraphicsOpenGL (a.k.a. P2D and P3D, respectively), PGraphicsJava2D (a.k.a. JAVA2D) and PGraphicsFX2D (a.k.a. FX2D).
If looking for the API docs, check them here.
Readers unfamiliar with geometry transformations may first check the great Processing 2D transformations tutorial by J David Eisenberg and this presentation that discusses some related formal foundations.
Instantiate your on-screen scene at the setup():
Scene scene;
void setup() {
scene = new Scene(this);
}
The Scene frontBuffer() corresponds to the PApplet main PGraphics
instance.
Off-screen scenes should be instantiated upon a PGraphics object:
Scene scene;
void setup() {
scene = new Scene(this, createGraphics(500, 500, P3D));
}
In this case, the Scene frontBuffer() corresponds to the PGraphics
instantiated with createGraphics() (which is of course different than the PApplet main PGraphics
instance).
A frame is a coordinate system that may be translated, rotated and scaled. Frame instances define each of the nodes comprising a scene graph. To customize their role (e.g., a drawing procedure or a culling condition) symply override the Frame visit() method. To illustrate their use, suppose the following scene graph is being implemented:
World
^
|\
1 eye
^
|\
2 3
To setup the scene hierarchy of attached frames, i.e., frames belonging to the scene, use code such as the following:
Scene scene;
Frame f1, f2, f3;
void setup() {
scene = new Scene(this);
// To attach a leading-frame (those whose parent is the world, such as f1)
// the scene parameter is passed to the Frame constructor:
f1 = new Frame(scene);
// whereas for the remaining frames we pass any constructor taking a
// reference frame paramater, such as Frame(Frame referenceFrame)
f2 = new Frame(f1);
f3 = new Frame(f1) {
// note that within visit() the geometry is defined
// at the frame local coordinate system
@Override
public void visit() {
sphere(50);
}
};
}
and then traverse it with:
void draw() {
// traverse() peforms a top-down traversal of the frame hierarchy, applying the
// local frame transformation and calling the visit() method on each visited frame
scene.traverse();
}
To set the scene tracked-frame (the frame the mouse should interact with) call setTrackedFrame(Frame) or update it using ray-casting with cast(), for example:
void mouseMoved() {
// the tracked-frame is updated using ray-casting from the set of scene attached frames
scene.cast();
}
To interact with a given frame use any Scene method that takes a Frame parameter, such as: spin(Frame), translate(Frame), scale(float, Frame) or zoom(float, Frame). For example:
public void mouseDragged() {
// spin f1
if (mouseButton == LEFT)
scene.spin(f1);
// translate f3
else if (mouseButton == RIGHT)
scene.translate(f3);
// zoom f2
else
scene.zoom(scene.mouseDX(), f2);
}
To interact with the default-frame (which is either the tracked-frame updated with the mouseMoved
above or the scene eye when the tracked-frame is null) use the frameless versions of the above methods, e.g., spin(), translate(), scale(float) or zoom(float). For example:
public void mouseDragged() {
// spins the default-frame (the eye or the frame picked with a mouseMoved)
if (mouseButton == LEFT)
scene.spin();
else if (mouseButton == RIGHT)
// translates the default-frame (the eye or the frame picked with a mouseMoved)
scene.translate();
// zooms the default-frame (the eye or the frame picked with a mouseMoved)
else
scene.zoom(scene.mouseDX());
}
See the CajasOrientadas example. Some advantages of using attached frames are:
- The scene gets rendered respect to an eye frame which may be set from any Frame instance (see setEye(Frame)). Note that the Scene sets up a default eye frame. To retrieve the scene eye call eye().
- The scene topology is set (even at run time) with setReference(Frame).
- Frames may be picked using ray-casting and the Scene provides all sorts of interactivity commands to manipulate them.
- The Scene methods location(Vector, Frame) and screenLocation(Vector, Frame) transforms coordinates between frame and screen space.
- The frame methods setTranslation(Vector), translate(Vector), setRotation(Quaternion), rotate(Quaternion), setScaling(float) and scale(float), locally manipulates a frame instance.
- The Frame methods setPosition(Vector), setOrientation(Quaternion), and setMagnitude(float), globally manipulates a frame instance.
- The Frame methods location(Vector, Frame) and displacement(Vector, Frame) transforms coordinates and vectors (resp.) from other frame instances.
- The Frame methods worldLocation(Vector) and worldDisplacement(Vector) transforms frame coordinates and vectors (resp.) to the world.
- The Frame method setConstraint(Constrain) applies a Constraint to a frame instance limiting its motion.
- The role played by a Frame instance during a scene graph traversal is implemented by overriding its visit() method.
To bypass the traverse() algorithm use detached frames, or override visit() to setup a cullingCondition for the frame as follows (see visit(), cull(boolean) and isCulled()):
Scene scene;
Frame frame;
void setup() {
scene = new Scene(this);
frame = new Frame(scene) {
@Override
public void visit() {
// Hierarchical culling is optional and disabled by default. When the cullingCondition
// (which should be implemented by you) is true, scene.traverse() will prune the branch at the frame
cull(cullingCondition);
if(!isCulled())
// Draw your object here, in the local coordinate system.
}
}
}
A Shape is an attached Frame specialization that can be set from a retained-mode rendering Processing PShape or from an immediate-mode rendering Processing procedure. Shapes can be picked precisely using their projection onto the screen, see setPrecision(Frame.Precision). Use traverse() to render all scene-graph shapes or draw() to render a specific one instead.
To set a retained-mode shape use Shape shape = new Shape(Scene scene, PShape shape)
or Shape shape = new Shape(Scene scene)
and then call Shape.setGraphics(PShape).
Immediate-mode shapes should override Shape.setGraphics(PGraphics)
, e.g., using an anonymous inner
Shape class instance, such as with the following:
...
Shape shape;
void setup() {
...
shape = new Shape(scene) {
@Override
protected void setGraphics(PGraphics canvas) {
//immediate-mode rendering procedure
}
};
}
and then render it with:
void draw() {
// calls visit() on each shape to draw the shape
scene.traverse();
}
See the DepthOfField example. Some advantages of using shapes are:
- Same as with attached frames.
- Shapes are picked precisely using ray-tracing against the pixels of their projection. See setPrecision.
- Shapes can be set as the scene eye (see setEye(Frame)) which may be useful to depict the viewer in first person camera style.
A frame (and hence a shape) can be animated through a key-frame Catmull-Rom interpolator path. Use code such as the following:
Scene scene;
PShape pshape;
Shape shape;
Interpolator interpolator;
void setup() {
...
shape = new Shape(scene, pshape);
interpolator = new Interpolator(shape);
for (int i = 0; i < random(4, 10); i++)
interpolator.addKeyFrame(scene.randomFrame());
interpolator.start();
}
which will create a random interpolator path containing [4..10] key-frames. The interpolation is also started. The interpolator path may be drawn with code like this:
...
void draw() {
scene.traverse();
scene.drawPath(interpolator, 5);
}
while traverse() will draw the animated shape(s) drawPath(Interpolator, int) will draw the interpolated path too. See the Interpolators example.
Setting up a Human Interface Device (hid) (different than the mouse which is provided by default) such as a keyboard or a space-navigator, is a two step process:
- Define an hid tracked-frame instance, using an arbitrary name for it (see setTrackedFrame(String, Frame)); and,
- Call any interactivity method that take an hid param (such as translate(String, float, float, float), rotate(String, float, float, float) or scale(String, float) following the name convention you defined in 1.
See the SpaceNavigator example.
Observations:
- An hid tracked-frame (see trackedFrame(String)) defines in turn an hid default-frame (see defaultFrame(String)) which simply returns the tracked-frame or the scene eye when the hid tracked-frame is
null
- The hid interactivity methods are implemented in terms of the ones defined previously by simply passing the hid defaultFrame(String) to them.
- The default hid is defined with a
null
String parameter (e.g., scale(float) simply callsscale(null, delta)
). The Scene default mouse hid presented in the Frames section is precisely implemented is this manner. - To update an hid tracked-frame using ray-casting call track(String, Point, Frame[]) (detached or attached frames), track(String, Point) (only attached frames) or cast(String, Point) (only for attached frames too). While track(String, Point, Frame[]) and track(String, Point) update the hid tracked-frame synchronously (i.e., they return the hid tracked-frame immediately), cast(String, Point) updates it asynchronously (i.e., it optimally updates the hid tracked-frame during the next call to the traverse() algorithm).
Application control (aka as Post-WIMP interaction styles) refers to interfaces βcontaining at least one interaction technique not dependent on classical 2D widgetsβ [van Dam], such as: tangible interaction, or perceptual and affective computing.
Implementing an application control for a frame is a two step process:
- Override the frame method interact(Object...) to parse the gesture into a custom (application) control.
- Send gesture data to the frame by calling one of the following scene methods: defaultHIDControl(Object...), control(String, Object...) or control(Frame, Object...).
See the ApplicationControl example.
The Scene implements several static drawing functions that complements those already provided by Processing, such as: drawCylinder(PGraphics, int, float, float), drawHollowCylinder(PGraphics, int, float, float, Vector, Vector), drawCone(PGraphics, int, float, float, float, float), drawCone(PGraphics, int, float, float, float, float, float) and drawTorusSolenoid(PGraphics, int, int, float, float).
Drawing functions that take a PGraphics
parameter (including the above static ones), such as beginHUD(PGraphics),
endHUD(PGraphics), drawAxes(PGraphics, float), drawCross(PGraphics, float, float, float) and drawGrid(PGraphics) among others, can be used to set a (Shape).
Another scene's eye (different than this one) can be drawn with drawEye(Graph). Typical usage include interactive minimaps and visibility culling visualization and debugging.
Import/update it directly from your PDE. Otherwise download your release and extract it to your sketchbook libraries
folder.
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!