diff --git a/analysis/hover/main.m b/analysis/hover/main.m new file mode 100644 index 0000000..1a5f141 --- /dev/null +++ b/analysis/hover/main.m @@ -0,0 +1,71 @@ +diffTable; + +EXP_ID = 1; +ARROW_ID = 2; +STARTED = 3; +TIME = 4; + +% Compute diff tables, and split by exps + +hovers = {}; +map = []; + +start = 1; +current_exp_id = M(1,EXP_ID); + +for i = 1:length(M), + + if M(i, EXP_ID) ~= current_exp_id, + + current_exp_id = M(i, EXP_ID); + start = start + 1; + + end + + map(i) = start; + +end + +hovers = cell(max(map), 1); + +is_hovering = false; +current_hover = -1; +current_time = 0; + +for i = 1:length(M), + + if M(i, STARTED), + + if (current_hover ~= -1 && M(i, ARROW_ID) ~= current_hover && is_hovering), + + % Add line for previous hovering + hovers{map(M(i, EXP_ID))} = [hovers{map(M(i, EXP_ID))}; M(i, TIME) - current_time]; + + end + + current_hover = M(i, ARROW_ID); + current_time = M(i, TIME); + + else + + hovers{map(M(i, EXP_ID))} = [hovers{map(M(i, EXP_ID))}; M(i, TIME) - current_time]; + + end + +end + +% All hover time +all_hovers = sort(vertcat(hovers{:})); + +times = 0:0.001:0.6; + +curve = zeros(length(times),1); + +for i = 1 : length(times), + + curve(i) = sum(all_hovers < times(i)); + +end + +plot(times, curve); + diff --git a/analysis/server-replay/main.js b/analysis/server-replay/main.js index 9e974cc..ccb1e7f 100644 --- a/analysis/server-replay/main.js +++ b/analysis/server-replay/main.js @@ -24,6 +24,7 @@ let modelMap; let smallMap; let smallModel; let triangleMeshes = []; +let colorToFace = []; let renderer = new THREE.CanvasRenderer(); renderer.domElement.style = renderer.domElement; @@ -45,6 +46,7 @@ scene.add(mesh); scene.add(camera); let counter = 0; +let forceFinished = false; init() @@ -78,7 +80,7 @@ function init() { } progLoader = new L3D.ProgressiveLoader( - buildPathBigObj(path), scene, info.camera, null, function(a,b) { /* process.stderr.write((100*a/b) + '%\n'); */ }, false, info.sort + buildPathBigObj(path), new THREE.Object3D(), info.camera, null, function(a,b) { /* process.stderr.write((100*a/b) + '%\n'); */ }, false, info.sort ); // Init variables @@ -104,6 +106,7 @@ function init() { face3.materialIndex = counter; material.materials.push(new THREE.MeshBasicMaterial({color: counter, overdraw: true})); + colorToFace[counter] = face3; geometry.faces.push(face3); @@ -116,12 +119,12 @@ function init() { } - process.stderr.write('Loading complete.\n'); + process.stderr.write('--> Loading complete.\n'); progLoader.onBeforeEmit = loop; progLoader.load(function() { - process.stderr.write("Loading complete\n"); + process.stderr.write("--> Loading complete\n"); forceFinished = true; }); @@ -144,19 +147,50 @@ function buildPathMap(path) { return './maps/' + path.name + '.json'; } function loop() { - process.stderr.write(imageNumber + '\n'); + process.stderr.write('--> ' + imageNumber + '\n'); - camera.update(20); + for (let i = 0; i < 10; i++) + camera.update(20); camera.look(); - process.stderr.write('Rendering...\n'); + process.stderr.write('--> Rendering...\n'); let lastTime = Date.now(); renderer.render(scene, camera); - process.stderr.write('Renderered in ' + (Date.now() - lastTime) + '\n'); + process.stderr.write('--> Renderered in ' + (Date.now() - lastTime) + '\n'); - fs.writeFileSync(__dirname + '/img/' + pad(imageNumber++, 5) + '.png', renderer.domElement.toBuffer()); + fs.writeFileSync(__dirname + '/img' + (info.sort ? '1' : '') + '/' + pad(imageNumber++, 5) + '.png', renderer.domElement.toBuffer()); + let score = 0; + let denom = 0; + + // Traverse image to find faces + lastTime = Date.now(); + let pixelData = renderer.getContext() + .getImageData(0, 0, renderer.domElement.width, renderer.domElement.height).data; + + for (let i = 0; i < pixelData.length; i += 4) { + + let pixelNumber = (pixelData[i] << 16) + (pixelData[i+1] << 8) + (pixelData[i+2]); + + if (pixelNumber !== 0) { + denom++; + try { + if (progLoader.hasFace(colorToFace[pixelNumber])) { + score++; + } + } catch(e) { + process.stderr.write('--> ' + pixelNumber + '\n'); + } + } + } + + process.stderr.write('--> Computed pixels in ' + (Date.now() - lastTime) + 'ms\n'); + + score /= denom; + + process.stderr.write('--> Score : ' + score + '\n'); + console.log(score); } @@ -187,7 +221,7 @@ function initElements(camera, sceneInfo, redCoins) { // camera.coins = L3D.generateCoins(L3D.createWhompCoins(), redCoins); return {name:'Whomps Fortress', folder:'whomp'}; default: - process.stderr.write('This sceneId doesn\'t exist\n'); + process.stderr.write('--> This sceneId doesn\'t exist\n'); process.exit(-1); diff --git a/js/l3d/src/cameras/ReplayCamera.js b/js/l3d/src/cameras/ReplayCamera.js index d56e07d..77b2aa9 100644 --- a/js/l3d/src/cameras/ReplayCamera.js +++ b/js/l3d/src/cameras/ReplayCamera.js @@ -26,6 +26,8 @@ L3D.ReplayCamera = function() { this.recommendationClicked = null; + this.isArrow = false; + }; L3D.ReplayCamera.prototype = Object.create(THREE.PerspectiveCamera.prototype); L3D.ReplayCamera.prototype.constructor = L3D.ReplayCamera; @@ -107,6 +109,11 @@ L3D.ReplayCamera.prototype.nextEvent = function() { var self = this; + if (self.isArrow) { + self.isArrow = false; + process.stderr.write('\033[31mArrowclicked finished !\033[0m\n'); + } + this.counter++; // Finished @@ -139,6 +146,8 @@ L3D.ReplayCamera.prototype.nextEvent = function() { // },500); // })(this); } else if (this.event.type == 'arrow') { + self.isArrow = true; + process.stderr.write('\033[33mArrowclicked ! ' + JSON.stringify(self.cameras[self.event.id].camera.position) + '\033[0m\n'); if (this.shouldRecover) { (function(self, tmp) { self.event.type = 'camera'; @@ -252,7 +261,7 @@ L3D.ReplayCamera.prototype.moveHermite = function(recommendation) { L3D.ReplayCamera.prototype.moveReco = function(recommendationId) { - this.recommendationClicked = this.cameras[recommendationId].camera; + this.recommendationClicked = recommendationId; this.moveHermite(this.cameras[recommendationId]); @@ -270,7 +279,7 @@ L3D.ReplayCamera.prototype.save = function() {}; */ L3D.ReplayCamera.prototype.toList = function() { - var camera = (this.recommendationClicked === null ? this : this.recommendationClicked); + var camera = this; // (this.recommendationClicked === null ? this : this.cameras[this.recommendationClicked].camera); camera.updateMatrix(); camera.updateMatrixWorld(); @@ -284,7 +293,7 @@ L3D.ReplayCamera.prototype.toList = function() { var ret = [[camera.position.x, camera.position.y, camera.position.z], [camera.target.x, camera.target.y, camera.target.z], - this.recommendationClicked !== null + this.recommendationClicked ]; for (var i = 0; i < frustum.planes.length; i++) { diff --git a/js/l3d/src/loaders/ProgressiveLoader.js b/js/l3d/src/loaders/ProgressiveLoader.js index fb05556..2d8fefe 100644 --- a/js/l3d/src/loaders/ProgressiveLoader.js +++ b/js/l3d/src/loaders/ProgressiveLoader.js @@ -301,7 +301,7 @@ ProgressiveLoader.prototype.initIOCallbacks = function() { this.socket.on('elements', function(arr) { - process.stderr.write('Received ' + arr.length + '\n'); + // process.stderr.write('Received ' + arr.length + '\n'); for (var i = 0; i < arr.length; i++) { @@ -431,15 +431,23 @@ ProgressiveLoader.prototype.initIOCallbacks = function() { } + var param; if (typeof self.onBeforeEmit === 'function') { - self.onBeforeEmit(); - } - // Ask for next elements - if (!self.laggy) { - self.socket.emit('next', self.getCamera()); + for (var m of self.meshes) { + m.geometry.computeBoundingSphere(); + } + param = self.onBeforeEmit(); + setTimeout(function() { self.socket.emit('next', self.getCamera(), param);}, 100); + } else { - setTimeout(function() { self.socket.emit('next', self.getCamera());}, 100); + + // Ask for next elements + if (!self.laggy) { + self.socket.emit('next', self.getCamera(), param); + } else { + setTimeout(function() { self.socket.emit('next', self.getCamera());}, 100); + } } }); diff --git a/js/l3d/src/scenes/initScene.js b/js/l3d/src/scenes/initScene.js index 96561fb..e1d7d5d 100644 --- a/js/l3d/src/scenes/initScene.js +++ b/js/l3d/src/scenes/initScene.js @@ -25,7 +25,7 @@ L3D.initPeachCastle = function(scene, collidableObjects, recommendation, clickab var loader = new L3D.ProgressiveLoader( '/static/data/castle/princess peaches castle (outside).obj', scene, - null, + recommendation, function(object) { if (clickable !== undefined) clickable.push(object); @@ -356,7 +356,7 @@ L3D.initWhompScene = function(scene, collidableObjects, recommendation, clickabl var loader = new L3D.ProgressiveLoader( '/static/data/whomp/Whomps Fortress.obj', scene, - null, + recommendation, function(object) { if (clickable !== undefined) clickable.push(object); @@ -512,7 +512,7 @@ L3D.initMountainScene = function(scene, collidableObjects, recommendation, click var loader = new L3D.ProgressiveLoader( '/static/data/mountain/coocoolmountain.obj', scene, - null, + recommendation, function(object) { // object.rotation.x = -Math.PI/2; // object.rotation.z = Math.PI/2; diff --git a/server/geo/MeshContainer.js b/server/geo/MeshContainer.js index 35e9360..eabe5b2 100644 --- a/server/geo/MeshContainer.js +++ b/server/geo/MeshContainer.js @@ -355,9 +355,6 @@ function pushMesh(name) { reco.matrixWorldInverse.getInverse( reco.matrixWorld ); var frustum = new THREE.Frustum(); - var projScreenMatrix = new THREE.Matrix4(); - projScreenMatrix.multiplyMatrices(reco.projectionMatrix, reco.matrixWorldInverse); - frustum.setFromMatrix(new THREE.Matrix4().multiplyMatrices(reco.projectionMatrix, reco.matrixWorldInverse)); geo.availableMeshes[name].recommendations.push({ diff --git a/server/geo/MeshStreamer.js b/server/geo/MeshStreamer.js index dca4174..f354c51 100644 --- a/server/geo/MeshStreamer.js +++ b/server/geo/MeshStreamer.js @@ -159,7 +159,7 @@ geo.MeshStreamer = function(path) { * Number of element to send by packet * @type {Number} */ - this.chunk = 1250; + this.chunk = 1250 / 2; this.previousReco = 0; @@ -344,7 +344,7 @@ geo.MeshStreamer.prototype.start = function(socket) { }); - socket.on('next', function(_camera) { + socket.on('next', function(_camera, score) { var cameraFrustum = {}; @@ -365,7 +365,7 @@ geo.MeshStreamer.prototype.start = function(socket) { planes: [] }; - var fullFrustum = _camera[2]; + var recommendationClicked = _camera[2]; for (i = 3; i < _camera.length; i++) { @@ -384,10 +384,25 @@ geo.MeshStreamer.prototype.start = function(socket) { // Create config for proportions of chunks var config; + var didPrefetch = false; - if (self.prefetch) { + if (!self.prefetch || (recommendationClicked === null && score < 0.85)) { + console.log("Score not good enough, no prefetch"); + config = [{ frustum: cameraFrustum, proportion: 1}]; + + } else if (recommendationClicked !== null) { + + console.log("Recommendation is clicking : full for " + JSON.stringify(self.mesh.recommendations[recommendationClicked].position)); + config = [{frustum: cameraFrustum, proportion:0.5}, {frustum : self.mesh.recommendations[recommendationClicked], proportion: 0.5}]; + + } else { + + console.log("Good % (" + score + "), allow some prefetching"); + + didPrefetch = true; config = [{ frustum: cameraFrustum, proportion : 0.5}]; + // config = []; // Find best recommendation var bestReco; @@ -398,20 +413,20 @@ geo.MeshStreamer.prototype.start = function(socket) { var sum = 0; - for (var i = 0; i < self.mesh.recommendations.length; i++) { + for (var i = 1; i <= self.mesh.recommendations.length; i++) { sum += self.predictionTable[self.previousReco][i]; } - for (var i = 0; i < self.mesh.recommendations.length; i++) { + for (var i = 1; i <= self.mesh.recommendations.length; i++) { if (self.predictionTable[self.previousReco][i] > 0) { config.push({ proportion : self.predictionTable[self.previousReco][i] / (2 * sum), - frustum : self.mesh.recommendations[i] + frustum : self.mesh.recommendations[i-1] }); @@ -428,66 +443,95 @@ geo.MeshStreamer.prototype.start = function(socket) { } - if (!fullFrustum) { - - // console.log('Frustum and prefetch : ' + (cameraFrustum !== undefined) + ' ' + (bestReco !== undefined)); - - } else { - - // console.log('Full frustum fetching (reco clicked)'); - - config = [{ - proportion: 1, - frustum: cameraFrustum - }]; - - } - - } else { - - config = [{frustum: cameraFrustum, proportion: 1}]; - } // Send next elements var oldTime = Date.now(); var next = self.nextElements(config); - // console.log(next.configSizes); + // console.log( + // 'Adding ' + + // next.size + + // ' for newConfig : ' + // + JSON.stringify(config.map(function(o) { return o.proportion})) + // ); + + + console.log(next.configSizes); // console.log('Time to generate chunk : ' + (Date.now() - oldTime) + 'ms'); if (self.prefetch && next.size < self.chunk) { + console.log("Chunk not full : prefetch reco"); + // Recompute config var newConfig = []; var sum = 0; - for (var i = 0; i < config.length; i++) { + if (!didPrefetch) { - // Check if config was full - if (next.configSizes[i] >= self.chunk * config[i].proportion) { + if (self.predictionTable !== undefined) { - newConfig.push(config[i]); - sum += config[i].proportion; + var sum = 0; + + for (var i = 1; i <= self.mesh.recommendations.length; i++) { + + sum += self.predictionTable[self.previousReco][i]; + + } + + for (var i = 1; i <= self.mesh.recommendations.length; i++) { + + if (self.predictionTable[self.previousReco][i] > 0) { + + newConfig.push({ + + proportion : self.predictionTable[self.previousReco][i] / (sum), + frustum : self.mesh.recommendations[i-1] + + }); + + } + + } + + } + + } else { + + for (var i = 0; i < config.length; i++) { + + // Check if config was full + if (next.configSizes[i] >= self.chunk * config[i].proportion) { + + newConfig.push(config[i]); + sum += config[i].proportion; + + } + + } + + // Normalize config probabilities + for (var i = 0; i < newConfig.length; i++) { + + newConfig[i].proportion /= sum; } } - for (var i = 0; i < newConfig.length; i++) { - - newConfig[i].proportion /= sum; - - } - - // Normalize config probabilities var newData = self.nextElements(newConfig, self.chunk - next.size); next.data.push.apply(next.data, newData.data); - // console.log('Adding ' + newData.size + ' for newConfig : ' + JSON.stringify(newConfig.map(function(o) { return o.proportion}))); + // console.log( + // 'Adding ' + + // newData.size + + // ' for newConfig : ' + // + JSON.stringify(newConfig.map(function(o) { return o.proportion})) + // ); next.size = next.size + newData.size; @@ -495,6 +539,8 @@ geo.MeshStreamer.prototype.start = function(socket) { if (next.size < self.chunk) { + console.log("Chunk not full : fill linear"); + // If nothing, just serve stuff var tmp = self.nextElements([ // { @@ -508,7 +554,7 @@ geo.MeshStreamer.prototype.start = function(socket) { } - // console.log('Chunk of size ' + next.size); + console.log('Chunk of size ' + next.size + ' (generated in ' + (Date.now() - oldTime) + 'ms)'); // console.log('Time to generate chunk : ' + (Date.now() - oldTime) + 'ms'); if (next.data.length === 0) { @@ -637,9 +683,13 @@ geo.MeshStreamer.prototype.nextElements = function(config, chunk) { // Sort buffer if (config[configIndex].frustum !== undefined) { + buffers[configIndex].sort(this.faceComparator(config[configIndex].frustum)); + } else { + // console.log("Did not sort"); + } // Fill chunk @@ -648,9 +698,9 @@ geo.MeshStreamer.prototype.nextElements = function(config, chunk) { var size = this.pushFace(buffers[configIndex][i], data); totalSize += size; - configSize += size; + configSizes[configIndex] += size; - if (configSize > chunk * config[configIndex].proportion) { + if (configSizes[configIndex] > chunk * config[configIndex].proportion) { break;