diff --git a/analysis/length.js b/analysis/length.js deleted file mode 100755 index a583fd9..0000000 --- a/analysis/length.js +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/node -var lib = require('./lib.js'); -var r = require('./initScene.js'); - -var reco = [ - r.createPeachRecommendations(), - r.createBobombRecommendations(), - r.createMountainRecommendations(), - r.createWhompRecommendations() -]; - -function distanceBetweenPoints(pt1, pt2) { - return ( - Math.sqrt( - (pt2.x - pt1.x) * (pt2.x - pt1.x) + - (pt2.y - pt1.y) * (pt2.y - pt1.y) + - (pt2.z - pt1.z) * (pt2.z - pt1.z) - ) - ); -} - -function main(path) { - - var db = lib.loadFromFile(path); - var groups = lib.makeGroups(db); - - // Erase groups that are not usable - var invalid = 0; - groups = groups.filter(function(elt) { - - // An elt is valid if it contains at least 2 exp, BaseRecommendation included - if (elt.length > 1 && elt.find(function(e) { return e.recommendation_style[4] === 'B'; }) !== undefined) { - return true - } else { - invalid++; - return false; - } - - }); - - var percentSum = 0; - var eltNum = 0; - - for (var i = 0; i < db.experiments.length; i++) { - - var exp = db.experiments[i]; - - if (exp.coinCombination.scene_id === 1 || exp.recommendation_style[4] === 'B' || !exp.finished || lib.coinsGot(exp) < 6) { - continue; - } - - eltNum++; - - var distance = 0; - var distanceWithReco = 0; - - - var j = 0; - - while (exp.elements.events[j].position === undefined) { j++; }; - - var startPosition = exp.elements.events[j].position; - j++; - - while (j < exp.elements.events.length) { - - var nextPosition, evt = exp.elements.events[j]; - - if (evt.position === undefined && evt.type !== 'arrow') { j++; continue; } - - if (evt.type === 'arrow') { - nextPosition = reco[exp.coinCombination.scene_id - 1][evt.id].position; - } else { - nextPosition = evt.position; - } - - var tmp = distanceBetweenPoints(startPosition, nextPosition); - - if (evt.type === 'arrow') { - distanceWithReco += tmp; - } - - distance += tmp; - - startPosition = nextPosition; - - j++; - - } - - percentSum += 100 * distanceWithReco / distance; - console.log(exp.id + ' -> ' + Math.floor(100 * distanceWithReco / distance) + '%'); - - } - - console.log('Mean : ' + percentSum / eltNum); - -} - -if (process.argv.length !== 3) { - process.stderr.write('Error : please give me a JSON file to work on\n'); - process.exit(-1); -} - -main(process.argv[2]) diff --git a/js/l3d/src/cameras/PointerCamera.js b/js/l3d/src/cameras/PointerCamera.js index feecace..d8aca95 100644 --- a/js/l3d/src/cameras/PointerCamera.js +++ b/js/l3d/src/cameras/PointerCamera.js @@ -181,6 +181,8 @@ L3D.PointerCamera = function() { * @param {Object} */ this.resetElements = {position: new THREE.Vector3(0,1,1), target: new THREE.Vector3()}; + + this.recommendationClicked = null; }; L3D.PointerCamera.prototype = Object.create(THREE.PerspectiveCamera.prototype); L3D.PointerCamera.prototype.constructor = L3D.PointerCamera; @@ -323,6 +325,7 @@ L3D.PointerCamera.prototype.hermiteMotion = function(time) { if (this.t > 1) { this.movingHermite = false; + this.recommendationClicked = null; this.anglesFromVectors(); } }; @@ -491,6 +494,8 @@ L3D.PointerCamera.prototype.moveHermite = function(recommendation, toSave) { var otherCamera = recommendation.camera || recommendation; + this.recommendationClicked = otherCamera; + this.moving = false; this.movingHermite = true; this.t = 0; @@ -762,7 +767,9 @@ L3D.PointerCamera.prototype.toList = function() { var ret = [[this.position.x, this.position.y, this.position.z], - [this.target.x, this.target.y, this.target.z]]; + [this.target.x, this.target.y, this.target.z], + this.recommendationClicked !== null + ]; 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 f599276..62b629d 100644 --- a/js/l3d/src/loaders/ProgressiveLoader.js +++ b/js/l3d/src/loaders/ProgressiveLoader.js @@ -1,5 +1,15 @@ L3D.ProgressiveLoader = (function() { +if (typeof process === 'undefined') { + process = { + stderr : { + write: function(str) { + console.log(str); + } + } + }; +} + /** * Parse a list as it is sent by the server and gives a slightly more comprehensible result * @param Array array corresponding to the line of the mesh file @@ -88,7 +98,7 @@ var _parseList = function(arr) { * @constructor * @memberOf L3D */ -var ProgressiveLoader = function(path, scene, camera, callback, log, laggy) { +var ProgressiveLoader = function(path, scene, camera, callback, log, laggy, prefetch) { var self = this; @@ -186,12 +196,24 @@ var ProgressiveLoader = function(path, scene, camera, callback, log, laggy) { */ this.camera = camera; - this.camera._moveReco = this.camera.moveReco; + if (this.camera instanceof L3D.ReplayCamera) { + this.camera._moveReco = this.camera.moveReco; - this.camera.moveReco = function(param) { - self.socket.emit('reco', param); - self.camera._moveReco.apply(self.camera, arguments); - }; + this.camera.moveReco = function(param) { + self.socket.emit('reco', param); + self.camera._moveReco.apply(self.camera, arguments); + }; + } else if (this.camera instanceof L3D.PointerCamera) { + // Only good for sponza model + this.camera._moveHermite = this.camera.moveHermite; + + this.camera.moveHermite = function(param) { + var toSend = param.position.x > 0 ? 0 : 1; + self.socket.emit('reco', toSend); + self.camera._moveHermite.apply(self.camera, arguments); + }; + + } /** * Number of total elements for loading @@ -220,6 +242,9 @@ var ProgressiveLoader = function(path, scene, camera, callback, log, laggy) { this.mapFace = {}; + this.prefetch = prefetch === undefined ? true : (!!prefetch); + console.log(this.prefetch); + }; ProgressiveLoader.prototype.hasFace = function(face) { @@ -277,6 +302,8 @@ ProgressiveLoader.prototype.initIOCallbacks = function() { this.socket.on('elements', function(arr) { + process.stderr.write('Received ' + arr.length + '\n'); + for (var i = 0; i < arr.length; i++) { if (typeof self.log === 'function' && self.numberOfFacesReceived % self.modulus === 0) { @@ -360,6 +387,8 @@ ProgressiveLoader.prototype.initIOCallbacks = function() { self.numberOfFacesReceived++; + self.mapFace[elt.a + '-' + elt.b + '-' + elt.c] = true; + if (!self.meshes[elt.mesh].added) { self.meshes[elt.mesh].added = true; @@ -392,7 +421,6 @@ ProgressiveLoader.prototype.initIOCallbacks = function() { } - self.mapFace[elt.a + '-' + elt.b + '-' + elt.c] = true; } else if (elt.type === 'global') { @@ -436,7 +464,7 @@ ProgressiveLoader.prototype.initIOCallbacks = function() { * Starts the communication with the server */ ProgressiveLoader.prototype.start = function() { - this.socket.emit('request', this.objPath, this.laggy); + this.socket.emit('request', this.objPath, this.laggy, this.prefetch); }; return ProgressiveLoader; diff --git a/js/l3d/src/scenes/initScene.js b/js/l3d/src/scenes/initScene.js index 8e3c34f..c04d44f 100644 --- a/js/l3d/src/scenes/initScene.js +++ b/js/l3d/src/scenes/initScene.js @@ -726,9 +726,13 @@ L3D.createSponzaRecommendations = function(width, height) { return [ createRecommendation( - new THREE.Vector3(97.36225946503932,10.925697484337014,12.852363038244272), - new THREE.Vector3(133.315101552449,18.576354168001703,-2.9229530646577633) - ) + new THREE.Vector3(97.36225946503932,10.925697484337014,12.852363038244272), + new THREE.Vector3(133.315101552449,18.576354168001703,-2.9229530646577633) + ), + createRecommendation( + new THREE.Vector3(-110.4869853758238,17.692671522169423,14.022902297589127), + new THREE.Vector3(-147.9067343700736,16.890814116754584,-0.08806541935797796) + ) ]; }; diff --git a/server/geo/MeshStreamer.js b/server/geo/MeshStreamer.js index 0a46236..20afe1e 100644 --- a/server/geo/MeshStreamer.js +++ b/server/geo/MeshStreamer.js @@ -5,9 +5,12 @@ var L3D = require('../../static/js/l3d.min.js'); try { var predictionTables = [ - JSON.parse(fs.readFileSync('./mat1.json')), - JSON.parse(fs.readFileSync('./mat2.json')), - JSON.parse(fs.readFileSync('./mat3.json')) + JSON.parse(fs.readFileSync('./geo/mat1.json')), + JSON.parse(fs.readFileSync('./geo/mat2.json')), + JSON.parse(fs.readFileSync('./geo/mat3.json')), + [[1,1,0], + [1,2,0], + [2,1,0]] ]; } catch (e) { process.stderr.write('No prefetching will be done !'); @@ -25,7 +28,7 @@ function isInFrustum(element, planes) { var vertex = element[i]; var currentOutcode = ""; - for (var j = 0; j < planes.length; j++) { + for (var j = 0; j < planes.length; j++) { var plane = planes[j]; @@ -156,7 +159,7 @@ geo.MeshStreamer = function(path) { * Number of element to send by packet * @type {Number} */ - this.chunk = 500; + this.chunk = 5000; this.previousReco = 0; @@ -278,12 +281,14 @@ geo.MeshStreamer.prototype.start = function(socket) { var self = this; - socket.on('request', function(path, laggy) { + socket.on('request', function(path, laggy, prefetch) { if (laggy === true) { self.chunk = 1; } + self.prefetch = prefetch; + self.mesh = geo.availableMeshes[path]; switch (path) { @@ -299,6 +304,8 @@ geo.MeshStreamer.prototype.start = function(socket) { case '/static/data/whomp/Whomps Fortress_sub.obj': self.predictionTable = predictionTables[2]; break; + default: + self.predictionTable = predictionTables[3]; }; if (self.mesh === undefined) { @@ -358,7 +365,7 @@ geo.MeshStreamer.prototype.start = function(socket) { planes: [] }; - var shouldPrefetch = _camera[2]; + var fullFrustum = _camera[2]; for (i = 3; i < _camera.length; i++) { @@ -375,60 +382,122 @@ geo.MeshStreamer.prototype.start = function(socket) { } - // Find best recommendation - var bestReco; - var bestScore = -Infinity; - var bestIndex = null; + // Create config for proportions of chunks + var config; + if (self.prefetch) { - if (self.predictionTable !== undefined) { + config = [{ frustum: cameraFrustum, proportion : 0.5}]; - for (var i = 0; i < self.mesh.recommendations.length; i++) { + // Find best recommendation + var bestReco; + var bestScore = -Infinity; + var bestIndex = null; - if (self.predictionTable[self.previousReco][i+1] > bestScore) { + if (self.predictionTable !== undefined) { - bestReco = self.mesh.recommendations[i]; - bestScore = self.predictionTable[self.previousReco][i+1]; - bestIndex = i; + var sum = 0; + + for (var i = 0; i < self.mesh.recommendations.length; i++) { + + sum += self.predictionTable[self.previousReco][i]; } + for (var i = 0; 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] + + }); + + } + + } + + // console.log(config.map(function(o) { return o.proportion; })); + + } else { + + // For sponza + bestReco = self.mesh.recommendations[0]; + + } + + 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 { - // For sponza - bestReco = self.mesh.recommendations[0]; - - } - - // Create config for proportions of chunks - var config = []; - - if (shouldPrefetch) { - - // Camera cull - config.push({ - proportion: 0.5, - frustum: cameraFrustum - }); - - config.push({ - proportion: 0.5, - frustum: bestReco - }); + config = [{frustum: cameraFrustum, proportion: 1}]; } // Send next elements + var oldTime = Date.now(); var next = self.nextElements(config); + // console.log(next.configSizes); + + // console.log('Time to generate chunk : ' + (Date.now() - oldTime) + 'ms'); + if (next.data.length === 0) { // If nothing, just serve stuff - var tmp = self.nextElements(); + var tmp = self.nextElements([ + // { + // proportion: 1, + // frustum: cameraFrustum + // } + ]); next.data = tmp.data; + } else if (!self.prefetch && next.size < this.chunk) { + + // Recompute config + var newConfig = []; + var sum = 0; + + for (var i = 0; i < config.length; i++) { + + // Check if config was full + if (next.configSizes[i] < this.chunk * config[i].proportion) { + + newConfig.push(config[i]); + sum += config[i].proportion; + + } + + } + + for (var i = 0; i < newConfig.length; i++) { + + newConfig[i].proportion /= sum; + + } + + // Normalize config probabilities + + var newData = self.nextElements(newConfig, this.chunk - next.size); + + next.data = next.data.push.apply(next.data, newData.data); + } socket.emit('elements', next.data); @@ -479,175 +548,186 @@ geo.MeshStreamer.prototype.nextMaterials = function() { * only interesting parts according to the camera * @returns {array} an array of elements ready to send */ -geo.MeshStreamer.prototype.nextElements = function(config) { +geo.MeshStreamer.prototype.nextElements = function(config, chunk) { + + if (chunk === undefined) + chunk = this.chunk; var i; var data = []; + var configSizes = []; + var mightBeCompletetlyFinished = true; // BOOM // if (camera != null) // this.mesh.faces.sort(this.faceComparator(camera)); - if (config === undefined) { config = [] } + if (config.length === 0) { + config.push({ + proportion: 1 + }); + } - configloop: - for (var configIndex = 0; configIndex < config.length + 1; configIndex++) { + totalSize = 0; + for (var configIndex = 0; configIndex < config.length; configIndex++) { - var sent = 0; + configSizes[configIndex] = 0; + + } + + faceloop: + for (var faceIndex = 0; faceIndex < this.mesh.faces.length; faceIndex++) { + + var currentFace = this.mesh.faces[faceIndex]; + + if (this.faces[currentFace.index] === true) { + + continue; - // config[0] should always be cameraFrustum, with eventually 0 proportion - var currentConfig = configIndex !== config.length ? config[configIndex] : config[0]; - if (currentConfig === undefined) { - currentConfig = { - proportion : Infinity, - } } - for (var faceIndex = 0; faceIndex < this.mesh.faces.length; faceIndex++) { + mightBeCompletetlyFinished = false; - var currentFace = this.mesh.faces[faceIndex]; + var vertex1 = this.mesh.vertices[currentFace.a]; + var vertex2 = this.mesh.vertices[currentFace.b]; + var vertex3 = this.mesh.vertices[currentFace.c]; - if (this.faces[currentFace.index] === true) { + for (var configIndex = 0; configIndex < config.length; configIndex++) { - continue; + var currentConfig = config[configIndex]; - } - - mightBeCompletetlyFinished = false; - - var vertex1 = this.mesh.vertices[currentFace.a]; - var vertex2 = this.mesh.vertices[currentFace.b]; - var vertex3 = this.mesh.vertices[currentFace.c]; - - if (currentConfig.frustum !== undefined) { + if ( configSizes[configIndex] < chunk * currentConfig.proportion) { var display = false; var exitToContinue = false; - threeVertices = [vertex1, vertex2, vertex3]; + var threeVertices = [vertex1, vertex2, vertex3]; // Frustum culling - if (!isInFrustum(threeVertices, currentConfig.frustum.planes)) { - continue; + if (currentConfig.frustum === undefined || (isInFrustum(threeVertices, currentConfig.frustum.planes) && !this.isBackFace(currentConfig.frustum, currentFace))) { + + // Send face + if (!this.vertices[currentFace.a]) { + + data.push(vertex1.toList()); + this.vertices[currentFace.a] = true; + configSizes[configIndex]++; + + } + + if (!this.vertices[currentFace.b]) { + + data.push(vertex2.toList()); + this.vertices[currentFace.b] = true; + configSizes[configIndex]++; + + } + + if (!this.vertices[currentFace.c]) { + + data.push(vertex3.toList()); + this.vertices[currentFace.c] = true; + configSizes[configIndex]++; + + } + + var normal1 = this.mesh.normals[currentFace.aNormal]; + var normal2 = this.mesh.normals[currentFace.bNormal]; + var normal3 = this.mesh.normals[currentFace.cNormal]; + + if (normal1 !== undefined && !this.normals[currentFace.aNormal]) { + + data.push(normal1.toList()); + this.normals[currentFace.aNormal] = true; + configSizes[configIndex]++; + totalSize++; + + } + + if (normal2 !== undefined && !this.normals[currentFace.bNormal]) { + + data.push(normal2.toList()); + this.normals[currentFace.bNormal] = true; + configSizes[configIndex]++; + totalSize++; + + } + + if (normal3 !== undefined && !this.normals[currentFace.cNormal]) { + + data.push(normal3.toList()); + this.normals[currentFace.cNormal] = true; + configSizes[configIndex]++; + totalSize++; + + } + + var tex1 = this.mesh.texCoords[currentFace.aTexture]; + var tex2 = this.mesh.texCoords[currentFace.bTexture]; + var tex3 = this.mesh.texCoords[currentFace.cTexture]; + + if (tex1 !== undefined && !this.texCoords[currentFace.aTexture]) { + + data.push(tex1.toList()); + this.texCoords[currentFace.aTexture] = true; + configSizes[configIndex]++; + totalSize++; + + } + + if (tex2 !== undefined && !this.texCoords[currentFace.bTexture]) { + + data.push(tex2.toList()); + this.texCoords[currentFace.bTexture] = true; + configSizes[configIndex]++; + totalSize++; + + } + + if (tex3 !== undefined && !this.texCoords[currentFace.cTexture]) { + + data.push(tex3.toList()); + this.texCoords[currentFace.cTexture] = true; + configSizes[configIndex]++; + totalSize++; + + } + + data.push(currentFace.toList()); + // this.meshFaces[meshIndex] = this.meshFaces[meshIndex] || []; + this.faces[currentFace.index] = true; + configSizes[configIndex]+=3; + totalSize+=3; + // this.meshFaces[meshIndex].counter++; + // currentMesh.faceIndex++; + + // if (totalSize > chunk) { + + // // console.log(configIndex, sent/(chunk * currentConfig.proportion)); + // return {data: data, finsihed:false, configSizes: configSizes, size: totalSize}; + + // } + + // Loop on next face + continue faceloop; + } - - // Backface culling - if (this.isBackFace(currentConfig.frustum, currentFace)) { - continue; - } - } - if (!this.vertices[currentFace.a]) { + if (totalSize > chunk) { - data.push(vertex1.toList()); - this.vertices[currentFace.a] = true; - sent++; - - } - - if (!this.vertices[currentFace.b]) { - - data.push(vertex2.toList()); - this.vertices[currentFace.b] = true; - sent++; - - } - - if (!this.vertices[currentFace.c]) { - - data.push(vertex3.toList()); - this.vertices[currentFace.c] = true; - sent++; - - } - - var normal1 = this.mesh.normals[currentFace.aNormal]; - var normal2 = this.mesh.normals[currentFace.bNormal]; - var normal3 = this.mesh.normals[currentFace.cNormal]; - - if (normal1 !== undefined && !this.normals[currentFace.aNormal]) { - - data.push(normal1.toList()); - this.normals[currentFace.aNormal] = true; - sent++; - - } - - if (normal2 !== undefined && !this.normals[currentFace.bNormal]) { - - data.push(normal2.toList()); - this.normals[currentFace.bNormal] = true; - sent++; - - } - - if (normal3 !== undefined && !this.normals[currentFace.cNormal]) { - - data.push(normal3.toList()); - this.normals[currentFace.cNormal] = true; - sent++; - - } - - var tex1 = this.mesh.texCoords[currentFace.aTexture]; - var tex2 = this.mesh.texCoords[currentFace.bTexture]; - var tex3 = this.mesh.texCoords[currentFace.cTexture]; - - if (tex1 !== undefined && !this.texCoords[currentFace.aTexture]) { - - data.push(tex1.toList()); - this.texCoords[currentFace.aTexture] = true; - sent++; - - } - - if (tex2 !== undefined && !this.texCoords[currentFace.bTexture]) { - - data.push(tex2.toList()); - this.texCoords[currentFace.bTexture] = true; - sent++; - - } - - if (tex3 !== undefined && !this.texCoords[currentFace.cTexture]) { - - data.push(tex3.toList()); - this.texCoords[currentFace.cTexture] = true; - sent++; - - } - - data.push(currentFace.toList()); - // this.meshFaces[meshIndex] = this.meshFaces[meshIndex] || []; - this.faces[currentFace.index] = true; - // this.meshFaces[meshIndex].counter++; - // currentMesh.faceIndex++; - - sent++; - - if (data.length > this.chunk) { - - // console.log(configIndex, sent/(this.chunk * currentConfig.proportion)); - return {data: data, finsihed:false}; - - } - - if (sent > this.chunk * currentConfig.proportion) { - - // console.log(configIndex, sent/(this.chunk * currentConfig.proportion)); - continue configloop; + // console.log(configIndex, sent/(chunk * currentConfig.proportion)); + return {data: data, finsihed:false, configSizes: configSizes, size: totalSize}; } } - return {data: data, finished: mightBeCompletetlyFinished}; } - return {data: data, finished: mightBeCompletetlyFinished}; + return {data: data, finished: mightBeCompletetlyFinished, configSizes: configSizes}; };