diff options
author | Stefan Radomski <radomski@tk.informatik.tu-darmstadt.de> | 2013-09-18 15:39:30 (GMT) |
---|---|---|
committer | Stefan Radomski <radomski@tk.informatik.tu-darmstadt.de> | 2013-09-18 15:39:30 (GMT) |
commit | 8dde1311719b29c63efb379566916cb1aa9a7cd7 (patch) | |
tree | 6849ab145936ea5a2bebee5b64e69c4d226c3810 /apps/samples/vrml | |
parent | 7938e286967597c7168b855b7e3fdfbd9b949e0e (diff) | |
download | uscxml-8dde1311719b29c63efb379566916cb1aa9a7cd7.zip uscxml-8dde1311719b29c63efb379566916cb1aa9a7cd7.tar.gz uscxml-8dde1311719b29c63efb379566916cb1aa9a7cd7.tar.bz2 |
Work on FFMpegInvoker
Diffstat (limited to 'apps/samples/vrml')
-rw-r--r-- | apps/samples/vrml/ffmpeg-server.scxml | 245 | ||||
-rw-r--r-- | apps/samples/vrml/img/close.png | bin | 0 -> 855 bytes | |||
-rw-r--r-- | apps/samples/vrml/img/pitchRoll-handle.png | bin | 0 -> 6142 bytes | |||
-rw-r--r-- | apps/samples/vrml/img/pitchRoll.pxm | bin | 0 -> 29076 bytes | |||
-rw-r--r-- | apps/samples/vrml/img/xy-handle.png | bin | 0 -> 4530 bytes | |||
-rw-r--r-- | apps/samples/vrml/img/xy.pxm | bin | 0 -> 29076 bytes | |||
-rw-r--r-- | apps/samples/vrml/img/yawZoom-handle.png | bin | 0 -> 5603 bytes | |||
-rw-r--r-- | apps/samples/vrml/img/yawZoom.pxm | bin | 0 -> 29076 bytes | |||
-rwxr-xr-x | apps/samples/vrml/stress-vrml-server.pl | 35 | ||||
-rw-r--r-- | apps/samples/vrml/viewer.html | 21 | ||||
-rw-r--r-- | apps/samples/vrml/viewer.js | 320 | ||||
-rw-r--r-- | apps/samples/vrml/vrml-server.scxml | 258 | ||||
-rw-r--r-- | apps/samples/vrml/vrml-server.scxml.old | 416 |
13 files changed, 1091 insertions, 204 deletions
diff --git a/apps/samples/vrml/ffmpeg-server.scxml b/apps/samples/vrml/ffmpeg-server.scxml new file mode 100644 index 0000000..1fd4038 --- /dev/null +++ b/apps/samples/vrml/ffmpeg-server.scxml @@ -0,0 +1,245 @@ +<scxml datamodel="ecmascript"> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" /> + + <script> +//<![CDATA[ + function clone(item) { + if (!item) { return item; } // null, undefined values check + + var types = [ Number, String, Boolean ], + result; + + // normalizing primitives if someone did new String('aaa'), or new Number('444'); + types.forEach(function(type) { + if (item instanceof type) { + result = type( item ); + } + }); + + if (typeof result == "undefined") { + if (Object.prototype.toString.call( item ) === "[object Array]") { + result = []; + item.forEach(function(child, index, array) { + result[index] = clone( child ); + }); + } else if (typeof item == "object") { + // testing that this is DOM + if (item.nodeType && typeof item.cloneNode == "function") { + var result = item.cloneNode( true ); + } else if (!item.prototype) { // check that this is a literal + if (item instanceof Date) { + result = new Date(item); + } else { + // it is an object literal + result = {}; + for (var i in item) { + result[i] = clone( item[i] ); + } + } + } else { + // depending what you would like here, + // just keep the reference, or create new object + if (false && item.constructor) { + // would not advice to do that, reason? Read below + result = new item.constructor(); + } else { + result = item; + } + } + } else { + result = item; + } + } + + return result; + } +//]]> + </script> + + <datamodel> + <data id="modelDir" /> + <data id="requests">{}</data> + </datamodel> + + <state id="start"> + <onentry> + <log expr="modelDir" /> + </onentry> + <invoke type="osgconvert" id="osgvonvert.frame"> + <param name="threads" expr="1" /> + <finalize> + <script> + requests[_event.data.context].job.renderedFrames++; + </script> + <!-- osgconverter will copy its send request for the reply so these are still available --> + <send target="#_ffmpeg" event="render.frame"> + <param name="frame" expr="_event.data.content.bmp" /> + <param name="context" expr="_event.data.context" /> + </send> + <if cond="requests[_event.data.context].job.renderedFrames >= requests[_event.data.context].job.totalFrames"> + <send target="#_ffmpeg" event="render.end"> + <param name="context" expr="_event.data.context" /> + </send> + </if> + </finalize> + </invoke> + + <invoke type="ffmpeg" id="ffmpeg"> + <finalize> + <script> +// dump(_event); + </script> + <send target="#_parent" event="render.done"> + <param name="context" expr="_event.data.context" /> + <param name="movie" expr="_event.data.movie" /> + <param name="mimetype" expr="_event.data.mimetype" /> + <param name="filename" expr="_event.data.filename" /> + </send> + <script> + // free up job in requests + requests[_event.data.context] = undefined; + </script> + + </finalize> + </invoke> + + <state id ="idle"> + <!-- <onentry> + <script> + print("Invokers:"); + dump(_invokers['ffmpeg']); + </script> + </onentry> --> + <transition event="http.post" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'movie'"> + <!-- + Something to encode just arrived. + Interpolate pose information and send each frame to the osg converter. + The finalize of the osgconverter will send the images to ffmpeg whose + finalize will trigger the response. + --> + <script> +//<![CDATA[ +// dump(_event); + var pathDelim = ':'; // we need to flatten directories - this will seperate them in filenames + + // store event + var job = {}; + job.event = _event; + job.framesPerSec = 25; + job.lengthInSec = _event.data.content.data.movieLength; + job.format = _event.data.content.data.format; + job.height = _event.data.content.data.height; + job.width = _event.data.content.data.width; + job.keyFrames = _event.data.content.data.frames; + job.frames = []; + job.totalFrames = job.lengthInSec * job.framesPerSec + job.renderedFrames = 0; + + // sanitize + if (job.height % 2) + job.height++; + if (job.width % 2) + job.width++; + + // calculate overall relative length + var totalRelLength = 0; + for (var index in job.keyFrames) { + totalRelLength += job.keyFrames[index].relFrameLength; + } + var framesToRelLength = job.totalFrames / totalRelLength; + + // create frames + for (var kfIndex = 0; job.keyFrames[kfIndex]; kfIndex++) { + var nextPose = false; + if (job.keyFrames[kfIndex + 1]) { + nextPose = job.keyFrames[kfIndex + 1].pose; + } + var keyFrame = job.keyFrames[kfIndex]; + + keyFrame.modelFile = keyFrame.imageURL.substr(keyFrame.serverURL.length + 1); + keyFrame.file = keyFrame.modelFile.substr(0, keyFrame.modelFile.indexOf('.')); + keyFrame.ext = keyFrame.modelFile.substr(keyFrame.modelFile.indexOf('.') + 1); + + keyFrame.modelFile = keyFrame.file + ".osgb"; + keyFrame.modelFile = keyFrame.modelFile.replace(/\//g, pathDelim); + keyFrame.modelFile = modelDir + '/' + keyFrame.modelFile; + + var currentPose = keyFrame.pose; + var thisFrames = Math.round(keyFrame.relFrameLength * framesToRelLength); + var startTransitionAt = Math.round((100 - keyFrame.relTransitionLength) * 0.01 * thisFrames); + var transitionFrames = thisFrames - startTransitionAt; + + //print("---------------------\n"); + //print("lengthInSec: " + job.lengthInSec + "\n"); + //print("totalFrames: " + job.totalFrames + "\n"); + //print("thisFrames for " + kfIndex + ": " + thisFrames + "\n"); + //print("startTransitionAt for " + kfIndex + ": " + startTransitionAt + "\n"); + //print("transitionFrames for " + kfIndex + ": " + transitionFrames + "\n"); + + for (var frameIndex = 0; frameIndex < thisFrames && job.frames.length <= job.totalFrames; frameIndex++) { + var frame = clone(keyFrame); + + if (frameIndex > startTransitionAt && nextPose) { + + var transitionProgress = (frameIndex - startTransitionAt) / transitionFrames; + frame.pose.pitch += (nextPose.pitch - frame.pose.pitch) * transitionProgress; + frame.pose.roll += (nextPose.roll - frame.pose.roll) * transitionProgress; + frame.pose.yaw += (nextPose.yaw - frame.pose.yaw) * transitionProgress; + frame.pose.x += (nextPose.x - frame.pose.x) * transitionProgress; + frame.pose.y += (nextPose.y - frame.pose.y) * transitionProgress; + frame.pose.z += (nextPose.z - frame.pose.z) * transitionProgress; + frame.pose.zoom += (nextPose.zoom - frame.pose.zoom) * transitionProgress; + + //print("#########################\n"); + //print("Transition at: " + frameIndex + " \ Progress: " + transitionProgress + "\n"); + //print("Interpolated pose:\n") + //dump(frame.pose); + } + + job.frames.push(frame); + } + } + requests[_event.origin] = {}; + requests[_event.origin].job = job; +//]]> + </script> + <send target="#_ffmpeg" event="render.start" idlocation="requests[_event.origin].sendId"> + <param name="context" expr="_event.origin" /> + <param name="format" expr="requests[_event.origin].job.format" /> + <param name="width" expr="requests[_event.origin].job.width" /> + <param name="height" expr="requests[_event.origin].job.height" /> + </send> + <foreach array="requests[_event.origin].job.frames" item="frame" index="index"> + <send target="#_osgvonvert.frame"> + <param name="format" expr="'bmp'" /> + <!-- param name="dest" expr="'/Users/sradomski/Desktop/ctrl/' + index + '.bmp'" / --> + <param name="source" expr="frame.modelFile" /> + <param name="pitch" expr="frame.pose.pitch" /> + <param name="roll" expr="frame.pose.roll" /> + <param name="yaw" expr="frame.pose.yaw" /> + <param name="zoom" expr="frame.pose.zoom" /> + <param name="x" expr="frame.pose.x" /> + <param name="y" expr="frame.pose.y" /> + <param name="z" expr="frame.pose.z" /> + <param name="width" expr="requests[_event.origin].job.width" /> + <param name="height" expr="requests[_event.origin].job.height" /> + <param name="autorotate" expr="frame.pose.autorotate" /> + <param name="context" expr="_event.origin" /> + </send> + </foreach> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 3 && + _event.data.pathComponent[1] === 'movie' && + _event.data.pathComponent[2] === 'codecs'"> + <send target="#_parent" event="send.codecs"> + <param name="context" expr="_event.origin" /> + <param name="codecs" expr="_invokers['ffmpeg']" /> + </send> + </transition> + </state> + </state> +</scxml>
\ No newline at end of file diff --git a/apps/samples/vrml/img/close.png b/apps/samples/vrml/img/close.png Binary files differnew file mode 100644 index 0000000..7233cbe --- /dev/null +++ b/apps/samples/vrml/img/close.png diff --git a/apps/samples/vrml/img/pitchRoll-handle.png b/apps/samples/vrml/img/pitchRoll-handle.png Binary files differnew file mode 100644 index 0000000..fcff0dd --- /dev/null +++ b/apps/samples/vrml/img/pitchRoll-handle.png diff --git a/apps/samples/vrml/img/pitchRoll.pxm b/apps/samples/vrml/img/pitchRoll.pxm Binary files differnew file mode 100644 index 0000000..1dbc3e2 --- /dev/null +++ b/apps/samples/vrml/img/pitchRoll.pxm diff --git a/apps/samples/vrml/img/xy-handle.png b/apps/samples/vrml/img/xy-handle.png Binary files differnew file mode 100644 index 0000000..0edb8cc --- /dev/null +++ b/apps/samples/vrml/img/xy-handle.png diff --git a/apps/samples/vrml/img/xy.pxm b/apps/samples/vrml/img/xy.pxm Binary files differnew file mode 100644 index 0000000..5e077ef --- /dev/null +++ b/apps/samples/vrml/img/xy.pxm diff --git a/apps/samples/vrml/img/yawZoom-handle.png b/apps/samples/vrml/img/yawZoom-handle.png Binary files differnew file mode 100644 index 0000000..f6a54ee --- /dev/null +++ b/apps/samples/vrml/img/yawZoom-handle.png diff --git a/apps/samples/vrml/img/yawZoom.pxm b/apps/samples/vrml/img/yawZoom.pxm Binary files differnew file mode 100644 index 0000000..ec00b18 --- /dev/null +++ b/apps/samples/vrml/img/yawZoom.pxm diff --git a/apps/samples/vrml/stress-vrml-server.pl b/apps/samples/vrml/stress-vrml-server.pl new file mode 100755 index 0000000..4145f29 --- /dev/null +++ b/apps/samples/vrml/stress-vrml-server.pl @@ -0,0 +1,35 @@ +#!/usr/bin/perl -w + +use Math::Round; + +my $pi = 3.14159; + +# make one thousand requests with random parameters +for (my $i = 0; $i < 1000; $i++) { + my $pitch = rand(2*$pi); + my $roll = rand(2*$pi); + my $yaw = rand(2*$pi); + my $width = rand(400) + 200; + my $height = rand(400) + 200; + my $url = "http://epikur.local:8080/vrml/HARD_MP_VAL_002.png". + "?pitch=".sprintf("%.3f",$pitch). + "&roll=".sprintf("%.3f",$roll). + "&yaw=".sprintf("%.3f",$yaw). + "&width=".sprintf("%.0f",$width). + "&height=".sprintf("%.0f",$height); + print $url."\n"; + `wget '$url'`; +} + +# for (my $pitch = 0; $pitch < 2*$pi; $pitch += 0.01) { +# for (my $roll = 0; $roll < 2*$pi; $roll += 0.01) { +# for (my $yaw = 0; $yaw < 2*$pi; $yaw += 0.01) { +# my $url = "http://epikur.local:8081/vrml/HARD_MP_VAL_002.png". +# "?pitch=".sprintf("%.3f",$pitch). +# "&roll=".sprintf("%.3f",$roll). +# "&yaw=".sprintf("%.3f",$yaw); +# print $url."\n"; +# `wget '$url'`; +# } +# } +# }
\ No newline at end of file diff --git a/apps/samples/vrml/viewer.html b/apps/samples/vrml/viewer.html index 9838c33..6e139e7 100644 --- a/apps/samples/vrml/viewer.html +++ b/apps/samples/vrml/viewer.html @@ -22,7 +22,22 @@ height:200px; padding:7px; } - </style> + .tundra .dijitTooltipContainer { + background-color:rgba(200,200,200,0.5); + background:rgba(200,200,200,0.5); + } +/* .removeThumb { + background-image: url(img/close.png); + background-repeat: no-repeat; + background-size: 100%; + text-align: center; + border: 0px; + width: 20px; + height: 20px; + vertical-align: top; + margin: -3px 0px 0px -8px; + } +*/ </style> <script type="text/javascript"> // dojoConfig = { @@ -51,8 +66,8 @@ "height" : 400, "autorotate" : false }, - "serverURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml", - "imageURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml/hook/HARD_MP_VAL_005.png" +// "serverURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml", +// "imageURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml/hook/HARD_MP_VAL_005.png" }); var viewer2 = new VRMLViewer("scene2"); // var annotations = new Annotations("annotations1", { 'viewer': viewer }); diff --git a/apps/samples/vrml/viewer.js b/apps/samples/vrml/viewer.js index 4cc167c..583f06f 100644 --- a/apps/samples/vrml/viewer.js +++ b/apps/samples/vrml/viewer.js @@ -35,6 +35,9 @@ function VRMLViewer(element, params) { this.updateScene = function() { if (self.imageURL && !self.batchChanges) { self.imgElem.src = self.imageURL + urlSuffixForPose(self.pose); + // we are showing an image, activate additional controls + self.movieAddButton.domNode.style.display = ""; + self.movieDropDown.domNode.style.display = ""; } }; @@ -67,18 +70,52 @@ function VRMLViewer(element, params) { return {x: lx,y: ly}; }; + this.populateMovieCodecs = function(server, selectElem) { + self.xhr.get({ + // The URL to request + url: server, + handleAs:"json", + headers:{"X-Requested-With":null}, + load: function(result) { + for (var codec in result.video) { + if (codec !== "mpeg1video" && + codec !== "mpeg2video" && + codec !== "mpeg4" && + codec !== "h264" && + codec !== "ayuv" && + codec !== "flashsv" && + codec !== "flashsv2" && + codec !== "flv" && + codec !== "rv40" && + codec !== "theora" && + codec !== "v210" && + codec !== "v308" && + codec !== "v408" && + codec !== "v410" && + codec !== "wmv3" && + codec !== "y41p" && + codec !== "yuv4") + continue; + selectElem.options.push({ label: result.video[codec].longName, value: codec }); + if (codec === "mpeg4") + selectElem[selectElem.options.length - 1].selected = true; + } + } + }); + } + this.refreshServer = function(server) { self.serverURL = server; self.localStorage.put("vrmlServer", self.serverURL, null); - self.progressElem.appendChild(self.progress.domNode); - self.progress.start(); +// self.progressElem.appendChild(self.progress.domNode); +// self.progress.start(); self.xhr.get({ // The URL to request url: server, handleAs:"json", headers:{"X-Requested-With":null}, load: function(result) { - self.progress.stop(); +// self.progress.stop(); for (id in self.fileStore.query) { self.fileStore.remove(id); } @@ -140,7 +177,10 @@ function VRMLViewer(element, params) { "dijit/TooltipDialog", "dojo/dnd/Moveable", "dojo/ready", - "dojo/dnd/Source"], + "dojo/dnd/Source", + "dijit/form/HorizontalSlider", + "dijit/form/Select", + "dijit/form/NumberSpinner"], function(domConst, xhr, dom, @@ -157,7 +197,10 @@ function VRMLViewer(element, params) { TooltipDialog, Moveable, ready, - Source) { + Source, + HorizontalSlider, + Selector, + NumberSpinner) { ready(function() { @@ -185,7 +228,11 @@ function VRMLViewer(element, params) { </div>\ <div style="position: absolute; left: 10px; top: 10px">\ <table></tr>\ - <td class="browseDropDown" style="vertical-align: middle"></td>\ + <td class="filesDropDown" style="vertical-align: middle"></td>\ + <td>\ + <div class="movieDropDown" style="display: inline"></div>\ + <button type="button" class="movieAddButton"></button>\ + </td>\ <td align="right"><button type="button" class="resetButton"></button></td>\ <td class="dragHandler" style="vertical-align: middle; padding-top: 4px;"></td>\ </tr></table>\ @@ -193,31 +240,33 @@ function VRMLViewer(element, params) { <div style="position: absolute; right: 10px; top: 15%; height: 50%">\ <div class="zoomSlide"></div>\ </div>\ - <div style="position: absolute; right: 55%; top: 48%">\ + <div style="position: absolute; right: 50%; top: 50%">\ <div class="pitchRollHandler" style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px;">\ <table>\ <tr>\ - <td><img class="pitchRollHandlerImg" src="' + self.resRoot + 'img/pitchRoll.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ + <td><img class="pitchRollHandlerImg" src="' + self.resRoot + 'img/pitchRoll-handle.png" height="20px" style="padding: 2px 0px 0px 4px;" /></td>\ <td><div class="pitchLabel"></div><div class="rollLabel"></div></td>\ </tr>\ </table>\ </div>\ </div>\ - <div style="position: absolute; right: 45%; top: 48%">\ - <div class="yawZoomHandler" style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px;">\ - <table>\ - <tr>\ - <td><img class="yawZoomHandlerImg" src="' + self.resRoot + 'img/yawZoom.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ - <td><div class="yawLabel"></div><div class="zoomLabel"></div></td>\ - </tr>\ - </table>\ + <div style="position: absolute; right: 50%; top: 50%">\ + <div class="yawZoomHandler">\ + <div style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px; position: absolute; left: -34px;">\ + <table>\ + <tr>\ + <td><img class="yawZoomHandlerImg" src="' + self.resRoot + 'img/yawZoom-handle.png" height="20px" style="padding: 2px 0px 0px 4px;" /></td>\ + <td><div class="yawLabel"></div><div class="zoomLabel"></div></td>\ + </tr>\ + </table>\ + </div>\ </div>\ </div>\ - <div style="position: absolute; right: 50%; top: 58%">\ + <div style="position: absolute; right: 50%; top: 50%">\ <div class="xyHandler" style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px;">\ <table>\ <tr>\ - <td><img class="xyHandlerImg" src="' + self.resRoot + 'img/xy.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ + <td><img class="xyHandlerImg" src="' + self.resRoot + 'img/xy-handle.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ <td><div class="xLabel"></div><div class="yLabel"></div></td>\ </tr>\ </table>\ @@ -237,7 +286,9 @@ function VRMLViewer(element, params) { // fetch special dom nodes for content self.messageBox = dojo.query("div.messages", element)[0]; self.imgElem = dojo.query("img.model", element)[0]; - self.browseDropDownElem = dojo.query("td.browseDropDown", element)[0]; + self.filesDropDownElem = dojo.query("td.filesDropDown", element)[0]; + self.movieDropDownElem = dojo.query("div.movieDropDown", element)[0]; + self.movieAddButtonElem = dojo.query("button.movieAddButton", element)[0]; self.resetButtonElem = dojo.query("button.resetButton", element)[0]; self.progressElem = dojo.query("div.progress", element)[0]; @@ -266,10 +317,18 @@ function VRMLViewer(element, params) { var rollLabel = dojo.query(".rollLabel", mover.node)[0]; var offset = moverRelativeTo(handlerImg, self.element); + offset.x += 30; + offset.y += 20; + + self.xyHandlerElem.style.zIndex = 1; + self.yawZoomHandlerElem.style.zIndex = 1; + self.pitchRollHandlerElem.style.zIndex = 2; + // self.pose.pitch = self.pose.pitch % (2 * 3.14159); // self.pose.roll = self.pose.roll % (2 * 3.14159); self.pose.roll = offset.x / self.pose.width - 0.5; self.pose.pitch = offset.y / self.pose.height - 0.5; + self.pose.pitch *= -1; self.pose.roll *= 2 * 3.14159; self.pose.pitch *= 2 * 3.14159; self.pose.roll = Math.ceil((self.pose.roll) * 10) / 10; @@ -295,6 +354,13 @@ function VRMLViewer(element, params) { var zoomLabel = dojo.query("div.zoomLabel", mover.node)[0]; var offset = moverRelativeTo(handlerImg, self.element); + offset.x += 7; + offset.y += 9; + + self.xyHandlerElem.style.zIndex = 1; + self.yawZoomHandlerElem.style.zIndex = 2; + self.pitchRollHandlerElem.style.zIndex = 1; + // self.pose.pitch = self.pose.pitch % (2 * 3.14159); // self.pose.roll = self.pose.roll % (2 * 3.14159); self.pose.yaw = (self.pose.width - offset.x) / self.pose.width - 0.5; @@ -324,6 +390,13 @@ function VRMLViewer(element, params) { var yLabel = dojo.query("div.yLabel", mover.node)[0]; var offset = moverRelativeTo(handlerImg, self.element); + offset.x += 3; + offset.y += 13; + + self.xyHandlerElem.style.zIndex = 2; + self.yawZoomHandlerElem.style.zIndex = 1; + self.pitchRollHandlerElem.style.zIndex = 1; + self.pose.x = offset.x / self.pose.width - 0.5; self.pose.y = offset.y / self.pose.height - 0.5; self.pose.x *= 100; @@ -348,7 +421,7 @@ function VRMLViewer(element, params) { item.imageURL = self.imageURL; item.serverURL = self.serverURL; item.pose = avatarPose; - return {node: avatar, data: item, type: item.type}; + return {node: avatar, data: item, type: item.type}; } var handler = dojo.create( 'div', { innerHTML: '<img src="' + self.resRoot + 'img/drag.png" width="20px" />' }); return {node: handler, data: item, type: item.type}; @@ -375,7 +448,7 @@ function VRMLViewer(element, params) { model: self.fileTreeModel, persist: false, showRoot: false, - style: "height: 200px;", + style: "height: 300px;", onClick: function(item){ if ('url' in item) { self.imageURL = item.url; @@ -405,7 +478,7 @@ function VRMLViewer(element, params) { self.serverBox = new TextBox({ name: "Server", value: self.serverURL, - style: "width: 70%", + style: "width: 65%", onKeyDown: function(e) { var code = e.keyCode || e.which; @@ -424,14 +497,203 @@ function VRMLViewer(element, params) { } }); - self.browseDropDownContent = domConst.toDom('<div />'); - self.browseDropDownContent.appendChild(self.serverBox.domNode); - self.browseDropDownContent.appendChild(self.browseButton.domNode); - self.browseDropDownContent.appendChild(self.fileList.domNode); + self.filesDropDownContent = domConst.toDom('<div />'); + self.filesDropDownContent.appendChild(self.serverBox.domNode); + self.filesDropDownContent.appendChild(self.browseButton.domNode); + self.filesDropDownContent.appendChild(self.fileList.domNode); + + self.filesToolTip = new TooltipDialog({ content:self.filesDropDownContent, style:"max-height:320px"}); + self.filesDropDown = new DropDownButton({ label: "Files", dropDown: self.filesToolTip }); + self.filesDropDownElem.appendChild(self.filesDropDown.domNode); + + self.movieDropDownContent = domConst.toDom('<div style="overflow: auto; max-height: 420px;" />'); + + self.createMovieThumb = function(item, mode) { + if (mode == 'avatar') { + // when dragged + var avatar = dojo.create( 'div', { innerHTML: item.data }); + var avatarPose = dojo.clone(self.pose); + avatarPose.width=60; + avatarPose.height=60; + var avatarImgUrl = urlSuffixForPose(avatarPose); + avatar.innerHTML = '<img src=' + self.imageURL + avatarImgUrl + ' /> '; + item.srcEcc = "VRMLViewer"; + item.iconPoseUrl = self.imageURL + avatarImgUrl; + item.imageURL = self.imageURL; + item.serverURL = self.serverURL; + item.pose = avatarPose; + return {node: avatar, data: item, type: item.type}; + } else { + // var thumb = dojo.create( 'div', { innerHTML: item.data }); + + // when added to list + var thumb = domConst.toDom("\ + <div>\ + <table><tr><td>\ + <img class=\"movieThumb\"/>\ + <img class=\"removeThumb\" style=\"vertical-align: top; margin: -3px 0px 0px -8px; width: 20px; height: 20px;\"/>\ + </td><td align=\"left\">\ + <table><tr>\ + <td>Frame:</td><td><div class=\"relFrameLength\"/></td>\ + </tr><tr>\ + <td>Transition:</td><td><div class=\"relTransitionLength\"/></td>\ + </tr></table>\ + </td></tr></table>\ + </div>\ + "); + thumb = dojo.query("div", thumb)[0]; + + var thumbImgElem = dojo.query("img.movieThumb", thumb)[0]; + var removeImgElem = dojo.query("img.removeThumb", thumb)[0]; + var relFrameLengthElem = dojo.query("div.relFrameLength", thumb)[0]; + var relTransitionLengthElem = dojo.query("div.relTransitionLength", thumb)[0]; + + item.relFrameLengthSlider = new HorizontalSlider({ + value: 50, + title: "Relative Duration of Frame", + style: "width:150px;" + }, relFrameLengthElem); + + item.relTransitionLengthSlider = new HorizontalSlider({ + value: 80, + title: "Relative Duration of Transition", + style: "width:150px;" + }, relTransitionLengthElem); + + removeImgElem.onclick = function() { + self.addToMovieHandler.forInItems(function(obj, key, ctx) { + if (obj.data === item) { + // haha - what a mess! + self.addToMovieHandler.selectNone(); + self.addToMovieHandler.selection[key] = obj; + self.addToMovieHandler.deleteSelectedNodes(); + } + }); + } + removeImgElem.src = self.resRoot + "img/close.png"; + + var thumbPose = dojo.clone(self.pose); + thumbPose.width = self.pose.width / 10; + thumbPose.height = self.pose.height / 10; + var thumbImgUrl = urlSuffixForPose(thumbPose); + + thumbImgElem.src = self.imageURL + thumbImgUrl; + // removeImgElem.src = self.resRoot + 'img/close.png'; + + item.srcEcc = "VRMLViewer"; + item.iconPoseUrl = self.imageURL + thumbImgUrl; + item.imageURL = self.imageURL; + item.serverURL = self.serverURL; + item.pose = thumbPose; + + return {node: thumb, data: item, type: item.type}; + } + }; + + self.addToMovieHandler = new Source(self.movieDropDownContent, {copyOnly: true, creator: self.createMovieThumb}); + + self.movieFormatSelection = new Selector({ + name: "movieFormat", + options: [] + }); + self.populateMovieCodecs(self.serverURL + '/movie/codecs', self.movieFormatSelection); + + self.movieDropDownContent.appendChild(dojo.create( 'div', { + innerHTML: "Format: ", + style: "margin-right: 1em; margin-left: 0.2em; display:inline;" + })); + self.movieDropDownContent.appendChild(self.movieFormatSelection.domNode); + + self.movieDurationSpinner = new NumberSpinner({ + value: 10, + smallDelta: 1, + style: "width: 40px", + constraints: { min:0, places:0 }, + }); + self.movieDropDownContent.appendChild(self.movieDurationSpinner.domNode); + self.movieDropDownContent.appendChild(dojo.create( 'div', { + innerHTML: "sec ", + style: "margin-right: 1em; margin-left: 0.2em; display:inline;" + })); + + self.movieHeightSpinner = new NumberSpinner({ + value: 400, + smallDelta: 1, + style: "width: 60px", + constraints: { min:40, places:0 }, + }); + self.movieDropDownContent.appendChild(self.movieHeightSpinner.domNode); + self.movieDropDownContent.appendChild(dojo.create( 'div', { + innerHTML: "x", + style: "margin-right: 0.5em; margin-left: 0.5em; display:inline;" + })); + + self.movieWidthSpinner = new NumberSpinner({ + value: 600, + smallDelta: 1, + style: "width: 60px", + constraints: { min:40, places:0 }, + }); + self.movieDropDownContent.appendChild(self.movieWidthSpinner.domNode); + + self.movieCreateButton = new Button({ + label: "Create", + onClick: function(){ + var form = document.createElement("form"); + + form.setAttribute("method", "post"); + form.setAttribute("action", self.serverURL + "/movie"); + + var submitData = {}; + submitData.frames = []; + submitData.movieLength = self.movieDurationSpinner.value; + submitData.format = self.movieFormatSelection.value; + submitData.width = self.movieWidthSpinner.value; + submitData.height = self.movieHeightSpinner.value; + + self.addToMovieHandler.forInItems(function(obj, key, ctx) { + var jsonData = { + iconPoseUrl: obj.data.iconPoseUrl, + imageURL: obj.data.imageURL, + serverURL: obj.data.serverURL, + pose: obj.data.pose, + relFrameLength: obj.data.relFrameLengthSlider.value, + relTransitionLength: obj.data.relTransitionLengthSlider.value, + } + submitData.frames.push(jsonData); + }); + + var hiddenField = document.createElement("input"); + hiddenField.setAttribute("type", "hidden"); + hiddenField.setAttribute("name", "data"); + hiddenField.setAttribute("value", JSON.stringify(submitData)); + + form.appendChild(hiddenField); + + document.body.appendChild(form); + form.submit(); + document.body.removeChild(form); + } + }); + self.movieDropDownContent.appendChild(self.movieCreateButton.domNode); + + + self.movieToolTip = new TooltipDialog({ content:self.movieDropDownContent }); + self.movieDropDown = new DropDownButton({ + label: "Movie", + style: "display: none;", + dropDown: self.movieToolTip }); + self.movieDropDownElem.appendChild(self.movieDropDown.domNode); + + self.movieAddButton = new Button({ + label: "+", + style: "margin-left: -10px; display: none;", + onClick: function(){ + // we could pass item.data here to creator + self.addToMovieHandler.insertNodes(false, [ { } ]); + } + }, self.movieAddButtonElem); - self.browseToolTip = new TooltipDialog({ content:self.browseDropDownContent, style:"max-height:200px"}); - self.browseDropDown = new DropDownButton({ label: "Files", dropDown: self.browseToolTip }); - self.browseDropDownElem.appendChild(self.browseDropDown.domNode); self.resetButton = new Button({ label: "Reset", diff --git a/apps/samples/vrml/vrml-server.scxml b/apps/samples/vrml/vrml-server.scxml index 8563cb9..20d63a6 100644 --- a/apps/samples/vrml/vrml-server.scxml +++ b/apps/samples/vrml/vrml-server.scxml @@ -5,55 +5,11 @@ <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 + * Transform a http request into a pose */ function reqToStruct(req) { var struct = {}; @@ -80,21 +36,14 @@ 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 keyForFile(file) { + var key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.strippedName; + return key; + } + function isSupportedFormat(extension) { if (extension === "gif") return true; @@ -176,38 +125,21 @@ <param name="dir" expr="_x['args']['tmp-path']" /> <param name="recurse" expr="false" /> <param name="reportExisting" expr="true" /> + <param name="suffix" expr="'osgb'" /> <!-- 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"); - } + var key = keyForFile(_event.data.file); + // this is a binary 3D file converted from the wrls + + if (_event.name === "file.deleted") { + delete models[key]; + print("Removed a vanished osgb file at " + _event.fileStruct.key + "\n"); } else { - print("Ignoring " + _event.data.file.name + "\n"); + models[key] = _event.data.file; + models[key].group = '/' + _event.data.file.name.split(pathDelim).slice(0,-1).join('/'); + print("Inserted a new osgb file at " + key + "\n"); } </script> </finalize> @@ -220,23 +152,21 @@ <param name="suffix" expr="'vrml wrl'" /> <finalize> <script> - _event.fileStruct = fileToStruct(_event.data.file); + _event.key = keyForFile(_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"); + wrls[_event.key] = _event.data.file; + print("Inserting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.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"); + delete wrls[_event.key]; + print("Deleting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.key + "\n"); } </script> <if cond="models && - (!(_event.fileStruct.key in models) || - wrls[_event.fileStruct.key].mtime > models[_event.fileStruct.key].mtime) - "> + (!(_event.key in models) || wrls[_event.key].mtime > models[_event.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'" /> + <param name="dest" expr="_x['args']['tmp-path'] + '/' + _event.key + '.osgb'" /> </send> </if> </finalize> @@ -244,83 +174,59 @@ <!-- Start the osgconvert invoker to transform 3D files --> <invoke type="osgconvert" id="osgvonvert.osgb"> - <param name="threads" expr="4" /> - <!--finalize> + <param name="threads" expr="20" /> + <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"); + //dump(_event); </script> - </finalize--> + <if cond="'context' in _event.data"> + <!-- this was generated in reply to a request --> + <if cond="_event.name.endsWith('success')"> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="_event.data.content[_event.data.format]" /> + </respond> + <else /> + <respond status="404" to="_event.data.context"> + <header name="Connection" value="close" /> + </respond> + </if> + </if> + </finalize> </invoke> + <!-- Start a nested SCXML interpreter to create movies from the images --> + <invoke type="scxml" id="scxml.ffmpeg" src="ffmpeg-server.scxml" autoforward="true"> + <param name="modelDir" expr="_x['args']['tmp-path']" /> + </invoke> + <!-- Idle here --> <state id="idle"> - <!--onentry> - <log expr="_event" /> - </onentry --> <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 --> + <!-- 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)"> + <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> - <respond status="200" to="_event.origin"> - <header name="Connection" value="close" /> - <header name="Access-Control-Allow-Origin" value="*" /> - <content fileexpr="processed[_event['fileStruct'].key][_event['fileStruct'].format].path" /> - </respond> - <else /> - <if cond="_event.name.endsWith('postponed')"> - <!-- - A postponed event we couldn't answer - --> - <respond status="404" to="_event.origin"> - <header name="Connection" value="close" /> - </respond> - <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'] + '\'' - "/> - </if> - </if> + <send target="#_osgvonvert.osgb"> + <param name="source" expr="models[_event['fileStruct'].key].path" /> + <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" /> + <param name="context" expr="_event.origin" /> + </send> <else /> <!-- There is no such model --> <respond status="404" to="_event.origin"> @@ -346,18 +252,6 @@ <transition event="http.get" target="idle" cond=" _event.data.pathComponent.length == 2 && - _event.data.pathComponent[1] === 'processed'"> - <script>//dump(_event)</script> - <respond status="200" to="_event.origin"> - <header name="Connection" value="close" /> - <header name="Content-Type" value="application/json" /> - <header name="Access-Control-Allow-Origin" value="*" /> - <content expr="processed" /> - </respond> - </transition> - - <transition event="http.get" target="idle" cond=" - _event.data.pathComponent.length == 2 && _event.data.pathComponent[1] === 'wrls'"> <script>//dump(_event)</script> <respond status="200" to="_event.origin"> @@ -367,7 +261,7 @@ <content expr="wrls" /> </respond> </transition> - + <!-- request for topmost list of all files --> <transition event="http.get" target="idle" cond=" _event.data.pathComponent.length == 1"> @@ -382,14 +276,34 @@ <!-- XHR CORS preflight response --> <transition event="http.options" target="idle"> - <script>dump(_event);</script> + <script>//dump(_event);</script> <respond status="200" to="_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" /> </respond> </transition> - + + <transition event="render.done" target="idle"> + <script>//dump(_event);</script> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" valueexpr="_event.data.mimetype" /> + <header name="Content-Disposition" valueexpr="'attachment; filename=' + _event.data.filename" /> + <content expr="_event.data.movie" /> + </respond> + </transition> + + <transition event="send.codecs" target="idle"> + <script>//dump(_event);</script> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" valueexpr="_event.data.mimetype" /> + <header name="Content-Disposition" valueexpr="'attachment; filename=' + _event.data.filename" /> + <content expr="_event.data.codecs" /> + </respond> + </transition> + </state> </state> <state id="final" final="true" /> diff --git a/apps/samples/vrml/vrml-server.scxml.old b/apps/samples/vrml/vrml-server.scxml.old new file mode 100644 index 0000000..70a7c3a --- /dev/null +++ b/apps/samples/vrml/vrml-server.scxml.old @@ -0,0 +1,416 @@ +<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> + //dump(_event); + </script> + <!-- <file operation="write" contentexpr="_event.data.content" sandbox="off" urlexpr="_event.data.dest" /> --> + </finalize> + <!--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> + + <!-- Start a nested SCXML interpreter to create movies from the images --> + <!-- <invoke type="scxml" id="scxml.ffmpeg" src="ffmpeg-server.scxml" autoforward="true"> + <param name="modelDir" expr="_x['args']['tmp-path']" /> + </invoke> --> + + <!-- Idle here --> + <state id="idle"> + <!--onentry> + <log expr="_event" /> + </onentry --> + <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> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content fileexpr="processed[_event['fileStruct'].key][_event['fileStruct'].format].path" /> + </respond> + <else /> + <if cond="_event.name.endsWith('postponed')"> + <!-- + A postponed event we couldn't answer + --> + <respond status="404" to="_event.origin"> + <header name="Connection" value="close" /> + </respond> + <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'] + '\'' + "/> + </if> + </if> + <else /> + <!-- There is no such model --> + <respond status="404" to="_event.origin"> + <header name="Connection" value="close" /> + </respond> + </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> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="models" /> + </respond> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'processed'"> + <script>//dump(_event)</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="processed" /> + </respond> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'wrls'"> + <script>//dump(_event)</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="wrls" /> + </respond> + </transition> + + <!-- request for topmost list of all files --> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 1"> + <script>//dump(_event);</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="overviewList()" /> + </respond> + </transition> + + <!-- XHR CORS preflight response --> + <transition event="http.options" target="idle"> + <script>//dump(_event);</script> + <respond status="200" to="_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" /> + </respond> + </transition> + + <transition event="render.done" target="idle"> + <script>dump(_event);</script> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" valueexpr="_event.data.mimetype" /> + <header name="Content-Disposition" valueexpr="'attachment; filename=' + _event.data.filename" /> + <content expr="_event.data.movie" /> + </respond> + </transition> + + </state> + </state> + <state id="final" final="true" /> +</scxml>
\ No newline at end of file |