AR project : Saito OBJLoader + NyARToolkit

edited September 2016 in Library Questions

Hi !

I've learned processing (thanks to the wonderful Daniel Shiffman) few days ago to help in a student project based on AR. First, I did the three examples in the tutorial of Amnon Owed ( ) and after a lot of fails, it finally worked.

Now, I try to do the same as the third example (cube) but instead of using beginshape/endshape, I want to input my own CAO model. The only thing I found is the library Saito OBJLoader which allows me to load a .obj file into Processing. But I meet always the same problem : "Material"MTLO" is not defined", each time the marker enter in the camera field (sorry about my English).

I actually selected a material for my model in my CAO software (PTCCreo Parametric) and saved it as a .obj file.

I'm sorry, i let the "mountain growth" and the loops because it doesn't work without and i actually don't know how to change it.

    // Augmented Reality Dynamic Example by Amnon Owed (21/12/11)
    // Processing 1.5.1 + NyARToolkit 1.1.6 + GSVideo 1.0

    import*; // for the loadPatternFilenames() function
    import processing.opengl.*; // for OPENGL rendering
    import jp.nyatla.nyar4psg.*; // the NyARToolkit Processing library
    import codeanticode.gsvideo.*; // the GSVideo library
    import saito.objloader.*;

    // a central location is used for the camera_para.dat and pattern files, so you don't have to copy them to each individual sketch
    // Make sure to change both the camPara and the patternPath String to where the files are on YOUR computer
    // the full path to the camera_para.dat file
    String camPara = "C:/Users/layatlu/Documents/Processing/libraries/nyar4psg/data/camera_para.dat";
    // the full path to the .patt pattern files
    String patternPath = "C:/Users/layatlu/Documents/Processing/libraries/nyar4psg/patternMaker/examples/ARToolKit_Patterns";
    // the dimensions at which the AR will take place. with the current library 1280x720 is about the highest possible resolution.
    int arWidth = 800;
    int arHeight = 600;
    // the number of pattern markers (from the complete list of .patt files) that will be detected, here the first 10 from the list.
    int numMarkers = 10;

    // the resolution at which the mountains will be displayed
    int resX = 60;
    int resY = 60;
    // this is a 2 dimensional float array that all the displayed mountains use during their update-to-draw routine
    float[][] val = new float[resX][resY];

    GSCapture cam;
    MultiMarker nya;
    float[] scaler = new float[numMarkers];
    float[] noiseScale = new float[numMarkers];
    float[] mountainHeight = new float[numMarkers];
    float[] mountainGrowth = new float[numMarkers];

    OBJModel model;

    void setup() {
      size(1280, 720, P3D); // the sketch will resize correctly, so for example setting it to 1920 x 1080 will work as well
      cam = new GSCapture(this, 800, 600); // initializing the webcam capture at a specific resolution (correct/possible settings depends on YOUR webcam)
      cam.start(); // start capturing
      //model H
      model = new OBJModel(this, "h.obj");
      model.scale(80); //because model is too small
      noStroke(); // turn off stroke for the rest of this sketch :-)
      // create a new MultiMarker at a specific resolution (arWidth x arHeight), with the default camera calibration and coordinate system
      nya = new MultiMarker(this, arWidth, arHeight, camPara, NyAR4PsgConfig.CONFIG_PSG);
      // set the delay after which a lost marker is no longer displayed. by default set to something higher, but here manually set to immediate.
      String[] patterns = loadPatternFilenames(patternPath);
      // for the selected number of markers, add the marker for detection
      // create an individual scale, noiseScale and maximum mountainHeight for that marker (= mountain)
      for (int i=0; i<numMarkers; i++) {
        nya.addARMarker(patternPath + "/" + patterns[i], 80);
        scaler[i] = random(0.8, 1.9); // scaled a little smaller or bigger
        // noiseScale[i] = random(0.02, 0.075); // the perlin noise scale to make it look nicely mountainy
        // mountainHeight[i] = random(75, 150); // the maximum height of a mountain

    void draw() {
      // if there is a cam image coming in...
      if (cam.available()== true) {; // read the cam image
        background(0); // a background call is needed for correct display of the marker results
        image(cam, 0, 0, width, height); // display the image at the width and height of the sketch window
        // create a copy of the cam image at the resolution of the AR detection (otherwise nya.detect will throw an assertion error!)
        PImage cSmall = cam.get();
        cSmall.resize(arWidth, arHeight);
        nya.detect(cSmall); // detect markers in the image
        drawMountains(); // draw dynamically flowing mountains on the detected markers (3D)

    // this function draws correctly placed 3D 'mountains' on top of detected markers
    // while the mountains are displayed they grow (up to a certain point), while not displayed they return to the zero-state
    void drawMountains() {
      // set the AR perspective uniformly, this general point-of-view is the same for all markers
      // turn on some general lights (without lights it also looks pretty cool, try commenting it out!)
      // for all the markers...
      for (int i=0; i<numMarkers; i++) {  // if the mountainGrowth is higher than zero, decrease by 0.05 (return to the zero-state), then continue to the next marker
        if ((!nya.isExistMarker(i))) {
          if (mountainGrowth[i] > 0) { 
            mountainGrowth[i] -= 0.05;
        // the following code is only reached and run if the marker DOES EXIST
        // if the mountainGrowth is lower than 1, increase by 0.03
        if (mountainGrowth[i] < 1) { 
          mountainGrowth[i] += 0.03;
        // the double for loop below sets the values in the 2 dimensional float array for this mountain, based on it's noiseScale, mountainHeight and index (i).
        float xoff = 0.0;
        for (int x=0; x<resX; x++) {
          xoff += noiseScale[i];
          float yoff = 0;
          for (int y=0; y<resY; y++) {
            yoff += noiseScale[i];
            val[x][y] = noise(i*10+xoff+frameCount*0.05, yoff) * mountainHeight[i]; // this sets the value
            float distance = dist(x, y, resX/2, resY/2);
            distance = map(distance, 0, resX/2, 1, 0);
            if (distance < 0) { 
              distance = -distance;
            } // this line causing the four corners to flap upwards (try commenting it out or setting it to zero)
            val[x][y] *= distance; // in the default case this makes the value approach zero towards the outer ends (try commenting it out to see the difference)
        PMatrix syst3D;

        // get the Matrix for this marker and use it (through setMatrix)
        syst3D = nya.getMarkerMatrix(i);
        scale(1, -1); // turn things upside down to work intuitively for Processing users
        scale(scaler[i]); // scale the mountain by it's individual scaler
        translate(-resX/2, -resY/2); // translate to center the mountain on the marker
        // for the full resolution...
        for (int x=0; x<resX-1; x++) {
          for (int y=0; y<resY-1; y++) {
            // each face is a Shape with a fill color, together they make a colored mountain
            fill(255, 0, 0);

      // reset to the default perspective

    // this function loads .patt filenames into a list of Strings based on a full path to a directory (relies on
    String[] loadPatternFilenames(String patternPath) {
      File folder = new File(patternPath);
      FilenameFilter pattFilter = new FilenameFilter() {
        public boolean accept(File dir, String name) {
          return name.toLowerCase().endsWith(".patt");
      return folder.list(pattFilter);


  • Answer ✓

    Do you have a .mtl file? Copy it to the same directory as the .obj file.

    If not, edit the .obj file and remove the references to mtl0

  • Oh, you're right, I wandered why I had like five different files named "h" with five different extensions, THANKS A LOT ! It seemed so obvious right now ahah

  • Finally it doesn't work, the .mtl file is in the same directory as the .obj, but same error appears.

  • Answer ✓

    Then try commenting out the mtl0 references in the obj file. You'll lose the materials but it'll verify the obj file and the loading code.

    We can't run the code without the relevant files. Even then we'd need the AR and video libraries.

    Try to reduce the code to the minimum needed to fix your error. If the problem is just loading the object then reduce it to just that. And supply the files.

  • Have you tried using the loadSahpe function to load your object? It might work ifthe format is recognize. Ensure obj file, material and textures are on data folder. Try

    PShape ps;
      void setup(){
      ps = loadShape("a_file.obj");
    void draw(){


  • loadShape function doesn't work, I tried and get a NullPointer exception, I'm a beginner so perhaps I did something wrong.

    import processing.opengl.*;
    PShape ps;
      void setup(){
      ps = loadShape("h.obj");
    void draw(){
  • Yes, I've already tried to make a little program only to load my obj file and display it, and it worked nicely, even if there was the same message written in the box...

Sign In or Register to comment.