From 99019c217fdef8a673ac118efe00b60c1fb13c38 Mon Sep 17 00:00:00 2001 From: Thomas FORGIONE Date: Thu, 11 Jun 2015 17:22:37 +0200 Subject: [PATCH] Working on progressive loader that supports textures and materials --- geo/Mesh.js | 48 +++- js/ProgressiveLoader.js | 115 +++++++-- js/prototype/initScene.js | 61 +++-- js/prototype/sponza.js | 243 ++++++++++++++++++ js/stream/main.js | 2 +- js/three/MTLLoader.js | 1 + socket.js | 49 ++-- .../princess peaches castle (outside).obj | 2 +- 8 files changed, 429 insertions(+), 92 deletions(-) create mode 100644 js/prototype/sponza.js diff --git a/geo/Mesh.js b/geo/Mesh.js index 2c0beb3..b6cebe7 100644 --- a/geo/Mesh.js +++ b/geo/Mesh.js @@ -56,6 +56,11 @@ geo.MeshStreamer.prototype.loadFromFile = function(path, callback) { var face = self.faces[self.faces.push(new geo.Face(line)) - 1]; self.orderedElements.push(face); + } else if (line[0] === 'u') { + + // usemtl + self.orderedElements.push(new geo.Usemtl(line)); + } } @@ -67,7 +72,7 @@ geo.MeshStreamer.prototype.loadFromFile = function(path, callback) { } geo.MeshStreamer.prototype.tryMerge = function() { - if (this.faces[0].aTexture) { + if (this.faces[0].aTexture !== undefined) { return this.orderedElements; } else { return this.merge(); @@ -89,7 +94,6 @@ geo.MeshStreamer.prototype.merge = function(callback) { } - // For each face for (var i = 0; i < this.faces.length; i++) { @@ -175,11 +179,11 @@ geo.TextureCoord = function() { } geo.TextureCoord.prototype.toList = function() { - return ['v', this.x, this.y]; + return ['vt', this.x, this.y]; } geo.TextureCoord.prototype.toString = function() { - return 'v ' + this.x + ' ' + this.y; + return 'vt ' + this.x + ' ' + this.y; } geo.Face = function() { @@ -192,7 +196,7 @@ geo.Face = function() { this.c = parseInt(split[3]) - 1; } else { // There might be textures coords - var split = arugments[0].split(' '); + var split = arguments[0].split(' '); // Split elements var split1 = split[1].split('/'); @@ -202,9 +206,15 @@ geo.Face = function() { var vIndex = 0; var tIndex = split1.length === 2 ? 1 : 2; - this.a = split1[vIndex]; this.aTexture = split1[tIndex]; - this.b = split2[vIndex]; this.bTexture = split2[tIndex]; - this.c = split3[vIndex]; this.cTexture = split3[tIndex]; + this.a = split1[vIndex] - 1; this.aTexture = split1[tIndex] - 1; + this.b = split2[vIndex] - 1; this.bTexture = split2[tIndex] - 1; + this.c = split3[vIndex] - 1; this.cTexture = split3[tIndex] - 1; + + if (split.length === 5) { + var split4 = split[4].split('/'); + + this.d = split3[vIndex] - 1; this.dTexture = split3[tIndex] - 1; + } } if (split.length === 5) @@ -233,6 +243,15 @@ geo.Face.prototype.toList = function() { var l = ['f', this.a, this.b, this.c]; if (this.d) l.push(this.d); + + if (this.aTexture) { + l.push(this.aTexture); + l.push(this.bTexture); + l.push(this.cTexture); + } + + if (this.dTexture) + l.push(this.aTexture); return l; } @@ -240,4 +259,17 @@ geo.Face.prototype.toString = function() { return 'f ' + this.a + ' ' + this.b + ' ' + this.c + (this.d ? ' ' + this.d : ''); } +geo.Usemtl = function() { + var split = arguments[0].split(' '); + this.name = split[1]; +} + +geo.Usemtl.prototype.toString = function() { + return 'usemtl ' + this.name; +} + +geo.Usemtl.prototype.toList = function() { + return ['u', this.name]; +} + module.exports = geo; diff --git a/js/ProgressiveLoader.js b/js/ProgressiveLoader.js index ef748f7..174006d 100644 --- a/js/ProgressiveLoader.js +++ b/js/ProgressiveLoader.js @@ -1,22 +1,24 @@ -var ProgressiveLoader = function(res, scene) { +var ProgressiveLoader = function(path, scene, materialCreator) { // Create mesh - var geometry = new THREE.Geometry(); - geometry.dynamic = true; + var obj = new THREE.Object3D(); + obj.up = new THREE.Vector3(0,0,1); + glob = obj; - var material = new THREE.MeshLambertMaterial(); - material.color.setRGB(1,0,0); - material.side = THREE.DoubleSide; - - var mesh = new THREE.Mesh(geometry, material); - mesh.up = new THREE.Vector3(0,0,1); + var currentMesh; + var currentMaterial; var added = false; var finished = false; var socket = io(); + var vertices = []; + var textCoords = []; + var uvs = []; + var faces = []; + // Init streaming with socket - socket.emit('request', res); + socket.emit('request', path); // When server's ready, start asking for the mesh socket.on('ok', function() { @@ -42,29 +44,33 @@ var ProgressiveLoader = function(res, scene) { if (elts[0] === 'v') { - mesh.geometry.vertices.push(new THREE.Vector3( + vertices.push(new THREE.Vector3( elts[1], elts[2], elts[3] )); - mesh.geometry.verticesNeedUpdate = true; + verticesNeedUpdate = true; + + } else if (elts[0] === 'vt') { + + textCoords.push(new THREE.Vector2( + elts[1], + elts[2] + )); } else if (elts[0] === 'f') { - if (elts[4]) - elts[4] = parseInt(elts[4]); - - mesh.geometry.faces.push(new THREE.Face3( + faces.push(new THREE.Face3( elts[1], elts[2], elts[3] )); // If the face has 4 vertices, create second triangle - if (elts[4]) { + if (elts.length === 5 || elts.length === 9) { - mesh.geometry.faces.push(new THREE.Face3( + faces.push(new THREE.Face3( elts[1], elts[3], elts[4] @@ -72,20 +78,79 @@ var ProgressiveLoader = function(res, scene) { } - // Add mesh to scene one there are a few faces in it + // Add texture + if (elts.length === 7 || elts.length === 9) { + uvs.push([ + textCoords[elts[4]], + textCoords[elts[5]], + textCoords[elts[6]] + ]); + } + + if (elts.length === 9) { + uvs.push([ + textCoords[elts[5]], + textCoords[elts[7]], + textCoords[elts[8]] + ]); + } + + // Add currentMesh to scene one there are a few faces in it if (!added) { - scene.add(mesh); + scene.add(obj); added = true; } + if (!currentMesh) { + var geo = new THREE.Geometry(); + geo.vertices = vertices; + geo.faces = faces; + + var material; + if (currentMaterial === undefined || currentMaterial === null) { + material = new THREE.MeshLambertMaterial({color: 'red'}); + } else { + material = materialCreator.create(currentMaterial); + currentMaterial = null; + } + currentMesh = new THREE.Mesh(geo, material); + } + obj.add(currentMesh); + + } else if (elts[0] === 'u') { + + // Add current mesh + // if (currentMesh) { + // obj.add(currentMesh); + // } + + // Prepare new mesh + faces = []; + uvs = []; + // currentMesh.geometry.computeFaceNormals(); + if (currentMesh) { + currentMesh.geometry.groupsNeedUpdate = true; + currentMesh.geometry.elementsNeedUpdate = true; + currentMesh.geometry.normalsNeedUpdate = true; + currentMesh.geometry.uvsNeedUpdate = true; + } + currentMaterial = elts[1]; + currentMesh = null; + + // console.log(material.map); + // currentMesh = new THREE.Mesh(geo, material); + } } - mesh.geometry.computeFaceNormals(); - mesh.geometry.groupsNeedUpdate = true; - mesh.geometry.elementsNeedUpdate = true; - mesh.geometry.normalsNeedUpdate = true; + if (currentMesh) { + currentMesh.geometry.computeFaceNormals(); + currentMesh.geometry.groupsNeedUpdate = true; + currentMesh.geometry.elementsNeedUpdate = true; + currentMesh.geometry.normalsNeedUpdate = true; + currentMesh.geometry.uvsNeedUpdate = true; + } },0); }); @@ -95,5 +160,5 @@ var ProgressiveLoader = function(res, scene) { finished = true; }); - return mesh; + return obj; } diff --git a/js/prototype/initScene.js b/js/prototype/initScene.js index e4e5a98..a3c7f5d 100644 --- a/js/prototype/initScene.js +++ b/js/prototype/initScene.js @@ -22,6 +22,14 @@ function initPeachCastle(scene, collidableObjects, loader, static_path) { static_path = "/static/"; } + // var loader = new THREE.MTLLoader('/static/data/castle/'); + // loader.load('/static/data/castle/princess peaches castle (outside).mtl', function(materialCreator) { + + // materialCreator.preload(); + + // var mesh = ProgressiveLoader('static/data/castle/princess peaches castle (outside).obj', scene, materialCreator); + // }); + loader.load( static_path + 'data/castle/princess peaches castle (outside).obj', static_path + 'data/castle/princess peaches castle (outside).mtl', @@ -53,34 +61,34 @@ function initPeachCastle(scene, collidableObjects, loader, static_path) { } ); - loader.load( - static_path + 'data/first/Floor 1.obj', - static_path + 'data/first/Floor 1.mtl', - function ( object ) { - object.position.z -= 10.9; - object.position.y += 0.555; - object.position.x += 3.23; + // loader.load( + // static_path + 'data/first/Floor 1.obj', + // static_path + 'data/first/Floor 1.mtl', + // function ( object ) { + // object.position.z -= 10.9; + // object.position.y += 0.555; + // object.position.x += 3.23; - var theta = 0.27; - object.rotation.y = Math.PI - theta; + // var theta = 0.27; + // object.rotation.y = Math.PI - theta; - object.up = new THREE.Vector3(0,0,1); - scene.add(object); - collidableObjects.push(object); - object.traverse(function (object) { - if (object instanceof THREE.Mesh) { - object.material.side = THREE.DoubleSide; - object.geometry.mergeVertices(); - object.geometry.computeVertexNormals(); - object.raycastable = true; - if (object.material.name === 'Material.054_777F0E0B_c.bmp' || - object.material.name === 'Material.061_5C3492AB_c.bmp' ) { - THREEx.Transparency.push(object); - } - } - }); - } - ); + // object.up = new THREE.Vector3(0,0,1); + // scene.add(object); + // collidableObjects.push(object); + // object.traverse(function (object) { + // if (object instanceof THREE.Mesh) { + // object.material.side = THREE.DoubleSide; + // object.geometry.mergeVertices(); + // object.geometry.computeVertexNormals(); + // object.raycastable = true; + // if (object.material.name === 'Material.054_777F0E0B_c.bmp' || + // object.material.name === 'Material.061_5C3492AB_c.bmp' ) { + // THREEx.Transparency.push(object); + // } + // } + // }); + // } + // ); } function resetPeachElements() { @@ -265,6 +273,7 @@ function initBobombScene(scene, collidableObjects, loader, static_path) { object.up = new THREE.Vector3(0,0,1); collidableObjects.push(object); scene.add(object); + glob = object; object.traverse(function (object) { if (object instanceof THREE.Mesh) { object.raycastable = true; diff --git a/js/prototype/sponza.js b/js/prototype/sponza.js new file mode 100644 index 0000000..3555459 --- /dev/null +++ b/js/prototype/sponza.js @@ -0,0 +1,243 @@ +var isFullscreen = false; +var beenFullscreen = false; + +var main_section = document.getElementById('main-section'); +var container_size = { + width: function() { if (!isFullscreen) return main_section.clientWidth; else return screen.width;}, + height: function() { + if (!isFullscreen) + return main_section.clientHeight + - document.getElementById('nav').offsetHeight + - document.getElementById('main-div').offsetHeight; + else + return screen.height; + } +}; + +// Let's be sure we avoid using global variables +var onWindowResize = (function() { + +// Disable scrolling +window.onscroll = function () { window.scrollTo(0, 0); }; + +var mesh_number = 25; +var renderer, scene, controls, cube, container, plane, mouse= {x:0, y:0}; +var bigmesh; +var raycaster; +var objects = []; +var cameras, cameraSelecter; +var spheres = new Array(mesh_number); +var visible = 0; +var stats; +var previewer; + +var loader; +var coins = []; +var previousTime; + + + +init(); +animate(); + +function init() { + BD.disable(); + + // Initialize scene + scene = new THREE.Scene(); + renderer = new THREE.WebGLRenderer({alpha:true, antialias:true}); + + // Collidable objects to prevent camera from traversing objects + var collidableObjects = new Array(); + + // Initialize renderer + container = document.getElementById('container'); + container.style.height = container_size.height() + 'px'; + container.style.width = container_size.width() + 'px'; + renderer.setSize(container_size.width(), container_size.height()); + // renderer.setSize(container_size.width(), container_size.height()); + renderer.shadowMapEnabled = true; + renderer.setClearColor(0x87ceeb); + + // Initialize previewer + previewer = new Previewer(renderer, scene); + previewer.domElement.style.position ="absolute"; + previewer.domElement.style.cssFloat = 'top-left'; + previewer.domElement.width = container_size.width(); + previewer.domElement.height = container_size.height(); + + // Initialize stats counter + stats = new Stats(); + stats.setMode(0); + stats.domElement.style.position = 'absolute'; + stats.domElement.style.cssFloat = "top-left"; + + // Add elements to page + container.appendChild( stats.domElement ); + container.appendChild(previewer.domElement); + container.appendChild(Coin.domElement); + container.appendChild(renderer.domElement); + + // Initialize pointer camera + var camera1 = new PointerCamera(50, container_size.width() / container_size.height(), 0.1, 100000, renderer, container); + + // cameras = initMainScene(camera1, scene, static_path, coins); + // cameras = initPeach(camera1, scene, static_path, coins); + // cameras = initBobomb(camera1, scene, static_path, coins); + // cameras = initWhomp(camera1, scene, static_path, coins); + // cameras = initMountain(camera1, scene, static_path, coins); + cameras = initSponza(camera1, scene, static_path, coins); + + // Add listeners + initListeners(); +} + +function initListeners() { + window.addEventListener('resize', onWindowResize, false); + + // Transmit click event to camera selecter + container.addEventListener('mousedown', function(event) { + if (event.which == 1) + cameraSelecter.click(event); + }, false + ); + + // Update camera selecter when mouse moved + container.addEventListener('mousemove', function(event) { + cameraSelecter.update(event); + }, false + ); + + // Escape key to exit fullscreen mode + document.addEventListener('keydown', function(event) { if (event.keyCode == 27) { stopFullscreen();} }, false); + + // HTML Bootstrap buttons + buttonManager = new ButtonManager(cameras, previewer); + + // Camera selecter for hover and clicking recommendations + cameraSelecter = new CameraSelecter(renderer, scene, cameras, coins, buttonManager); +} + +function render() { + cameraSelecter.update(); + + // Update recommendations (set raycastable if shown) + var transform = buttonManager.showArrows ? show : hide; + cameras.map(function(camera) { + if (camera instanceof RecommendedCamera) { + transform(camera); + + camera.traverse(function(elt) { + elt.raycastable = buttonManager.showArrows; + }); + } + }); + + // Update coins + coins.forEach(function(coin) { coin.update(); }); + + // Update main camera + var currentTime = Date.now() - previousTime; + cameras.updateMainCamera(isNaN(currentTime) ? 20 : currentTime); + previousTime = Date.now(); + + // Update the recommendations + cameras.update(cameras.mainCamera()); + + + // Set current position of camera + cameras.look(); + + var left = 0, bottom = 0, width = container_size.width(), height = container_size.height(); + renderer.setScissor(left, bottom, width, height); + renderer.enableScissorTest(true); + renderer.setViewport(left, bottom, width, height); + renderer.render(scene, cameras.mainCamera()); + + // Remove borders of preview + previewer.clear(); + + // Hide arrows in recommendation + cameras.map(function(camera) { if (camera instanceof RecommendedCamera) hide(camera); }); + + // Render preview + previewer.render(cameraSelecter.prev, container_size.width(), container_size.height()); +} + +function animate() { + // Render each frame + requestAnimationFrame(animate); + + // stats count the number of frames per second + stats.begin(); + render(); + stats.end(); +} + +function onWindowResize() { + + container.style.width = container_size.width() + "px"; + container.style.height = container_size.height() + "px"; + + previewer.domElement.width = container_size.width(); + previewer.domElement.height = container_size.height(); + + renderer.setSize(container_size.width(), container_size.height()); + cameras.forEach(function(camera) {camera.aspect = container_size.width() / container_size.height();}); + cameras.forEach(function(camera) {camera.updateProjectionMatrix();}); + render(); +} + +function hide(object) { + object.traverse(function(object) {object.visible = false;}); +} + +function show(object) { + object.traverse(function(object) {object.visible = true;}); +} + +// onWindowResize will be the only global function +return onWindowResize; + +})(); + +function fullscreen() { + var container = document.getElementById('container'); + isFullscreen = true; + + if (!beenFullscreen) { + beenFullscreen = true; + alert('To quit fullscren mode, type ESC key'); + } + + container.style.position = "absolute"; + container.style.cssFloat = "top-left"; + container.style.top = "50px"; + container.style.bottom = "0px"; + container.style.left = "0px"; + container.style.right = "0px"; + container.style.width=""; + container.style.height=""; + container.style.overflow = "hidden"; + + onWindowResize(); +} + +function stopFullscreen() { + isFullscreen = false; + + var container = document.getElementById('container'); + + container.style.position = ""; + container.style.cssFloat = ""; + container.style.top = ""; + container.style.bottom = ""; + container.style.left = ""; + container.style.right = ""; + container.style.width = container_size.width() + "px"; + container.style.height = container_size.height() + "px"; + + onWindowResize(); +} + + diff --git a/js/stream/main.js b/js/stream/main.js index c7c4c37..c705add 100644 --- a/js/stream/main.js +++ b/js/stream/main.js @@ -45,7 +45,7 @@ function init() { // Load the scene // loader = new THREE.OBJLoader(); - sphere = ProgressiveLoader(params.get.res, scene); + sphere = ProgressiveLoader('static/data/spheres/' + params.get.res + '.obj', scene); plane = new Plane(1000,1000); plane.translate(0,0,-100); diff --git a/js/three/MTLLoader.js b/js/three/MTLLoader.js index 784c93c..b846b1b 100644 --- a/js/three/MTLLoader.js +++ b/js/three/MTLLoader.js @@ -313,6 +313,7 @@ THREE.MTLLoader.MaterialCreator.prototype = { params[ 'map' ] = this.loadTexture( this.baseUrl + value ); params[ 'map' ].wrapS = this.wrap; params[ 'map' ].wrapT = this.wrap; + map = params['map']; break; diff --git a/socket.js b/socket.js index 464a982..e3e1202 100644 --- a/socket.js +++ b/socket.js @@ -2,34 +2,6 @@ var fs = require('fs'); var sleep = require('sleep'); var geo = require('./geo/Mesh.js'); -function parseLine(line) { - var elts = line.split(' '); - if (elts[0] === 'v') { - - return [ - 'v', - parseFloat(elts[1]), - parseFloat(elts[2]), - parseFloat(elts[3]) - ]; - - } else if (elts[0] === 'f') { - - var tmp = [ - 'f', - parseInt(elts[1]) - 1, - parseInt(elts[2]) - 1, - parseInt(elts[3]) - 1 - ]; - - if (elts[4]) { - tmp.push(parseInt(elts[4]) - 1); - } - - return tmp; - } -} - module.exports = function(io) { io.on('connection', function(socket) { @@ -44,10 +16,15 @@ module.exports = function(io) { // console.log(socket.conn.remoteAddress + " disconnected !"); // }); - socket.on("request", function(res) { + socket.on("request", function(path) { // console.log('Asking for static/data/spheres/' + res + '.obj'); - var path = 'static/data/spheres/' + res + '.obj'; + var regex = /.*\.\..*/; + + if (regex.test(path)) { + socket.emit('error'); + socket.disconnect(); + } mesh = new geo.MeshStreamer(path, function() { socket.emit('ok'); @@ -57,15 +34,25 @@ module.exports = function(io) { // console.log(mesh.orderedElements[i].toString()); // } + // var counter = 0; + // for (var i = 0; i < mesh.orderedElements.length; i++) { + // if (mesh.orderedElements[i] instanceof geo.Usemtl) { + // counter++; + // } + // } + // console.log(counter); + }); }); socket.on('next', function() { + + var bound = 100; var toSend = []; var elt; - for (var limit = mesh.index + 200; mesh.index < limit; mesh.index++) { + for (var limit = mesh.index + bound; mesh.index < limit; mesh.index++) { elt = mesh.orderedElements[mesh.index]; diff --git a/static/data/castle/princess peaches castle (outside).obj b/static/data/castle/princess peaches castle (outside).obj index 3caf768..44f49fb 100644 --- a/static/data/castle/princess peaches castle (outside).obj +++ b/static/data/castle/princess peaches castle (outside).obj @@ -2340,7 +2340,7 @@ f 377/560 460/561 385/562 f 460/561 377/560 461/563 f 462/564 336/565 463/562 f 336/565 462/564 337/566 -usemtl Material.104 +# usemtl Material.104 # f 735/107 468/108 470/109 # f 594/107 593/108 464/109 # f 593/107 595/108 464/109