Porting the Cartoon Painter Python sample to C++

Greetings,

I need to use the cartoon inking shader from the Cartoon Painter that was developed a year ago by Cla (see this topic: [Cartoon Painter)).

Since I’ve never used shaders before, I’m trying to port it to C++ first, and see what I can do with it then.

I have something that actually do stuff. I’m just not exactly sure what or why: though I managed to port pretty much everything, some of the code in Python isn’t directly portable to C++.
And I think the two lines I didn’t manage to reproduce correctly using the C++ API might be responsible.

There are at least two things that I didn’t manage to port, and a few other things might be wrong.

First, this line:

        self._normals_camera = base.makeCamera(self._normals_buf,
                                               camName='normals_camera',
                                               lens=base.cam.node().getLens())

There is no such make_camera method in C++ Panda3D, and I can’t find what the first parameter might be (it’s a variable created by makeTextureBuffer, which in C++ returns a GraphicsOutput).

And then, there’s this line:

base.cam2d.node().getLens()

What is this cam2d ? And where does it come from ?

Also, please tell me if I was wrong to do the next things (where framework was declared as ‘PandaFramework framwork’):
-> Replacing base.win with framework.get_window(0)
-> Replacing base.cam with framework.get_window(0).get_camera(0)

If you want a look at the whole thing, here it is:

#include <panda3d/pandaFramework.h>
#include <panda3d/pandaSystem.h>
#include <panda3d/stereoDisplayRegion.h>
#define DEFAULT_STEPFUNC_MIN    0.8
#define DEFAULT_STEPFUNC_MAX    1.0
#define DEFAULT_STEPFUNC_STEPS  1.0
#define DEFAULT_SEPARATION      0.001
#define DEFAULT_CUTOFF          0.3
#define DEFAULT_LIGHT_POS       LVector3f(30, -50, 0)

#define CARTOON_SHADING_SHADER "cartoonpainter/shading.sha"
#define CARTOON_NORMALS_SHADER "cartoonpainter/normalGen.sha"
#define CARTOON_INKING_SHADER  "cartoonpainter/inkGen.sha"
#define CARTOON_SHADING_TAG    "CartoonPainter.CartoonShading"
#define CARTOON_INKING_TAG     "CartoonPainter.CartoonInking"

PandaFramework       framework;

class CartoonPainter : public AsyncTask
{
public:
  CartoonPainter(int sort = -1)
  {
    NodePath   tmp;
    PT(Shader) shader        = Shader::load(CARTOON_SHADING_SHADER, Shader::SL_Cg);
    PT(Shader) normal_shader = Shader::load(CARTOON_NORMALS_SHADER, Shader::SL_Cg);
    PT(Shader) inking_shader = Shader::load(CARTOON_INKING_SHADER,  Shader::SL_Cg);
    
    enabled           = true;
    camera_spot_light = false;
    // paintings = {}
    stepf_min         = DEFAULT_STEPFUNC_MIN;
    stepf_max         = DEFAULT_STEPFUNC_MAX;
    stepf_steps       = DEFAULT_STEPFUNC_STEPS;
    separation        = DEFAULT_SEPARATION;
    cutoff            = DEFAULT_CUTOFF;
    
    // Check if the videocard support shaders. If it doesn't, then the CartoonPainter will be disabled
    shaders_supported = framework.get_window(0)->get_graphics_window()->get_gsg()->get_supports_basic_shaders();
    if (!shaders_supported)
    {
      enabled = false;
      cerr << "CartoonPainter disabled. Video driver reports that shaders are not supported" << endl;
      return ;
    }
    
    // Create two new scenes, one 3d and the other 2d. The 3d scene will have attached all those nodepaths
    // we want to cartoon paint. The 2d scene will have a card where the black outlines of the cartoon inking
    // are drawn.
    toon_render      = NodePath("toon_render");
    inking_render_2d = NodePath("inking_render2d");
    inking_render_2d.set_depth_test(false);
    inking_render_2d.set_depth_write(false);
    
    // Make a new display region where to render objects in cartoon shading.
    // toon_render will have a light node used by the shader as input.
    toon_dr          = framework.get_window(0)->get_graphics_window()->make_stereo_display_region();
    toon_dr->set_sort(sort - 1);
    toon_camera_ptr  = new Camera("toon_camera");
    toon_camera      = toon_render.attach_new_node(toon_camera_ptr);
    toon_camera_ptr->set_lens(framework.get_window(0)->get_camera(0)->get_lens());
    toon_dr->set_camera(toon_camera);
    light            = toon_render.attach_new_node("light");
    light.set_pos(DEFAULT_LIGHT_POS);
    toon_render.set_shader_input("light", light);
    toon_render.set_shader_input("min",   LVector4f(stepf_min));
    toon_render.set_shader_input("max",   LVector4f(stepf_max));
    toon_render.set_shader_input("steps", LVector4f(stepf_steps));
    toon_camera_ptr->set_tag_state_key(CARTOON_SHADING_TAG);
    tmp              = NodePath("tmp");
    tmp.set_shader(shader);
    toon_camera_ptr->set_tag_state("True", tmp.get_state());

    // Make a 'normals buffer' user later by the cartoon inker to get the
    // black outlines around all theo bjects of toon render. The normals
    // buffer will contain a picture of the model colorized so that the
    // color of the model is a representation of the model's normal at that point
    normals_buf        = framework.get_window(0)->get_graphics_window()->make_texture_buffer("normals_buf", 0, 0);
    normals_buf->set_clear_color(LVector4f(0.5f, 0.5f, 0.5f, 1));
    normals_camera     = framework.get_window(0)->make_camera();
    normals_camera_ptr = reinterpret_cast<Camera*>(normals_camera.node());
    normals_camera_ptr->set_name("normals_camera");
    normals_camera_ptr->set_lens(framework.get_window(0)->get_camera(0)->get_lens());
    // WARNING The python make_camera call also did something with the GraphicsOutput. No idea what. No idea how to reproduce.
    tmp                = NodePath("tmp");
    tmp.set_shader(normal_shader);
    normals_camera_ptr->set_initial_state(tmp.get_state());
    
    // Make a new display region for a 2d scene where we are going to draw a texture card
    // with the black outlines of every objects in toon_render
    inking_dr          = framework.get_window(0)->get_graphics_window()->make_stereo_display_region();
    inking_dr->set_sort(sort);
    inking_camera_ptr  = new Camera("inking_camera");
    inking_camera      = inking_render_2d.attach_new_node(inking_camera_ptr);
    // WARNING The next line is bullshit, python said base.cam2d.node. The fuck is cam2d ??
    inking_camera_ptr->set_lens(framework.get_window(0)->get_camera(0)->get_lens());
    inking_dr->set_camera(inking_camera);
    
    // Extract a texture card from the normal buffer and process it using the cartoon inker. The
    // final result will be a transparent texture containing the black outlines for every objects in toon_render
    ink_outlines       = normals_buf->get_texture_card();
    ink_outlines.set_transparency(TransparencyAttrib::M_alpha);
    ink_outlines.set_color(1, 1, 1, 0);
    ink_outlines.reparent_to(inking_render_2d);
    ink_outlines.set_shader(inking_shader);
    ink_outlines.set_shader_input("separation", LVector4f(separation, 0, separation, 0));
    ink_outlines.set_shader_input("cutoff",     LVector4f(cutoff));
    
    // Start a task to update the internal cameras and every nodepath in toon_render
    AsyncTaskManager::get_global_ptr()->add(this);
  }
  
