Difference between revisions of "XB PointStream/custom parsers"

From CDOT Wiki
Jump to: navigation, search
(Created page with ' ===Parser Interface=== There will be cases in which users have their own file format which they want to render with XB PointStream (for example, .ARA or .XML). We need to give …')
 
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
===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.
 +
  
===Parser Interface===
+
==Example==
 +
Here is an example which you can use to help understand the process: [link here]
  
There will be cases in which users have their own file format
 
which they want to render with XB PointStream (for example,
 
.ARA or .XML). We need to give users the ability to write their
 
own parser and hook it into the library.
 
  
Users would write their JavaScript code which would implement
+
==Parser Skeleton==
the methods below. Once they do that, they would register their
 
parser with the library by passing in an extension and their parser.
 
  
The library would then take care of the rest by creating an instance
+
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.
of their parser, call its methods and return a point cloud object.
 
  
 
<pre>
 
<pre>
/*
+
var Your_Parser_Name = (function() {
   The constructor of the parser.
+
  /*
 +
   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;
 +
}());
 +
</pre>
  
  @param {Object} obj - collection of named functions
+
== Putting Everything Together ==
  
  These functions pass the parser back to the library since the library could
+
=== Sample Parser ===
   be working with many parsers simultaneously.
+
Here is a very simple parser which only reads in vertex data from a file.
 +
<pre>
 +
/*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() {
  
   start - must occur exactly once. Has one argument, the parser itself
+
   function FOO_Parser(config) {
  end  - must occur exactly once. Has one argument, the parser itself
+
 
  parse - may occur one or many times. Has two arguments, the parser itself and a
+
    var start = config.start || function(){};
  named collection of value types.
+
    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;});
  
  See below for an example
+
    /**/
 +
    this.load = function(path){
  
*/
+
      AJAX = new XMLHttpRequest();   
Constructor(obj)
+
      AJAX.parser = this;
  
/*
+
      /*occurs exactly once, when the resource begins to be downloaded */
  Begins to load the resource.
+
      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;
  
  @param {String} path - path to resource
+
        // if the onprogress event didn't get called--we simply got
*/
+
        // the file in one go, we can parse from start to finish.
load(path)
+
        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);
  
////// Getters
+
        numTotalPoints = numParsedPoints;
 +
        progress = 1;
 +
        end(AJAX.parser);
 +
      }
  
/*
+
      AJAX.parseChunk = function(chunk){
  Get the version of this parser.
+
     
+
        // this occurs over network connections, but not locally.
  @returns {String} parser version
+
        if(chunk !== ""){
*/
+
          // trim leading and trailing spaces
version
+
          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){
  Get the number of points which have been parsed.
+
            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;
 +
        }
  
  @returns {Number} the number of points parsed so far by the parser.
+
        onProgressCalled = true;
*/
 
numParsedPoints
 
  
/*
+
        // if we have something to actually parse
  Get the total number of points in the point cloud, including points
+
        if(AJAX.responseText){
  which have not yet been parsed.
+
          var ascData = AJAX.responseText;
  
  @returns {Number} the total number of points in the resource or -1 if unknown.
+
          // 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
numTotalPoints
+
          // 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;
 +
}());
 +
</pre>
  
/*
+
=== Create your HTML file ===
  Get the progress of the parser, how much it has parsed so far.
+
Create an HTML which includes your parser, the library and the demo.js script.
 +
<pre>
 +
<html>
 +
  <head>
 +
    <script src="foo_parser.js"></script>
 +
    <script src="xbps.js"></script>
 +
    <script src="demo.js"></script>
 +
  </head>
  
   @returns {Number} value between 0 to 1 or -1 if unknown.
+
   <body onLoad="start();">
*/
+
  <canvas id="canvas" width="300" height="300"></canvas> 
progress
+
  </body>
 +
</html>
 +
</pre>
  
/*
+
=== Create the Demo.js file ===
  The size of the resource in bytes.
+
Give XBPS a reference to your parser and tell it what files it can read. In this case, we pass in "foo".
  
   @returns {Number} the number of bytes in the resource or -1 if unknown.
+
<pre>
*/
+
function start(){
fileSize
+
  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");
 +
}
 
</pre>
 
</pre>
  
 
<pre>
 
<pre>
 
/*
 
/*
   The following example demonstrates how XB PointStream might use
+
   The following example demonstrates how XBPS might use
 
   a particular parser.
 
   a particular parser.
 
*/
 
*/
Line 105: Line 274:
  
 
// create a hypothetical parser and set the callbacks
 
// create a hypothetical parser and set the callbacks
parser = new XYZParser({ start: startCallback,
+
parser = new Your_Parser_Name({ start: startCallback,
                        parse: parseCallback,
+
                                parse: parseCallback,
                        end: finishCallback});
+
                                end: finishCallback});
 
// load some resource
 
// load some resource
 
parser.load("pointcloud.xyz");
 
parser.load("pointcloud.xyz");
 
</pre>
 
</pre>

Latest revision as of 15:08, 16 February 2011

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");