Parser module

JSON serialization/deserialization, the darts plugin factory, and scene parser.

JSON

JSON is a human-readible data interchange format for expressing attribute-value pairs. You can read more about it here:

Darts relies on the great nlohmann-json C++ library for parsing and manipulating JSON data.

In darts, we will use JSON for two purposes:

  1. As a generic way to pass named parameters to functions
  2. As a way to specify and load text-based scene files

Darts already supports serializing all the basis darts data-types (such as floats, strings, Vec, Color3, etc.) to and from a JSON object. Additionally, most of the constructors for ray tracing classes in darts take a single JSON object as a parameter. This allows us to have a uniform interface for creating these structures while allowing the constructors to retrieve the necessary values from the JSON object. This dramatically simplifies our code for writing a parser for reading scene files from disk.

darts_tutorial1.cpp provides a gentle introduction to this functionality and nlohmann-json website provides extensive documentation.

Plugins and factories

Darts relies on a Factory pattern a automatically register new plugins that extend its functionality. Dart plugins are simply classes that extend a few abstract base classes within darts, such as Material, Surface, and later we will also introduce Texture, Integrator, Sampler, and more.

Registering plugins

Whenever you are adding a new plugin to darts, you will call the macro #DARTS_REGISTER_CLASS_IN_FACTORY, which creates some code that informs a global #DartsFactory how to create objects defined by your plugin. For instance, if we were adding a hypothetical SuperAwesome Material to darts, we would include:

DARTS_REGISTER_CLASS_IN_FACTORY(Material, SuperAwesome, "super-awesome")

at the bottom of our implementation file for the plugin. This informs darts about a new #Material of type SuperAwesome, and associates the keyword super-awesome with it. This allows us to create such a Material in other parts of darts (or from a file on disc) without ever having to #include a header for that plugin. We can simply call the function #DartsFactory<Material>::create(). This function accepts a json object as a parameter, and returns a shared_ptr to a Material. It determines the specific Material class to create by looking for a "type" field in the json object. If the value is "super-awesome" it will create our SuperAwesome Material for us.

More generally we will use #DartsFactory<Type>::create() with different classes for the Type, such as Surface, Texture, etc.

Registering instances

It is often necessary to have one element in a scene refer to another one by name (for instance, if we'd like to load a texture and use it to texture more than one Material). Each JSON plugin specification can therefore contain an optional "name": field. Latter parts of the JSON file can refer to that instance by this name. To facilitate this, #DartsFactory::register_instance() allows storing previously parsed/created instances into a registry. These can later be retrieved by name using #DartsFactory::find(). The darts basecode already has an example of how to accomplish this for Materials in the #Scene::parse() function, but you may want to add similar functional for other types of plugins as you extend darts.

An example JSON specification

In addition to the "type": and "name": fields, plugins may require any number of additional parameters which can be specified using additional fields in the json object. A full specification of our hypothetical SuperAwesome Material might look as follows in JSON:

"materials":
[
    {
        "type": "super-awesome",
        "name": "my awesome material",
        "fuzziness": 1.5,
        "color": [1, .2, .7],
        "awesomeness-factor": 1000
    }
]

The constructor for SuperAwesome would then be responsible for retrieving the values from the json object and appropriately initialize the material.

The darts scene parser

You will be implementing the top-level darts parser in Scene::parse(). It takes a json object which contains the contents of a json file loaded from a disc.