XB PointStream/custom parsers

From CDOT Wiki
Jump to: navigation, search

Parser Interface

Intro

Currently XBPS only supports reading .ASC file types. If you require the library to render other files, you will need to write a custom parser and register it with the library. This should not be difficult, there are only a few things your parser must implement.


Example

Here is an example which you can use to help understand the process: [link here]


Parser Skeleton

The following bit of code is a skeleton which you can use as an aid to help you write your own parser for XB PointStream.

var Your_Parser_Name = (function() {
  /*
  XBPS will create an instance of your parser and pass in an object with three named properties:
  
  start - must occur exactly once. When you call this function, pass in a reference to your parser
  end   - must occur exactly once. When you call this function, pass in a reference to your parser
  parse - may occur one or many times. When you call this function, pass in a reference to your parser
  as the first argument and an object as the second argument.

  This second object must have variables referencing typed single-dimensional arrays which contain
  the parsed values. For example, if vertsArray and colsArray were Float32Array arrays, you would call
  the parse function like this:

  var attributes = {};
  attributes["ps_Vertex"] = vertsArray;
  attributes["ps_Color"] = colsArray;
  parse(thisParser, attributes);

  PointStream will create buffers using these values and start rendering them using the built-in
  shaders. Notice the variable names have been qualified with "ps_". If you are using the XB PointStream
  built-in shaders, you will need to use these exact variable names.
 
  These are the only two variables the built-in shaders read. If your parser reads in vertex normal data,
  you will need to write your own shaders to handle lighting.
  */
  function Your_Parser_Name(config) {
   
    /*Returns the version of this parser.*/
    this.__defineGetter__("version", function(){return /*!!*/;});
   
    /*Get the number of parsed points so far.*/
    this.__defineGetter__("numParsedPoints", function(){return /*!!*/;});
   
    /*Get the total number of points in the point cloud.*/
    this.__defineGetter__("numTotalPoints", function(){return /*!!*/;});
   
    /*Returns the progress of downloading the point cloud between zero and one.*/
    this.__defineGetter__("progress", function(){return /*!!*/;});
   
    /*Returns the file size of the resource in bytes.*/
    this.__defineGetter__("fileSize", function(){return /*!!*/;});
   
    /*Path = path to the resource */
    this.load = function(path){/*!!*/};
  }
  return Your_Parser_Name;
}());

Putting Everything Together

Sample Parser

Here is a very simple parser which only reads in vertex data from a file.

/*The following is a very simple parser written only to be used as
  an example of how a user could write a parser for XB PointStream.*/
