This part of the module implements the scene graph functionality for M.GRL. This provides a simple means of instancing 2D and 3D art assets, greatly simplifies rendering code, and prerforms rendering optimizations to have better performance than would be achieved with by rendering manually.
Additionally, a mechanism for data binding exists on most of the properties of graph objects. For example, you could set the object’s “location_x” coordinate to be a value like “10”, or you could set it to be a function that returns a numerical value like “10”. This can be used to perform animation tasks. When a function is assigned to a property in such a fashion, it is called a “driver function”.
Note that, being a scene graph, objects can be parented to other objects. When the parent moves, the child moves with it! Empty graph objects can be used to influence objects that draw. Between empties, inheritance, and driver functions, you are given the tools to implement animations without requiring vertex deformation.
Some properties on graph nodes can be accessed either as an array or as individual channels. Node.location = [x,y,z] can be used to set a driver function for all three channels at once. The individual channels can be accessed, set, or assigned their own driver methods via .location_x, .location_y, and .location_z. Currently, .location, .rotation, and .scale work like this on all graph nodes. CameraNodes also have .look_at and .up_vector. In the future, all vec3 uniform variables will be accessible in this way. If a GraphNode-descended object is assigned to a “tripple” handle, such as the example of look_at in the code above, then a driver function will be automatically created to wrap the object’s “location” property. Note, you should avoid setting individual channels via the array handle - don not do ”.location[0] = num”!
Word of caution: driver functions are only called if the scene graph thinks it needs them for rendering! The way this is determined, is that driver functions associated to glsl variables are always evaluated. If such a driver function attempts to read from another driver function, then that driver is evaluated (and cached, so the value doesn’t change again this frame), and so on.
please.GraphNode ()
Constructor function that creates an Empty node. The constructor accepts no arguments, but the created object may be configrued by adjusting its properties. All properties that would have a numerical value normally set to them may also be set as a function (called a “driver”) that returns a numerical value. When the scene graph’s ”.tick” method is called, the driver functions are evaluated, and their results are cached for use by the scene graph’s .draw() method.
var empty = new please.GraphNode();
var empty.rotation.x = 10;
var empty.rotation.x = fuction() { return performance.now()/100; };
Most of the time when you want to draw something with the scene graph, you create the GraphNodes indirectly from loaded game assets.
var character = please.access("alice.jta").instance();
var sprite_animation = please.access("particle.gani").instance();
var just_a_quad = please.access("hello_world.png").instance();
GraphNodes have some special properties:
Additionally, each GraphNode has a “shader” property, which is an object containing additional animatable properties for automatically setting GLSL shader variables when it is drawn. The following variables have non-zero defaults.
Graph nodes have the following getters for accessing graph inhertiance. You should avoid saving the vaules returned by these anywhere, as you can prevent objects from being garbage collected or accidentally create a reference cycle.
GraphNodes also have the following methods for managing the scene graph:
If you want to create your own special GraphNodes, be sure to set the following variables in your constructor to ensure they are unique to each instance.
var FancyNode = function () {
please.GraphNode.call(this);
};
FancyNode.prototype = Object.create(please.GraphNode.prototype);
If you want to make an Empty or a derived constructor drawable, set the “__drawable” property to true, and set the “draw” property to a function that contains your custom drawing code. Optionally, the “bind” property may also be set to a function. Bind is called before Draw, and is used to set up GL state. Bind is called regardless of if the node is visible, though both bind and draw requrie the node be drawable. The bind method is essentially vestigial and should not be used.
please.SceneGraph ()
Constructor function that creates an instance of the scene graph. The constructor accepts no arguments. The graph must contain at least one camera to be renderable. See CameraNode docstring for more details.
The .tick() method on SceneGraph instances is called once per frame (multiple render passes may occur per frame), and is responsible for determining the world matricies for each object in the graph, caching the newest values of driver functions, and performs state sorting. While .tick() may be called manually, it is nolonger required as the draw call will do it automatically.
The .draw() method is responsible for invoking the .draw() methods of all of the nodes in the graph. State sorted nodes will be invoked in the order determined by .tick, though the z-sorted nodes will need to be sorted on every draw call. This method may called as many times as you like per frame. Normally the usage of this will look something like the following example: