' '

Difficulty

2

Prerequisites

ray-tracers/v1

patterns/basics

materials/pattern

In order to create a new pattern, we need to define a new subclass of Pattern2DImplementation or Pattern3DImplementation and define the at method. Next, we need to create a factory function that creates an instance of the new pattern implementation class and wraps it in a Pattern2D or Pattern3D.

some-new-pattern.h
#pragma once

#include "patterns/pattern.h"


namespace patterns
{
    namespace _private_
    {
        class SomeNewPatternImplementation : public Pattern2DImplementation
        {
        public:
            SomeNewPatternImplementation(parameters);

            bool at(const math::Point2D& point) const override;

        private:
            // fields
        };
    }

    Pattern2D factory_function(parameters);
}
some-new-pattern.cpp
#include "patterns/some-new-pattern.h"


patterns::_private_::SomeNewPatternImplementation::SomeNewPatternImplementation(parameters)
    : m_fields(parameters)
{
    // NOP
}

bool patterns::_private_::SomeNewPatternImplementation::at(const math::Point2D& point) const
{
    /* pattern logic */
}

Pattern2D patterns::some_new_pattern(parameters)
{
    return Pattern2D(std::make_shared<_private_::SomeNewPatternImplementation>(parameters));
}

This is a lot of boilerplate code to write. The only part that distinguishes one pattern from another is at's body. Wouldn’t it make more sense to only have to define that part and let the rest be automatically generated for you?

Ideal syntax
Pattern2D patterns::some_new_pattern(parameters)
{
    return generate_new_pattern(/* pattern logic */);
}

Making this possible is exactly the goal of this extension. This will save us from having to declare and define a class with fields and a constructor each time.

Create files patterns/lambda-pattern.h and patterns/lambda-pattern.cpp.

1. 2D Implementation

As mentioned before, the one thing distinguishing one pattern from another is the pattern logic which normally resides within the at method. This logic is code, and code is typically contained within a function. A lightweight syntax to define functions is to make use of lambdas, hence the name of this extension.

1.1. LambdaPattern2DImplementation

We will now define a class LambdaPattern2DImplementation which takes care of most of the boilerplate. Conceptually, it looks like this in pseudocode:

class LambdaPattern2DImplementation:
    def __init__(self, func): # (1)
        self.__func = func

    def at(self, point2d):
        return self.__func(point2d) # (2)
  1. The constructor takes a function and stores it in a field.

  2. The at method simply calls this function.

Define the LambdaPattern2DImplementation in C++.

  • It will have a field m_function. You will need to discover its type for yourself. Remember that it needs to have the same parameters and return type as at.

  • Note that functions can be passed by value, i.e., no reference or smart pointer trickery is necessary.

  • The constructor will need to initialize this field.

  • The at method will call this m_function.

1.2. make_pattern

Next, we need a factory function which we’ll name make_pattern. It takes a function, creates a LambdaPattern2DImplementation object with it and wraps it in a Pattern2D object.

Define make_pattern. It’s defined exactly in the same way as other factory functions. The function parameter is a parameter just like any other, just with a rather long name.

2. 3D Implementation

The 3D variant is nearly identical.

Create a class LambdaPattern3DImplementation class that inherits from Pattern3DImplementation. Add a field, constructor and at method.

Overload make_pattern so that it can also create 3D patterns.

3. Evaluation

Create a pattern named xsplit(boundary) that assigns white (true) to all points whose x-coordinate is less than boundary.

def scene_at(now)
{
  var camera = Cameras.perspective( [ "eye": pos(0,0,5),
                                      "look_at": pos(0,0,0) ] )

  var white = Materials.uniform( [ "ambient": Colors.white() ] )

  var black = Materials.uniform( [ "ambient": Colors.black() ] )

  var t = Animations.animate(-2.5, 2.5, seconds(5))[now]

  var pattern = Patterns.xsplit(t)
  var mat = Materials.from_pattern(pattern, white, black)

  var root = decorate(mat, xy_plane())

  var lights = [ Lights.omnidirectional( pos(5,5,5), Colors.white() ) ]

  var scene = create_scene(camera, root, lights)
}

var raytracer   = Raytracers.v1()
var renderer    = Renderers.standard( [ "width": 200,
                                        "height": 200,
                                        "sampler": Samplers.single(),
                                        "ray_tracer": raytracer ] )

pipeline( scene_animation(scene_at, seconds(5)),
          [ Pipeline.animation(30),
            Pipeline.renderer(renderer),
            Pipeline.wif(),
            Pipeline.base64(),
            Pipeline.stdout() ] )

Create a pattern named ysplit(boundary) that assigns white (true) to all points whose y-coordinate is less than boundary.

def scene_at(now)
{
  var camera = Cameras.perspective( [ "eye": pos(0,0,5),
                                      "look_at": pos(0,0,0) ] )

  var white = Materials.uniform( [ "ambient": Colors.red() ] )

  var black = Materials.uniform( [ "ambient": Colors.blue() ] )

  var t = Animations.animate(-2.5, 2.5, seconds(5))[now]

  var pattern = Patterns.ysplit(t)
  var mat = Materials.from_pattern(pattern, white, black)

  var root = decorate(mat, xy_plane())

  var lights = [ Lights.omnidirectional( pos(5,5,5), Colors.white() ) ]

  var scene = create_scene(camera, root, lights)
}

var raytracer   = Raytracers.v1()
var renderer    = Renderers.standard( [ "width": 200,
                                        "height": 200,
                                        "sampler": Samplers.single(),
                                        "ray_tracer": raytracer ] )

pipeline( scene_animation(scene_at, seconds(5)),
          [ Pipeline.animation(30),
            Pipeline.renderer(renderer),
            Pipeline.wif(),
            Pipeline.base64(),
            Pipeline.stdout() ] )