var FOO_Parser = (function() {

  function FOO_Parser(config) {
   
    var start = config.start || function(){};
    var parse = config.parse || function(){};
    var end = config.end || function(){};
   
    var fileSizeInBytes = 0;
    var numParsedPoints = 0;
    var numTotalPoints = 0;
    var progress = 0;
      
    // keep track if onprogress event handler was called to
    // handle Chrome/WebKit vs. Minefield differences.
    // Minefield will call onprogress zero or many times
    // Chrome/WebKit will call onprogress one or many times
    var onProgressCalled = false;
    var AJAX = null;
       
    /* Returns the version of this parser. */
    this.__defineGetter__("version", function(){return 0.1;});
   
    /* Get the number of parsed points so far. */
    this.__defineGetter__("numParsedPoints", function(){return numParsedPoints;});
   
    /* Get the total number of points in the point cloud. */
    this.__defineGetter__("numTotalPoints", function(){ return numTotalPoints;});
   
    /* Get the progress of downloading the point cloud (zero to one or -1 if unknown) */
    this.__defineGetter__("progress", function(){ return progress;});
   
    /* Returns the file size of the resource in bytes. */
    this.__defineGetter__("fileSize", function(){return fileSizeInBytes;});

    /**/
    this.load = function(path){

      AJAX = new XMLHttpRequest();     
      AJAX.parser = this;

      /*occurs exactly once, when the resource begins to be downloaded */
      AJAX.onloadstart = function(evt){
        start(AJAX.parser);
      };
           
      /*occurs exactly once, when the file is done being downloaded */
      AJAX.onload = function(evt){
        var ascData = AJAX.responseText;
        var chunk = null;

        // if the onprogress event didn't get called--we simply got
        // the file in one go, we can parse from start to finish.
        if(onProgressCalled === false){
          chunk = ascData;
        }
        // otherwise the onprogress event was called at least once,
        // that means we need to get the data from a specific point to the end.
        else if(ascData.length - AJAX.lastNewLineIndex > 1){
          chunk = ascData.substring(AJAX.lastNewLineIndex, ascData.length);
        }

        AJAX.parseChunk(chunk);

        numTotalPoints = numParsedPoints;
        progress = 1;
        end(AJAX.parser);
      }

      AJAX.parseChunk = function(chunk){
       
        // this occurs over network connections, but not locally.
        if(chunk !== ""){
          // trim leading and trailing spaces
          chunk = chunk.replace(/\s+$/,"");
          chunk = chunk.replace(/^\s+/,"");
         
          // split on white space
          chunk = chunk.split(/\s+/);
         
          var numVerts = chunk.length/3;
          numParsedPoints += numVerts;
                   
          var verts = new Float32Array(numVerts * 3);

          for(var i = 0, j = 0, len = chunk.length; i < len; i += 3, j += 3){
            verts[j]   = parseFloat(chunk[i]);
            verts[j+1] = parseFloat(chunk[i+1]);
            verts[j+2] = parseFloat(chunk[i+2]);
          }
                   
          // XB PointStream expects an object with named/value pairs
          // which contain the attribute arrays. These must match attribute
          // names found in the shader
          parse(AJAX.parser, {"ps_Vertex":verts});
        }
      };
   
      /*On Minefield, this will occur zero or many times
        On Chrome/WebKit this will occur one or many times */
      AJAX.onprogress = function(evt){
     
       if(evt.lengthComputable){
          fileSizeInBytes = evt.total;
          progress = evt.loaded/evt.total;
        }

        onProgressCalled = true;

        // if we have something to actually parse
        if(AJAX.responseText){
          var ascData = AJAX.responseText;

          // likely stopped getting data in the middle of a line in the file:
          // 1.079 1.296 9.360 0 0 0 4.307 1.181 5.208\n
          // 3.163 2.225 6.139 0 0 0 0.6<-- stopped here
         
          // So find the last known newline. Everything from the last
          // request to this last newline can be placed in a buffer.
          var lastNewLineIndex = ascData.lastIndexOf("\n");
          AJAX.lastNewLineIndex = lastNewLineIndex;
         
          // if the status just changed and we finished downloading the
          // file, grab everyting until the end. If there is only a bunch
          // of whitespace, make a note of that and don't bother parsing.
          if(AJAX.readyState === 4){
            var chunk = ascData.substring(AJAX.startOfNextChunk, ascData.length);
            AJAX.parseChunk(chunk);
          }
          // if we still have more data to go
          else{
            // Start of the next chunk starts after the newline.
            var chunk = ascData.substring(AJAX.startOfNextChunk, lastNewLineIndex + 1);
            AJAX.startOfNextChunk = lastNewLineIndex + 1;
            AJAX.parseChunk(chunk);
          }
        }
      };
     
      AJAX.open("GET", path, true);
      AJAX.send(null);
    };
  }
  return FOO_Parser;
}());

Create your HTML file

Create an HTML which includes your parser, the library and the demo.js script.

<html>
  <head>
    <script src="foo_parser.js"></script>
    <script src="xbps.js"></script>
    <script src="demo.js"></script>
  </head>

  <body onLoad="start();">
  <canvas id="canvas" width="300" height="300"></canvas>   
  </body>
</html>

Create the Demo.js file

Give XBPS a reference to your parser and tell it what files it can read. In this case, we pass in "foo".

function start(){
  var ps = new PointStream();
  ps.setup(document.getElementById('canvas'));
  ps.registerParser("foo", FOO_Parser);
  ps.onRender = function render() {
    ps.translate(0, 0, -20);
    ps.render(acorn);
  };
  var acorn = ps.load("pointCloud.foo");
}
/*
  The following example demonstrates how XBPS might use
  a particular parser.
*/

var parser;

function startCallback(parser){
  // started
}

function parseCallback(parser, attributes){
  parser.version;
  parser.numParsedPoints;
  parser.numTotalPoints;
  parser.progress;
  parser.fileSize;
}

function finishCallback(parser){
  // finished
}

// create a hypothetical parser and set the callbacks
parser = new Your_Parser_Name({ start: startCallback,
                                parse: parseCallback,
                                end: finishCallback});
// load some resource
parser.load("pointcloud.xyz");