diff options
Diffstat (limited to 'apps/samples/vrml')
-rw-r--r-- | apps/samples/vrml/README.md | 150 | ||||
-rw-r--r-- | apps/samples/vrml/viewer.html | 48 | ||||
-rw-r--r-- | apps/samples/vrml/viewer.js | 386 | ||||
-rw-r--r-- | apps/samples/vrml/vrml-server.scxml | 396 |
4 files changed, 980 insertions, 0 deletions
diff --git a/apps/samples/vrml/README.md b/apps/samples/vrml/README.md new file mode 100644 index 0000000..2a019da --- /dev/null +++ b/apps/samples/vrml/README.md @@ -0,0 +1,150 @@ +# VRML Server + +The VRML Server allows clients to retrieve sceneshots of 3D models on the filesystem in a variety of formats. + +## General Mode of Operation + +The VRML server will monitor its vrml directory recursively for <tt>vrml</tt> and <tt>wrl</tt> +files. Whenever such a file is found, it is converted into a native, binary representation and +saved in the tmp directory. Clients can now access sceneshots of this model by specifying the +desired pose and format. + +## Accessing Sceneshots + +In the simplest case, a sceneshot is retrieved by simply requested its respective URL on the VRML server: + +<tt>http://host/vrml/HARD_MP_VAL_000.png</tt> + +All paths start with vrml and then reflect the vrml directory structure as it is being monitored. When a directory +<tt>foo</tt> was added (either by creation or linking) in the vrml directory, its 3D models will be available at: + +<tt>http://host/vrml/foo/FANCY_MODEL_000.png</tt> + +When you do not pass any parameters, you will get a sceneshot of the model with its largest, axis aligned surface area +facing the camera. That is, the model will be rotated by multiples of 90deg to show the side of the bounding box which +has the largest surface area. The implied assumption is that this side is suited to identify the model and its eventual +problems the quickest. + +If you do not like the standard sceneshot, you can pass a couple of parameters to adapt most aspects of the scene: + +<table> + <tr><th>Name</th><th>Range</th><th>Description</th></tr> + <tr><td><tt>pitch</tt></td><td>[0 .. 2π] rad</td><td>Rotation along the x-axis</td></tr> + <tr><td><tt>roll</tt></td><td>[0 .. 2π] rad</td><td>Rotation along the z-axis</td></tr> + <tr><td><tt>yaw</tt></td><td>[0 .. 2π] rad</td><td>Rotation along the y-axis</td></tr> + <tr><td><tt>zoom</tt></td><td>[0 .. ∞] bounding-sphere units</td><td>Distance of camera to model center</td></tr> + <tr><td><tt>x</tt></td><td>[-∞ .. ∞] OpenGL units</td><td>Translation on x-axis</td></tr> + <tr><td><tt>y</tt></td><td>[-∞ .. ∞] OpenGL units</td><td>Translation on y-axis</td></tr> + <tr><td><tt>z</tt></td><td>[-∞ .. ∞] OpenGL units</td><td>Translation on z-axis (consider using zoom instead)</td></tr> + <tr><td><tt>width</tt></td><td>[0 .. BIG] pixels</td><td>The width of the image (limited by your GPU)</td></tr> + <tr><td><tt>height</tt></td><td>[0 .. BIG] pixels</td><td>The height of the image</td></tr> + <tr><td><tt>autorotate</tt></td><td>[<tt>on</tt> | <tt>off</tt>]</td><td>Whether or not to autorotate first</td></tr> +</table> + +<tt>http://host/vrml/HARD_MP_VAL_000.png?pitch=0.3&width=2560&height=1600</tt> + +There are some caveats: +<ul> + <li>With euler angles such as pitch/roll/yaw, a gimbal lock can occur. + <li>Choosing zoom, x, y or z to big will move the model off the clipping distance. + <li>width and height have no upper limit, this might be a potential DoS. + <li>When observing series of models with autorotating on, not every model is guaranteed to start with the same pose. + <li>The OpenGL units really ought to be expressed in multiples of bounding-sphere units. +</ul> + +## REST API + +The main purpose of the REST API is to provide clients with a list of available model files. + +<table> + <tr><th>Path</th><th>Type</th><th>Return Value</th><th>Example</th></tr> + <tr> + <td><tt>/vrml</tt></td> + <td><tt>GET</tt></td> + <td> + A JSON structure identifying all known models in the hierarchy as found on the filesystem.<br/> + + The entries are organized in a tree, reflecting the original locations relative to the vrml + directory. The suffix of <tt>png</tt> is just one example, supported extensions are ultimately + defined by the available <a href="http://www.link.de/here">OSG writer plugins</a>, but limited for + now to <tt>gif</tt>, <tt>jpg</tt>, <tt>png</tt>, <tt>tif</tt> and <tt>bmp</tt>. + </td> + <td> +<pre> +{ + "models": { + "HARD_MP_VAL_000": { + "path": "/HARD_MP_VAL_000.png", + "url": "http://host/vrml/HARD_MP_VAL_000.png" + }, + "HARD_MP_VAL_001": { + "path": "/HARD_MP_VAL_001.png", + "url": "http://host/vrml/HARD_MP_VAL_001.png" + }, + "data": { + "SOFT_MP_VAL_000": { + "path": "/data/SOFT_MP_VAL_000.png", + "url": "http://host/vrml/data/SOFT_MP_VAL_000.png" + }, + ... +</pre> + </td> + </tr> + + <tr> + <td><tt>/vrml/models</tt>, <tt>/vrml/wrls</tt></td> + <td><tt>GET</tt></td> + <td> + A JSON structure with information about the available binary model files in the tmp directory or the wrl + files in the vrml directory respectively.<br/> + + The entries correspond to the tree at <tt>/vrml</tt> but all paths are flattened using the path delimiter + ('<tt>:</tt>' per default). + </td> + <td> +<pre> +{ + "HARD_MP_VAL_000": { + "atime": 1363002503, + "ctime": 1362521747, + "dir": "/tmp", + "extension": "osgb", + "group": "/", + "mtime": "1362521747", + "name": "HARD_MP_VAL_000.osgb", + "path": "/tmp/HARD_MP_VAL_000.osgb", + "relDir": "/", + "relPath": "/HARD_MP_VAL_000.osgb", + "size": "580201", + "strippedName": "HARD_MP_VAL_000" + }, +... +</pre> + </td> + </tr> + + <tr> + <td><tt>/vrml/processed</tt></td> + <td><tt>GET</tt></td> + <td> + A JSON structure with information about the sceneshots that were requested recently and are still on disc.<br/> + + The individual entries within a model key encode the request parameters seperated by underscores, that is:<br/> + The euler angles <tt>pitch</tt>, <tt>roll</tt>, <tt>yaw</tt>, <tt>zoom</tt>, translation in <tt>x</tt>, translation + in <tt>y</tt>, translation in <tt>z</tt>, <tt>width</tt>, <tt>height</tt>, and whether or not to <tt>autorotate</tt>. + </td> + <td> + <pre> +{ + "HARD_MP_VAL_000": { + "0.94_0_0_1_0_0_0_640_480_on.png": { + "atime": 1363002687, + "ctime": 1363002687, + "dir": "/tmp", +... + </pre> + </td> + </tr> + + +</table> diff --git a/apps/samples/vrml/viewer.html b/apps/samples/vrml/viewer.html new file mode 100644 index 0000000..4cf971b --- /dev/null +++ b/apps/samples/vrml/viewer.html @@ -0,0 +1,48 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> + <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.3/dijit/themes/tundra/tundra.css"> + <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.3/dojox/layout/resources/FloatingPane.css"> + <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.3/dojox/layout/resources/ResizeHandle.css"> + + <style type="text/css"> + .alternateDock { + position:absolute; + background-color:#ededed; + right:0px; top:0px; + border-left:1px solid #ccc; + height:100%; + + } + #alternateDock ul.dojoxDockList { display:block; } + .testFixedSize { + width:300px; + height:200px; + padding:7px; + } + </style> + + <script type="text/javascript"> + // dojoConfig = { + // async : false, + // isDebug : true, + // debugAtAllCosts : true, + // } + </script> + + <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.8.3/dojo/dojo.js"></script> + <script type="text/javascript" src="viewer.js"></script> + + <script type="text/javascript"> + require(["dojo/domReady!", "dojo"], function(dom, dojo) { + var viewer = new VRMLViewer(dojo.byId("scene1"), { 'dojo': dojo }); + }); + </script> + </head> + <body class="tundra"> + <div style="width: 600px; height: 400px"> + <div id="scene1"></div> + </div> + </body> +</html> diff --git a/apps/samples/vrml/viewer.js b/apps/samples/vrml/viewer.js new file mode 100644 index 0000000..dac3a96 --- /dev/null +++ b/apps/samples/vrml/viewer.js @@ -0,0 +1,386 @@ +function VRMLViewer(element, params) { + + // private attributes + var self = this; + var dojo = require("dojo"); + var domConst = dojo.require('dojo/dom-construct'); + var xhr = dojo.require("dojo/_base/xhr"); + + require(["dojox/storage"], function (storage) { + self.localStorage = dojox.storage.manager.getProvider(); + self.localStorage.initialize(); + var savedServerURL = self.localStorage.get("vrmlServer"); + if (savedServerURL) { + self.serverURL = savedServerURL; + self.serverBox.set('value', savedServerURL); + } + }); + + /** + * Why can't I fetch dijits via CommonJS? + */ + // var Button = require('dijit/form/Button'); + // var HorizontalSlider = require('dijit/form/HorizontalSlider'); + // var VerticalSlider = require('dijit/form/VerticalSlider'); + + + // private instanceId + if (!VRMLViewer.instances) + VRMLViewer.instances = 0; + var instanceId = VRMLViewer.instances++; + + // public attributes + this.pitch = 0; + this.roll = 0; + this.yaw = 0; + this.zoom = 1; + this.x = 0; + this.y = 0; + this.z = 0; + this.width = 640; + this.height = 480; + this.autorotate = false; + + this.serverURL = "http://88.69.49.213:8080/vrml"; + this.imageURL; + + view = "normal"; + if (params.view == "maximized") { + view = "maximized"; + } + + // if (this.view == "maximized") { + // this.width = Math.min(element.clientWidth - 150, 800); + // this.height = (this.width * 3) / 4; + // } else { + // this.width = Math.min(element.clientWidth - 50, 800); + // this.height = (this.width * 3) / 4; + // } + + // privileged public methods + this.updateScene = function() { + if (self.imageURL) { + self.imgElem.src = self.imageURL + + '?width=' + self.width + + '&height=' + self.height + + '&pitch=' + self.pitch + + '&roll=' + self.roll + + '&yaw=' + self.yaw + + '&x=' + self.x + + '&y=' + self.y + + '&z=' + self.z + + '&zoom=' + self.zoom + + '&autorotate=' + (self.autorotate ? '1' : '0'); + } + } + + this.refreshServer = function(server) { + serverURL = server; + self.localStorage.put("vrmlServer", serverURL, null); + xhr.get({ + // The URL to request + url: server, + handleAs:"json", + headers:{"X-Requested-With":null}, + load: function(result) { + (function fillstore(tree, parentId) { + for (key in tree) { + if ('url' in tree[key]) { + self.fileStore.add({id:parentId+key, name:key, url:self.serverURL + tree[key].path, parent:parentId}); +// self.messageBox.innerHTML += '<pre>' + self.serverURL + tree[key].path + '</pre>'; +// self.messageBox.innerHTML += '<pre>' + tree[key].url + '?width=200&height=150</pre>' + '<img src="' + tree[key].url + '?width=200&height=150" />'; + } else { + self.fileStore.add({id:parentId+key, name:key, parent:parentId}); + fillstore(tree[key], parentId+key); + } + } + } (result.models, "root", "")); + } + }); + } + + // establish our dom + element.appendChild(domConst.toDom('\ + <div id="floatPane">\ + <div style="text-align: right"><div class="server" /></div><button type="button" class="browseButton"></button></div>\ + <div style="height: 100%; overflow: auto" class="fileList"></div>\ + </div>\ + <table>\ + <tr>\ + <td valign="top">\ + <div style="position: relative; padding: 0px">\ + <img class="model" style="z-index: -1; min-width: ' + self.width + 'px; min-height: ' + self.height + 'px"></img>\ + <div style="position: absolute; left: 10px; top: 7%; height: 100%">\ + <div class="pitchSlide"></div>\ + </div>\ + <div style="position: absolute; right: 10px; top: 15%; height: 50%">\ + <div class="zoomSlide"></div>\ + </div>\ + <div style="position: absolute; left: 7%; top: 10px; width: 100%">\ + <div class="rollSlide"></div>\ + </div>\ + <div style="position: absolute; left: 7%; bottom: 15px;">\ + <div class="yawSlide"></div>\ + </div>\ + <table cellspacing="0" style="position: absolute; right: 5px; bottom: 25px">\ + <tr>\ + <td align="right">x: <input type="text" class="xSpinner"></input></td>\ + </tr>\ + <tr>\ + <td align="right">y: <input type="text" class="ySpinner"></input></td>\ + </tr>\ + <tr>\ + <td align="right"><button type="button" class="resetButton"></button></td>\ + </tr>\ + </table>\ + </div>\ + </td>\ + <td valign="top" height="100%">\ + </td>\ + </tr>\ + <tr>\ + <td colspan="2"><div class="messages"></div></td>\ + </tr>\ + </table>\ + ')); + + // fetch special dom nodes for content + this.messageBox = dojo.query("div.messages", element)[0]; + this.imgElem = dojo.query("img.model", element)[0]; + this.serverBoxElem = dojo.query("div.server", element)[0]; + this.browseButtonElem = dojo.query("button.browseButton", element)[0]; + this.fileListElem = dojo.query("div.fileList", element)[0]; + + this.resetButtonElem = dojo.query("button.resetButton", element)[0]; + this.xSpinnerElem = dojo.query("input.xSpinner", element)[0]; + this.ySpinnerElem = dojo.query("input.ySpinner", element)[0]; + this.pitchSlideElem = dojo.query("div.pitchSlide", element)[0]; + this.rollSlideElem = dojo.query("div.rollSlide", element)[0]; + this.yawSlideElem = dojo.query("div.yawSlide", element)[0]; + this.zoomSlideElem = dojo.query("div.zoomSlide", element)[0]; + + require(["dojox/layout/FloatingPane"], function(FloatingPane) { + self.floatPane = new FloatingPane({ + title: "VRML Viewer", + resizable: true, dockable: false, closable: false, + style: "position:absolute;top:10;left:10;width:250px;height:300px;z-index: 2", + id: "floatPane", + }, dojo.byId("floatPane")); + + self.floatPane.startup(); + }); + + // require(['dijit/form/Button','dijit/Dialog'], + // function (Button) { + // var d = new Dialog({ + // 'title':'I am nonmodal', + // 'class':'nonModal' + // }); + // }); + + // setup fileStore for tree list + require(["dojo/store/Memory", "dojo/store/Observable", "dijit/tree/ObjectStoreModel"], + function(Memory, Observable, ObjectStoreModel){ + self.fileStore = new Observable(new Memory({ + data: [ { id: 'root', name:'3D Models'} ], + getChildren: function(object){ + return this.query({parent: object.id}); + } + })); + self.fileTreeModel = new ObjectStoreModel({ + store: self.fileStore, + query: { id: "root" } + }); + }); + + // setup actual tree dijit + require(["dojo/dom", "dojo/store/Memory", "dojo/store/Observable", "dijit/tree/ObjectStoreModel", "dijit/Tree"], + function(dom, Memory, Observable, ObjectStoreModel, Tree) { + self.fileList = new dijit.Tree({ + id: "fileList", + model: self.fileTreeModel, + persist: false, + showRoot: false, + onClick: function(item){ +// self.messageBox.innerHTML = '<pre>' + item.url + '?width=200&height=150</pre>' + '<img src="' + item.url + '?width=200&height=150" />'; + if ('url' in item) { + self.imageURL = item.url; + self.updateScene(); + } + }, + getIconClass: function(item, opened) { + return (!item || !('url' in item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf" + }, + getIconStyle: function(item, opened){ + if('url' in item) { + return { backgroundImage: "url('" + item.url + "?width=16&height=16')"}; + } + } + //return {backgroundImage: "url('" + item.url + "?width=16&height=16')"}; + + }).placeAt(self.fileListElem); +// tree.dndController.singular = true; + }); + + require(["dijit/form/TextBox"], function(TextBox) { + self.serverBox = new TextBox({ + name: "Server", + value: self.serverURL, + style: "width: 70%", + + onKeyDown: function(e) { + var code = e.keyCode || e.which; + if( code === 13 ) { + e.preventDefault(); + self.refreshServer(this.get("value")); + return false; + } + }, + }, self.serverBoxElem); + }); + + require(["dijit/form/Button"], function(Button) { + self.resetButton = new Button({ + label: "Reset", + onClick: function(){ + self.xSpinner.set('value',0); + self.ySpinner.set('value',0); + self.zSpinner.set('value',0); + self.pitchSlide.attr('value',0); + self.rollSlide.attr('value',0); + self.yawSlide.attr('value',0); + self.zoomSlide.attr('value',1); + + self.floatPane.startup(); + self.floatPane.show(); + } + }, self.resetButtonElem); + }); + + require(["dijit/form/NumberSpinner"], function(NumberSpinner) { + self.xSpinner = new NumberSpinner({ + value: 0, + smallDelta: 1, + constraints: { places:0 }, + style: "width:60px", + onChange: function(value){ + self.x = value; + self.updateScene(); + } + }, self.xSpinnerElem ); + }); + + require(["dijit/form/NumberSpinner"], function(NumberSpinner) { + self.ySpinner = new NumberSpinner({ + value: 0, + smallDelta: 1, + constraints: { places:0 }, + style: "width:60px", + onChange: function(value){ + self.y = value; + self.updateScene(); + } + }, self.ySpinnerElem ); + }); + + require(["dijit/form/NumberSpinner"], function(NumberSpinner) { + self.zSpinner = new NumberSpinner({ + value: 0, + smallDelta: 1, + constraints: { places:0 }, + style: "width:60px", + onChange: function(value){ + self.z = value; + self.updateScene(); + } + }, self.zSpinnerElem ); + }); + + require(["dijit/form/Button"], function(Button) { + self.browseButton = new Button({ + label: "Browse", + onClick: function(){ + self.refreshServer(self.serverBox.get("value")); + } + }, self.browseButtonElem); + }); + + // add zoom slider + require(["dijit/form/VerticalSlider"], function(VerticalSlider) { + self.zoomSlide = new VerticalSlider({ + minimum: 0.001, + showButtons: false, + maximum: 1, + value: 1, + intermediateChanges: false, + style: "height: 90%", + onChange: function(value){ + self.zoom = Math.ceil(value * 1000) / 1000; + self.updateScene(); + } + }, self.zoomSlideElem); + }); + + // add pitch slider + require(["dijit/form/VerticalSlider", "dijit/form/VerticalRuleLabels", "dijit/form/VerticalRule"], function(VerticalSlider, VerticalRuleLabels, VerticalRule) { + // Create the rules + // var rulesNode = dojo.create("div", {}, self.pitchSlideElem, "first"); + // var sliderRules = new VerticalRule({ + // container: "leftDecoration", + // count: 11, + // style: "width: 5px;"}, rulesNode); + + // Create the labels + // var labelsNode = dojo.create( + // "div", {}, self.pitchSlideElem, "first"); + // var sliderLabels = new VerticalRuleLabels({ + // labels: ["Pitch", ""], + // container: "rightDecoration", + // labelStyle: "-webkit-transform: rotate(-90deg); -moz-transform: rotate(-90deg); padding-left: -3px; font-size: 0.75em" + // }, labelsNode); + + self.pitchSlide = new VerticalSlider({ + minimum: 0, + showButtons: false, + maximum: 2 * 3.14159, + value: 0, + intermediateChanges: false, + style: "height: 90%", + onChange: function(value){ + self.pitch = Math.ceil(value * 100) / 100; + self.updateScene(); + } + }, self.pitchSlideElem); + }); + + // add roll slider + require(["dijit/form/HorizontalSlider"], function(HorizontalSlider) { + self.rollSlide = new HorizontalSlider({ + minimum: 0, + showButtons: false, + maximum: 2 * 3.14159, + intermediateChanges: false, + style: "width: 90%", + onChange: function(value){ + self.roll = Math.ceil(value * 100) / 100; + self.updateScene(); + } + }, self.rollSlideElem); + }); + + // add yaw slider + require(["dijit/form/HorizontalSlider"], function(HorizontalSlider) { + self.yawSlide = new HorizontalSlider({ + minimum: 0, + showButtons: false, + maximum: 2 * 3.14159, + intermediateChanges: false, + style: "width: 90%", + onChange: function(value){ + self.yaw = Math.ceil(value * 100) / 100; + self.updateScene(); + } + }, self.yawSlideElem); + }); + +} diff --git a/apps/samples/vrml/vrml-server.scxml b/apps/samples/vrml/vrml-server.scxml new file mode 100644 index 0000000..0d6517c --- /dev/null +++ b/apps/samples/vrml/vrml-server.scxml @@ -0,0 +1,396 @@ +<scxml datamodel="ecmascript" name="vrml"> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" /> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/string.endsWith.js" /> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/array.last.js" /> + <script> + var wrls = {}; // information of the wrl, vrml files + var models = {}; // information of the osgb files + var processed = {}; // information about processed files + + var pathDelim = ':'; // we need to flatten directories - this will seperate them in filenames + + /** + * This pattern matches the query string we use as part of generated image filenames + */ + var numPattern = '(-?[0-9]+\.?[0-9]*)'; + var formatPattern = new RegExp( + '-(' + numPattern + // pitch + '_' + numPattern + // roll + '_' + numPattern + // yaw + '_' + numPattern + // zoom + '_' + numPattern + // x + '_' + numPattern + // y + '_' + numPattern + // z + '_' + numPattern + // width + '_' + numPattern + // height + '_(off|on)' + // autorotate + ')\\.\\w+$'); // end + + /** + * Transform a file we found into a processed or model struct + */ + function fileToStruct(file) { + var struct = {}; + var formatMatch = formatPattern.exec(file.name); + // is this a processed file? + if (formatMatch && formatMatch.length == 12) { + struct.key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.name.substr(0, file.name.length - formatMatch[0].length); + struct.format = formatMatch[1] + '.' + file.extension; + struct.pitch = parseFloat(formatMatch[2]); + struct.roll = parseFloat(formatMatch[3]); + struct.yaw = parseFloat(formatMatch[4]); + struct.zoom = parseFloat(formatMatch[5]); + struct.x = parseFloat(formatMatch[6]); + struct.y = parseFloat(formatMatch[7]); + struct.z = parseFloat(formatMatch[8]); + struct.width = parseFloat(formatMatch[9]); + struct.height = parseFloat(formatMatch[10]); + struct.autorotate = parseFloat(formatMatch[11]); + } else { + struct.key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.strippedName; + } + return struct; + } + + /** + * Transform a http request into something to look up in the processed structure + */ + function reqToStruct(req) { + var struct = {}; + + var query = (('query' in req) ? req.query : {}); + + struct.pitch = (('pitch' in query && isNumber(query.pitch)) ? query.pitch : 0); + struct.roll = (('roll' in query && isNumber(query.roll)) ? query.roll : 0); + struct.yaw = (('yaw' in query && isNumber(query.yaw)) ? query.yaw : 0); + struct.zoom = (('zoom' in query && isNumber(query.zoom)) ? query.zoom : 1); + struct.x = (('x' in query && isNumber(query.x)) ? query.x : 0); + struct.y = (('y' in query && isNumber(query.y)) ? query.y : 0); + struct.z = (('z' in query && isNumber(query.z)) ? query.z : 0); + struct.width = (('width' in query && isNumber(query.width)) ? query.width : 640); + struct.height = (('height' in query && isNumber(query.height)) ? query.height : 480); + struct.autorotate = (('autorotate' in query && (query.autorotate === 'on' || query.autorotate === 'off')) ? query.autorotate : 'on'); + + var fileComp = req.pathComponent[req.pathComponent.length - 1]; + struct.file = fileComp.substr(0, fileComp.indexOf('.')); + struct.ext = fileComp.substr(fileComp.indexOf('.') + 1); + + struct.key = _event.data.pathComponent.slice(1, _event.data.pathComponent.length - 1).join(pathDelim); + if (struct.key.length > 0) + struct.key += pathDelim; + struct.key += struct.file; + + struct.format = + struct.pitch + '_' + + struct.roll + '_' + + struct.yaw + '_' + + struct.zoom + '_' + + struct.x + '_' + + struct.y + '_' + + struct.z + '_' + + struct.width + '_' + + struct.height + '_' + + struct.autorotate + '.' + + struct.ext; + return struct; + } + + function isSupportedFormat(extension) { + if (extension === "gif") + return true; + if (extension === "jpg") + return true; + if (extension === "jpeg") + return true; + if (extension === "png") + return true; + if (extension === "tif") + return true; + if (extension === "tiff") + return true; + if (extension === "bmp") + return true; + return false; + } + + // list all available models in a summary format for the topmost request + function overviewList() { + var struct = {}; + struct.models = {}; + for (key in models) { + var model = models[key]; + var group = models[key].group + var name = model.strippedName.split(pathDelim).last(); + var entry = assign(struct, ['models'].concat(group.substr(1).split('/')).concat(name), {}); + entry.url = + _ioprocessors['http'].location + model.relDir + model.strippedName.split(pathDelim).join('/') + '.png'; + entry.path = model.relDir + model.strippedName.split(pathDelim).join('/') + '.png'; + } + return struct; + } + + // check whether a given string represents a number + function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + // allow to set deep keys in an object + function assign(obj, path, value) { + if (typeof path === 'string') + path = path.split('.'); + if (!(path instanceof Array)) + return undefined; + + lastKeyIndex = path.length-1; + for (var i = 0; i < lastKeyIndex; ++ i) { + key = path[i]; + if (key.length == 0) + continue; + if (!(key in obj)) + obj[key] = {} + obj = obj[key]; + } + obj[path[lastKeyIndex]] = value; + return obj[path[lastKeyIndex]]; + } + </script> + <state id="main"> + <!-- Stop processing if no vrml-path was given on command line --> + <transition target="final" cond="_x['args']['vrml-path'] == undefined || _x['args']['vrml-path'].length == 0"> + <log expr="'No --vrml-path given'" /> + </transition> + + <!-- Stop processing if no tmp-path was given on command line --> + <transition target="final" cond="_x['args']['tmp-path'] == undefined || _x['args']['tmp-path'].length == 0"> + <log expr="'No --tmp-path given'" /> + </transition> + + <!-- Stop processing if any error occurs --> + <transition target="final" event="error"> + <log expr="'An error occured:'" /> + <script>dump(_event);</script> + </transition> + + <!-- Start the directory monitor for generated files --> + <invoke type="dirmon" id="dirmon.processed"> + <param name="dir" expr="_x['args']['tmp-path']" /> + <param name="recurse" expr="false" /> + <param name="reportExisting" expr="true" /> + <!-- Called for every file we found --> + <finalize> + <script> + _event.fileStruct = fileToStruct(_event.data.file); + + if (_event.data.file.extension === "osgb") { + // this is a binary 3D file converted from the wrls + + if (_event.name === "file.deleted") { + delete models[_event.fileStruct.key]; + print("Removed a vanished osgb file at " + _event.fileStruct.key + "\n"); + } else { + models[_event.fileStruct.key] = _event.data.file; + models[_event.fileStruct.key].group = '/' + _event.data.file.name.split(pathDelim).slice(0,-1).join('/'); + print("Inserted a new osgb file at " + _event.fileStruct.key + "\n"); + } + + } else if ('format' in _event.fileStruct) { + // this is a processed file generated for some request + + if (_event.name === "file.deleted") { + delete processed[_event.fileStruct.key][_event.fileStruct.format]; + print("Removed a vanished processed file at " + _event.fileStruct.key + "\n"); + } else { + if (!(_event.fileStruct.key in processed)) { + processed[_event.fileStruct.key] = {} + } + processed[_event.fileStruct.key][_event.fileStruct.format] = _event.data.file; + print("Inserted a new processed file at " + _event.fileStruct.key + "\n"); + } + } else { + print("Ignoring " + _event.data.file.name + "\n"); + } + </script> + </finalize> + </invoke> + + <!-- Start the directory monitor for wrl files --> + <invoke type="dirmon" id="dirmon.vrml"> + <param name="dir" expr="_x['args']['vrml-path']" /> + <param name="recurse" expr="true" /> + <param name="suffix" expr="'vrml wrl'" /> + <finalize> + <script> + _event.fileStruct = fileToStruct(_event.data.file); + if (_event.name === "file.existing" || _event.name === "file.added") { + wrls[_event.fileStruct.key] = _event.data.file; + print("Inserting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.fileStruct.key + "\n"); + } + if (_event.name === "file.deleted") { + delete wrls[_event.fileStruct.key]; + print("Deleting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.fileStruct.key + "\n"); + } + </script> + <if cond="models && + (!(_event.fileStruct.key in models) || + wrls[_event.fileStruct.key].mtime > models[_event.fileStruct.key].mtime) + "> + <send target="#_osgvonvert.osgb"> + <param name="source" expr="_event.data.file.path" /> + <param name="dest" expr="_x['args']['tmp-path'] + '/' + _event.fileStruct.key + '.osgb'" /> + </send> + </if> + </finalize> + </invoke> + + <!-- Start the osgconvert invoker to transform 3D files --> + <invoke type="osgconvert" id="osgvonvert.osgb"> + <param name="threads" expr="4" /> + <!--finalize> + <script> + // we could put the file into the osgbs or processed here, but we rely on the directory monitors for now + print("Received " + _event.name + " regarding " + _event.data.dest + "\n"); + </script> + </finalize--> + </invoke> + + <!-- Idle here --> + <state id="idle"> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length >= 2 && + _event.data.pathComponent[_event.data.pathComponent.length - 1].indexOf('.') !== -1"> + <!-- request for a specific format + http://host/vrml/relative/path/format?query=string --> + <script> + _event.fileStruct = reqToStruct(_event.data); + _event.dest = _x['args']['tmp-path'] + '/' + _event['fileStruct'].key + '-' + _event['fileStruct'].format; + + print("Got a request for [" + _event['fileStruct'].key + '-' + _event['fileStruct'].format + "]\n"); +// dump(_event); + </script> + <if cond="_event['fileStruct'].key in models && isSupportedFormat(_event['fileStruct'].ext)"> + <!-- There is such a file available as osgb --> + <if cond=" + _event['fileStruct'].key in processed && + _event['fileStruct'].format in processed[_event['fileStruct'].key]"> + <script> + //print("Sending " + processed[_event['fileStruct'].key][_event['fileStruct'].format].path + "\n"); + </script> + <response status="200" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content fileexpr="processed[_event['fileStruct'].key][_event['fileStruct'].format].path" /> + </response> + <else> + <if cond="_event.name.endsWith('postponed')"> + <!-- + A postponed event we couldn't answer + --> + <response status="404" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + </response> + <else> + <script> + print("Processing outfile " + _event['dest'] + " from model " + _event['file'] + "\n"); + </script> + <send target="#_osgvonvert.osgb"> + <param name="source" expr="models[_event['fileStruct'].key].path" /> + <param name="dest" expr="_event['dest']" /> + <param name="pitch" expr="_event.fileStruct.pitch" /> + <param name="roll" expr="_event.fileStruct.roll" /> + <param name="yaw" expr="_event.fileStruct.yaw" /> + <param name="zoom" expr="_event.fileStruct.zoom" /> + <param name="x" expr="_event.fileStruct.x" /> + <param name="y" expr="_event.fileStruct.y" /> + <param name="z" expr="_event.fileStruct.z" /> + <param name="width" expr="_event.fileStruct.width" /> + <param name="height" expr="_event.fileStruct.height" /> + <param name="autorotate" expr="_event.fileStruct.autorotate" /> + </send> + <!-- + Redeliver the event once the untilexpr is true. The untilexpr has to evaluate + into another valid expression that we will check again on stable configurations. + --> + <postpone + untilexpr=" + '\'' + _event['fileStruct'].key + '\' in processed && + \'' + _event['fileStruct'].format + '\'' + ' in processed[\'' + _event['fileStruct'].key + '\'] || + _event.name === \'convert.failure\' && _event.data.dest === \'' + _event['dest'] + '\'' + "/> + </else> + </if> + </else> + </if> + <else> + <!-- There is no such model --> + <response status="404" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + </response> + </else> + </if> + </transition> + + <!-- + process request for JSON datastructures + --> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'models'"> + <script>//dump(_event)</script> + <response status="200" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="models" /> + </response> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'processed'"> + <script>//dump(_event)</script> + <response status="200" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="processed" /> + </response> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'wrls'"> + <script>//dump(_event)</script> + <response status="200" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="wrls" /> + </response> + </transition> + + <!-- request for topmost list of all files --> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 1"> + <script>//dump(_event);</script> + <response status="200" requestexpr="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="overviewList()" /> + </response> + </transition> + + <!-- XHR CORS preflight response --> + <transition event="http.options" target="idle"> + <script>dump(_event);</script> + <response status="200" requestexpr="_event.origin"> + <header name="Access-Control-Allow-Origin" value="*" /> + <header name="Access-Control-Allow-Methods" value="GET, OPTIONS" /> + <header name="Access-Control-Allow-Headers" value="X-Requested-With" /> + </response> + </transition> + + </state> + </state> + <state id="final" final="true" /> +</scxml>
\ No newline at end of file |