XB PointStream/custom parsers
From Open Source@Seneca
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");
