diff --git a/server.js b/server.js
new file mode 100644
index 0000000..dbc7817
--- /dev/null
+++ b/server.js
@@ -0,0 +1,2 @@
+process.chdir('./server');
+require('./server/server.js')();
diff --git a/server/controllers/demo/index.js b/server/controllers/demo/index.js
new file mode 100644
index 0000000..a11c829
--- /dev/null
+++ b/server/controllers/demo/index.js
@@ -0,0 +1,32 @@
+module.exports.demoConfig = function(req, res) {
+ res.setHeader('Content-Type', 'text/html');
+
+ res.render('demo-config.jade', res.locals, function(err, result) {
+ res.send(result);
+ });
+
+};
+
+module.exports.demo = function(req, res) {
+
+ res.setHeader('Content-Type', 'text/html');
+
+ switch (req.query.scene) {
+ case '2': res.locals.scene = 'L3D.initBobomb'; break;
+ case '3': res.locals.scene = 'L3D.initMountain'; break;
+ case '4': res.locals.scene = 'L3D.initWhomp'; break;
+ }
+
+ switch (req.query.bookmark) {
+ case '0': res.locals.bookmark = 'L3D.BaseRecommendation'; break;
+ case '1': res.locals.bookmark = 'L3D.ViewportRecommendation'; break;
+ case '2': res.locals.bookmark = 'L3D.ArrowRecommendation'; break;
+ }
+
+ res.locals.prefetch = req.query.prefetch;
+
+ res.render('demo.jade', res.locals, function(err, result) {
+ res.send(result);
+ });
+};
+
diff --git a/server/controllers/demo/urls.js b/server/controllers/demo/urls.js
new file mode 100644
index 0000000..70d2451
--- /dev/null
+++ b/server/controllers/demo/urls.js
@@ -0,0 +1,4 @@
+module.exports = {
+ '/demo-config': 'demoConfig',
+ '/demo': 'demo'
+};
diff --git a/server/controllers/demo/views/demo-config.jade b/server/controllers/demo/views/demo-config.jade
new file mode 100644
index 0000000..33e0ef8
--- /dev/null
+++ b/server/controllers/demo/views/demo-config.jade
@@ -0,0 +1,42 @@
+extends ../../../views/base.jade
+
+block extrahead
+ link(rel="stylesheet", href="/static/css/signin.css")
+
+block content
+ form#form.form-signin(method="GET", action='/demo')
+ h2 Set your parameters !
+ .form-group
+ label Select your scene
+ .form-group
+ input(type='radio', name='scene', value='2', style={'margin-right': '5px'}, checked)
+ | Bobomb scene
+ input(type='radio', name='scene', value='3', style={'margin-right': '5px'})
+ | Cool cool mountain scene
+ input(type='radio', name='scene', value='4', style={'margin-right': '5px'})
+ | Whomp scene
+
+ label Select your bookmarks
+ .form-group
+ input(type='radio', name='bookmark', value='0', style={'margin-right': '5px'}, checked)
+ | No bookmarks
+ input(type='radio', name='bookmark', value='1', style={'margin-right': '5px'})
+ | Viewports
+ input(type='radio', name='bookmark', value='2', style={'margin-right': '5px'})
+ | Arrows
+
+ label Select your prefetching policy
+ .form-group
+ input(type='radio', name='prefetch', value='NV-PN', style={'margin-right': '5px'}, checked)
+ | NV-PN : frustum / backface culling, no prefetch
+ input(type='radio', name='prefetch', value='V-PP', style={'margin-right': '5px'})
+ | V-PP : try to predict the next clicked bookmark and prefetch it
+ input(type='radio', name='prefetch', value='V-PD', style={'margin-right': '5px'})
+ | V-PD : only prefetch when clicking on a bookmark
+ input(type='radio', name='prefetch', value='V-PP+PD', style={'margin-right': '5px'})
+ | V-PP+PD : try to predict bookmark and prefetch on click
+
+ button.btn.btn-lg.btn-primary.btn-block(type="submit") Start playing !
+
+
+
diff --git a/server/controllers/demo/views/demo.jade b/server/controllers/demo/views/demo.jade
new file mode 100644
index 0000000..21d2c0f
--- /dev/null
+++ b/server/controllers/demo/views/demo.jade
@@ -0,0 +1,65 @@
+extends ../../../views/withjs
+
+block title
+ title #{title} - Prototype
+
+block prepend js
+ script BD_DISABLED = true;
+
+block extrajs
+ script(src="/static/js/l3dp.min.js")
+ block configjs
+ script.
+ initMainScene = #{scene};
+ Recommendation = #{bookmark};
+ prefetch = "#{prefetch}";
+ locked = #{session.locked === undefined ? 'true' : session.locked};
+ coinsId=[];
+
+ script(src="/static/js/prototypeinteractive.min.js")
+ script document.getElementById('music').volume = 0.5;
+
+
+block extrahead
+ link(rel="stylesheet" href="/static/css/prototype.css")
+
+block content
+ #main-div.panel-group(style={'margin-top':'10px', 'margin-bottom':'10px'})
+ block description
+ #alert-placeholder
+ .progress
+ .progress-bar.progress-bar-striped.active(role='progress-bar', aria-valuenow='0',aria-valuemin="0", aria-valuemax="100", style={width:'0%'})
+ #percentage 0%
+
+ div(style="margin:5px")
+ #container(style={'padding': '0px', 'margin': '0px'}, tabindex="1")
+
+ nav.navbar.navbar-default.navbar-fixed-bottom
+ .container
+ button#reset.btn.btn-primary.navbar-btn(style={'margin-right': '10px', 'margin-bottom':'10px'}) Reset camera
+ block extrabutton
+
+ button#undo.btn.btn-default.navbar-btn(style={'margin-right': '10px', 'margin-bottom': '10px'})
+ span.glyphicon.glyphicon-triangle-left('aria-hidden'="true")
+
+ button#redo.btn.btn-default.navbar-btn(style={'margin-right': '10px', 'margin-bottom':'10px'})
+ span.glyphicon.glyphicon-triangle-right('aria-hidden'="true")
+
+ //-input#fullarrow(type="checkbox", style={'margin-right': '10px', 'margin-bottom': '10px'})
+ //-label(for="fullarrow" style={'margin-right':'10px'}) Full arrow
+
+ input#lock(type="checkbox", style={'zoom':'1.7'}, checked)
+ label(for="lock" style={'margin-right':'10px', 'zoom': '1.7'}) Pointer lock
+
+ input#showarrows(type="checkbox", style={'margin-right': '10px', 'margin-bottom': '10px', 'display':'none'}, checked)
+ label(for="showarrows" style={'margin-right':'10px', 'display': 'none'}) Show arrows
+
+ //- block lastbutton
+
+ //-input#recommendation(type="checkbox", style={'margin-right': '10px', 'margin-bottom': '10px'})
+ //-label(for="recommendation" style={'margin-right':'10px'}) Fixed prev
+
+ audio#music(controls, volume=0.5)
+ source(src="/static/data/music/bobomb.ogg")
+ source(src="/static/data/music/bobomb.mp3")
+
diff --git a/server/geo/ConfigGenerators/ConfigGenerator.js b/server/geo/ConfigGenerators/ConfigGenerator.js
new file mode 100644
index 0000000..bf20532
--- /dev/null
+++ b/server/geo/ConfigGenerators/ConfigGenerator.js
@@ -0,0 +1,33 @@
+/**
+ * Creates an empty config generator (generating always empty configs)
+ * @constructor
+ * @param streamer {geo.MeshStreamer} the parent mesh streamer
+ */
+geo.ConfigGenerator = function(streamer) {
+
+ this.streamer = streamer;
+ this.beginning = true;
+
+};
+
+/**
+ * Generates an empty configuration
+ * @return {Object[]} an empty array
+ */
+geo.ConfigGenerator.prototype.generateMainConfig = function() {
+
+ process.stderr.write('Warning : empty config generator used\n');
+ return [];
+
+};
+
+/**
+ * Generates an empty configuration
+ * @return {Object[]} an empty array
+ */
+geo.ConfigGenerator.prototype.generateFillingConfig = function() {
+
+ process.stderr.write('Warning : empty config generator used\n');
+ return [];
+
+};
diff --git a/server/geo/ConfigGenerators/ConfigGeneratorEnd.js b/server/geo/ConfigGenerators/ConfigGeneratorEnd.js
new file mode 100644
index 0000000..b5dd585
--- /dev/null
+++ b/server/geo/ConfigGenerators/ConfigGeneratorEnd.js
@@ -0,0 +1,24 @@
+/**
+ * Creates a configuration generator from a string that can be 'NV-PN', 'V-PP', 'V-PD', or 'V-PP-PD'
+ * @param prefetchingPolicy {String} the string corresponding to the prefetching policy
+ * @param streamer {Reference to the {@link geo.MeshStreamer}
+ * @return {geo.ConfigGenerator} An instance of one of the subclasses of {@link geo.ConfigGenerator}
+ */
+geo.ConfigGenerator.createFromString = function(prefetchingPolicy, streamer) {
+
+ // Convert arguments into an array
+ var args = Array.prototype.slice.call(arguments);
+ args[0] = null;
+
+ // Shift that array in order to create a generator easily
+ switch (prefetchingPolicy) {
+ case 'NV-PN': return new (Function.prototype.bind.apply(geo.NV_PN_Generator,args));
+ case 'V-PD': return new (Function.prototype.bind.apply(geo.V_PD_Generator,args));
+ case 'V-PP': return new (Function.prototype.bind.apply(geo.V_PP_Generator,args));
+ case 'V-PP-PD': return new (Function.prototype.bind.apply(geo.V_PP_PD_Generator,args));
+ default:
+ process.stderr.write('Warning : prefetch type not recognized, using default...\n');
+ return new geo.ConfigGenerator();
+ }
+
+};
diff --git a/server/geo/ConfigGenerators/NV_PN.js b/server/geo/ConfigGenerators/NV_PN.js
new file mode 100644
index 0000000..db0ba34
--- /dev/null
+++ b/server/geo/ConfigGenerators/NV_PN.js
@@ -0,0 +1,40 @@
+/**
+ * Class that represents a generator that streams first the frustum of the
+ * camera, and then linearly according to the .obj file
+ * @constructor
+ * @augments geo.ConfigGenerator
+ * @param streamer {geo.MeshStreamer} the parent mesh streamer
+ */
+geo.NV_PN_Generator = function(streamer) {
+
+ geo.ConfigGenerator.apply(this, arguments);
+
+};
+
+geo.NV_PN_Generator.prototype = Object.create(geo.ConfigGenerator);
+geo.NV_PN_Generator.prototype.constructor = geo.NV_PN_Generator;
+
+/**
+ * Generates a configuration with only the camera frustum, with proportion of 1
+ * @returns {Object[]} an array with one element corresponding to the camera frustum
+ */
+geo.NV_PN_Generator.prototype.generateMainConfig = function(cameraFrustum, recommendationClicked) {
+
+ var config;
+
+ // Case without prefetch
+ console.log("No prefetching");
+ config = [{ frustum: cameraFrustum, proportion: 1}];
+
+ return config;
+};
+
+/**
+ * Generates an empty configuration
+ * @returns {Object[]} an empty array
+ */
+geo.NV_PN_Generator.prototype.generateFillingConfig = function(previousConfig, previousData, cameraFrustum, recommendationClicked) {
+
+ return [];
+
+};
diff --git a/server/geo/ConfigGenerators/V_PD.js b/server/geo/ConfigGenerators/V_PD.js
new file mode 100644
index 0000000..7ca2cd6
--- /dev/null
+++ b/server/geo/ConfigGenerators/V_PD.js
@@ -0,0 +1,61 @@
+/**
+ * Class that represents a generator that streams the recommendation clicked if any, of the frustum
+ * @constructor
+ * @augments geo.ConfigGenerator
+ * @param streamer {geo.MeshStreamer} the parent mesh streamer
+ */
+geo.V_PD_Generator = function() {
+
+ geo.ConfigGenerator.apply(this, arguments);
+
+};
+
+geo.V_PD_Generator.prototype = Object.create(geo.ConfigGenerator);
+geo.V_PD_Generator.prototype.constructor = geo.V_PD_Generator;
+
+/**
+ * Generates a config that streams everything on the recommendation clicked if any, or full on the frustum
+ * @param cameraFrustum {Object} the frustum of the camera (with its position, target, and planes)
+ * @param recommendationClicked {Number|null} id of the recommendation (can be null if no recommendations are clicked)
+ * @returns {Object[]} an array with one element corresponding to the recommendation clicked, or the camera frustum if there are no recommendations clicked
+ */
+geo.V_PD_Generator.prototype.generateMainConfig = function(cameraFrustum, recommendationClicked) {
+
+ var config;
+ if (recommendationClicked != null) {
+
+ if (this.streamer.beginning === true) {
+ this.streamer.beginning = false;
+ }
+
+ // Case full reco
+ console.log("Going to " + recommendationClicked);
+ console.log("Recommendation is clicking : full for " + JSON.stringify(this.streamer.mesh.recommendations[recommendationClicked].position));
+ config = [{recommendationId : recommendationClicked + 1, proportion: 1, smart:true}];
+
+ } else if (this.streamer.beginning === true) {
+
+ console.log('Begining : full init');
+ config = [{recommendationId : 0, proportion:1, smart: true}];
+
+
+ } else {
+
+ // Case without prefetch
+ console.log("No prefetching");
+ config = [{ frustum: cameraFrustum, proportion: 1}];
+
+ }
+
+ return config;
+};
+
+/**
+ * Generates a configuration with only the camera frustum, with proportion of 1
+ * @returns {Object[]} an array with one element corresponding to the camera frustum
+ */
+geo.V_PD_Generator.prototype.generateFillingConfig = function() {
+
+ return [{proportion:1, frustum: cameraFrustum}];
+
+};
diff --git a/server/geo/ConfigGenerators/V_PP.js b/server/geo/ConfigGenerators/V_PP.js
new file mode 100644
index 0000000..44929c0
--- /dev/null
+++ b/server/geo/ConfigGenerators/V_PP.js
@@ -0,0 +1,109 @@
+/**
+ * Class that represents a generator that streams the frustum, and tries to preload recommendations that might be clicked
+ * @constructor
+ * @augments geo.ConfigGenerator
+ * @param streamer {geo.MeshStreamer} the parent mesh streamer
+ */
+geo.V_PP_Generator = function() {
+
+ geo.ConfigGenerator.apply(this, arguments);
+
+};
+
+geo.V_PP_Generator.prototype = Object.create(geo.ConfigGenerator);
+geo.V_PP_Generator.prototype.constructor = geo.V_PP_Generator;
+
+/**
+ * Generates a config that streams partly the frustum, and splits the rest of the chunk among the recommendations that are likely to be clicked
+ * @param cameraFrustum {Object} the frustum of the camera (with its position, target, and planes)
+ * @returns {Object[]} an array with one element corresponding the camera frustum, and other for eventual recommendations to preload
+ */
+geo.V_PP_Generator.prototype.generateMainConfig = function(cameraFrustum) {
+
+ var config;
+
+ if (this.streamer.beginning === true) {
+
+ console.log('Begining : full init');
+ config = [{recommendationId : 0, proportion:1, smart: true}];
+
+ } else {
+
+ // Case full prefetch
+ console.log("Allow some prefetching");
+
+ didPrefetch = true;
+ config = [{ frustum: cameraFrustum, proportion : this.streamer.frustumPercentage}];
+
+ if (this.streamer.predictionTable !== undefined) {
+
+ var sum = 0;
+
+ for (var i = 1; i <= this.streamer.mesh.recommendations.length; i++) {
+
+ sum += this.streamer.predictionTable[this.streamer.previousReco][i];
+
+ }
+
+ for (var i = 1; i <= this.streamer.mesh.recommendations.length; i++) {
+
+ if (this.streamer.predictionTable[this.streamer.previousReco][i] > 0) {
+
+ config.push({
+
+ proportion : this.streamer.predictionTable[this.streamer.previousReco][i] * this.streamer.prefetchPercentage / sum,
+ recommendationId : i,
+ smart: true
+
+ });
+
+ }
+
+ }
+
+ } else {
+
+ process.stderr.write('ERROR : PREDICTION TABLE IF UNDEFINED');
+
+ }
+
+ }
+
+ return config;
+
+};
+
+/**
+ * Generates a config that depends on the previous configuration and that tries to fill the recommendations that were not already filled.
+ * @param previousConfig {Object} the previous configuration list (that was launched on generateMainConfig
+ * @param previousData {Object} the data that were given by the nextElements
method on {@link geo.MeshStreamer}
+ * @param cameraFrustum {Object} the frustum of the camera, containing its position, target and planes
+ * @returns {Object[]} a configuration that tries to fill what was not filled before
+ */
+geo.V_PP_Generator.prototype.generateFillingConfig = function(previousConfig, previousData, cameraFrustum) {
+
+ var sum = 0;
+ var newConfig = [];
+
+ for (var i = 0; i < previousConfig.length; i++) {
+
+ // Check if previousConfig was full
+ if (previousResult.configSizes[i] >= this.streamer.chunk * previousConfig[i].proportion) {
+
+ newConfig.push(previousConfig[i]);
+ sum += previousConfig[i].proportion;
+
+ }
+
+ }
+
+ // Normalize previousConfig probabilities
+ for (var i = 0; i < newConfig.length; i++) {
+
+ newConfig[i].proportion /= sum;
+
+ }
+
+ return newConfig;
+
+};
diff --git a/server/geo/ConfigGenerators/V_PP_PD.js b/server/geo/ConfigGenerators/V_PP_PD.js
new file mode 100644
index 0000000..718ff72
--- /dev/null
+++ b/server/geo/ConfigGenerators/V_PP_PD.js
@@ -0,0 +1,128 @@
+/**
+ * Class that represents a generator that streams the current recommendation clicked if any, or the frustum, and tries to preload recommendations that might be clicked
+ * @constructor
+ * @augments geo.V_PP_Generator
+ * @param streamer {geo.MeshStreamer} the parent mesh streamer
+ */
+geo.V_PP_PD_Generator = function() {
+
+ geo.V_PP_Generator.apply(this, arguments);
+
+};
+
+geo.V_PP_PD_Generator.prototype = Object.create(geo.V_PP_Generator);
+geo.V_PP_PD_Generator.prototype.constructor = geo.V_PP_PD_Generator;
+
+/**
+ * Generates a config that streams partly the frustum, and splits the rest of the chunk among the recommendations that are likely to be clicked
+ * @param cameraFrustum {Object} the frustum of the camera (with its position, target, and planes)
+ * @param recommendationClicked {Number|null} id of the recommendation (can be null if no recommendations are clicked)
+ * @returns {Object[]} an array with one element corresponding the current recommendation or the camera frustum, and others for eventual recommendations to preload
+ */
+geo.V_PP_PD_Generator.prototype.generateMainConfig = function(cameraFrustum, recommendationClicked) {
+
+ var config;
+
+ if (recommendationClicked != null) {
+
+ if (this.streamer.beginning === true) {
+ this.streamer.beginning = false;
+ }
+
+ // Case full reco
+ console.log("Going to " + recommendationClicked);
+ console.log("Recommendation is clicking : full for " + JSON.stringify(this.streamer.mesh.recommendations[recommendationClicked].position));
+ config = [{recommendationId : recommendationClicked + 1, proportion: 1, smart:true}];
+
+
+
+ } else if (this.streamer.beginning === true) {
+
+ console.log('Begining : full init');
+ config = [{recommendationId : 0, proportion:1, smart: true}];
+
+
+ } else {
+
+ // Case full prefetch
+ console.log("Allow some prefetching");
+
+ config = [{ frustum: cameraFrustum, proportion : this.streamer.frustumPercentage}];
+
+ // Find best recommendation
+ var bestReco;
+ var bestScore = -Infinity;
+ var bestIndex = null;
+
+ if (this.streamer.predictionTable !== undefined) {
+
+ var sum = 0;
+
+ for (var i = 1; i <= this.streamer.mesh.recommendations.length; i++) {
+
+ sum += this.streamer.predictionTable[this.streamer.previousReco][i];
+
+ }
+
+ for (var i = 1; i <= this.streamer.mesh.recommendations.length; i++) {
+
+ if (this.streamer.predictionTable[this.streamer.previousReco][i] > 0) {
+
+ config.push({
+
+ proportion : this.streamer.predictionTable[this.streamer.previousReco][i] * this.streamer.prefetchPercentage / sum,
+ recommendationId : i,
+ smart: true
+
+ });
+
+ }
+
+ }
+
+ } else {
+
+ process.stderr.write('ERROR : PREDICTION TABLE IF UNDEFINED');
+
+ }
+
+ }
+
+ return config;
+
+};
+
+/**
+ * Generates a config that depends on the previous configuration and that tries to fill the recommendations that were not already filled.
+ * @param previousConfig {Object} the previous configuration list (that was launched on generateMainConfig
+ * @param previousData {Object} the data that were given by the nextElements
method on {@link geo.MeshStreamer}
+ * @param cameraFrustum {Object} the frustum of the camera, containing its position, target and planes
+ * @returns {Object[]} a configuration that tries to fill what was not filled before
+ */
+geo.V_PP_PD_Generator.prototype.generateFillingConfig = function(previousConfig, previousData, cameraFrustum) {
+
+ var sum = 0;
+ var newConfig = [];
+
+ for (var i = 0; i < previousConfig.length; i++) {
+
+ // Check if previousConfig was full
+ if (previousResult.configSizes[i] >= this.streamer.chunk * previousConfig[i].proportion) {
+
+ newConfig.push(previousConfig[i]);
+ sum += previousConfig[i].proportion;
+
+ }
+
+ }
+
+ // Normalize previousConfig probabilities
+ for (var i = 0; i < newConfig.length; i++) {
+
+ newConfig[i].proportion /= sum;
+
+ }
+
+ return newConfig;
+
+};