Difference between revisions of "XB PointStream/custom parsers"
(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. | ||
+ | |||
− | == | + | ==Example== |
+ | Here is an example which you can use to help understand the process: [link here] | ||
− | |||
− | |||
− | |||
− | |||
− | + | ==Parser Skeleton== | |
− | |||
− | |||
− | The | + | 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. |
− | |||
<pre> | <pre> | ||
− | /* | + | 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; | ||
+ | }()); | ||
+ | </pre> | ||
− | + | == Putting Everything Together == | |
− | + | === Sample Parser === | |
− | + | 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 | + | 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; | ||
+ | }()); | ||
+ | </pre> | ||
− | + | === Create your HTML file === | |
− | + | 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> | ||
− | + | <body onLoad="start();"> | |
− | + | <canvas id="canvas" width="300" height="300"></canvas> | |
− | + | </body> | |
+ | </html> | ||
+ | </pre> | ||
− | + | === 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". | |
− | + | <pre> | |
− | + | 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"); | ||
+ | } | ||
</pre> | </pre> | ||
<pre> | <pre> | ||
/* | /* | ||
− | The following example demonstrates how | + | 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 | + | parser = new Your_Parser_Name({ start: startCallback, |
− | + | parse: parseCallback, | |
− | + | end: finishCallback}); | |
// load some resource | // load some resource | ||
parser.load("pointcloud.xyz"); | parser.load("pointcloud.xyz"); | ||
</pre> | </pre> |
Latest revision as of 14:08, 16 February 2011
Contents
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");