  AsyncTask::DoneStatus do_task(void)
  {
    if (enabled)
    {
      Camera*     base_cam = framework.get_window(0)->get_camera(0);
      NodePath    np_base_cam(base_cam);
      LQuaternion cam_quat = np_base_cam.get_quat(np_base_cam.get_top());
      LPoint3     cam_pos  = np_base_cam.get_pos(np_base_cam.get_top());
      Lens*       cam_lens = base_cam->get_lens();
      
      toon_camera.set_quat   (toon_camera.get_top(), cam_quat);
      toon_camera.set_pos    (toon_camera.get_top(), cam_pos);
      normals_camera.set_quat(normals_camera.get_top(), cam_quat);
      normals_camera.set_pos (normals_camera.get_top(), cam_pos);
      toon_camera_ptr->set_lens(cam_lens);
      normals_camera_ptr->set_lens(cam_lens);
    }
    return (AsyncTask::DS_cont);
  }
  
  void paint(NodePath nodepath)
  {
    if (enabled)
    {
      inp = nodepath.instance_under_node(toon_render, nodepath.get_name());
      inp.set_tag(CARTOON_SHADING_TAG, "True");
      inp.set_tag(CARTOON_INKING_TAG,  "True");
      nodepath.stash();
    }
  }

private:
  bool     enabled, shaders_supported;
  bool     camera_spot_light;
  float    stepf_min, stepf_max, stepf_steps, separation, cutoff;
  
  NodePath             toon_render, inking_render_2d;
  StereoDisplayRegion* toon_dr;
  NodePath             toon_camera;
  Camera*              toon_camera_ptr;
  NodePath             light;
  
  GraphicsOutput*      normals_buf;
  NodePath             normals_camera;
  Camera*              normals_camera_ptr;
  
  StereoDisplayRegion* inking_dr;
  NodePath             inking_camera;
  Camera*              inking_camera_ptr;
  
  NodePath             ink_outlines;

  NodePath             inp;
};

class ToonMaker
{
public:
  ToonMaker() : cartoon_painter()
  {
    WindowFramework* win = framework.get_window(0);
    
    NodePath big_nik = win->load_model(framework.get_models(), "nik-dragon");
    big_nik.reparent_to(win->get_render());
    big_nik.set_pos(0, 70, 0);
    cartoon_painter.paint(big_nik);
  }

private:
  CartoonPainter cartoon_painter;
};

int main(int argc, char** argv)
{
  WindowFramework* window;
  
  framework.open_framework(argc, argv);
  framework.set_window_title("Fallout Equestria");
  window = framework.open_window();

  {
    ToonMaker tm;
    
    framework.main_loop();
    framework.close_framework();
  }
  return (0);
}