diff --git a/controllers/prototype/index.js b/controllers/prototype/index.js index 70287d4..30ffc49 100644 --- a/controllers/prototype/index.js +++ b/controllers/prototype/index.js @@ -1,7 +1,8 @@ +var tools = require('../../my_modules/filterInt.js'); var pg = require('pg'); var pgc = require('../../private.js'); -var createNewId = function(req, callback) { +var createNewId = function(req, res, callback) { pg.connect(pgc.url, function(err, client, release) { client.query( "INSERT INTO users(name) VALUES('anonymous'); SELECT currval('users_id_seq');", @@ -16,6 +17,42 @@ var createNewId = function(req, callback) { }); } +var getPathFromId = function(req, res, callback, id) { + pg.connect(pgc.url, function(err, client, release) { + client.query( + "SELECT ((camera).position).x AS px, " + + "((camera).position).y AS py, " + + "((camera).position).z AS pz, " + + "((camera).target).x AS tx, " + + "((camera).target).y AS ty, " + + "((camera).target).z AS tz " + + "FROM keyboardevent WHERE user_id = $1;", + [id], + function(err, result) { + res.locals.path = []; + for (var i in result.rows) { + res.locals.path.push( + { + position : { + x: result.rows[i].px, + y: result.rows[i].py, + z: result.rows[i].pz + }, + target : { + x: result.rows[i].tx, + y: result.rows[i].ty, + z: result.rows[i].tz + } + } + ); + } + callback(); + release(); + } + ); + }); +} + module.exports.index = function(req, res) { res.setHeader('Content-Type', 'text/html'); @@ -25,7 +62,7 @@ module.exports.index = function(req, res) { } module.exports.arrows = function(req, res) { - createNewId(req, function() { + createNewId(req, res, function() { res.setHeader('Content-Type', 'text/html'); res.locals.cameraStyle = 'arrows'; @@ -37,7 +74,7 @@ module.exports.arrows = function(req, res) { } module.exports.viewports = function(req, res) { - createNewId(req, function() { + createNewId(req, res, function() { res.setHeader('Content-Type', 'text/html'); res.locals.cameraStyle = 'viewports'; @@ -49,7 +86,7 @@ module.exports.viewports = function(req, res) { } module.exports.reverse = function(req, res) { - createNewId(req, function() { + createNewId(req, res, function() { res.setHeader('Content-Type', 'text/html'); res.locals.cameraStyle = 'reverse'; @@ -59,3 +96,24 @@ module.exports.reverse = function(req, res) { }); }); } + +module.exports.replay_info = function(req, res) { + res.setHeader('Content-Type', 'text/plain'); + + // Parse id + var id = tools.filterInt(req.params.id); + + getPathFromId(req, res, function() { + res.send(JSON.stringify(res.locals.path)); + }, id); +} + +module.exports.replay = function(req, res) { + res.setHeader('Content-Type', 'text/html'); + res.locals.cameraStyle = "replay"; + res.locals.id = tools.filterInt(req.params.id); + res.render('prototype.jade', res.locals, function(err, result) { + res.send(result); + }); + +} diff --git a/controllers/prototype/urls.js b/controllers/prototype/urls.js index 8c9ecbd..0fb9a60 100644 --- a/controllers/prototype/urls.js +++ b/controllers/prototype/urls.js @@ -2,5 +2,7 @@ module.exports = { '/prototype': 'index', '/prototype/arrows': 'arrows', '/prototype/viewports': 'viewports', - '/prototype/reverse': 'reverse' + '/prototype/reverse': 'reverse', + '/prototype/replay/:id': 'replay', + '/prototype/replay_info/:id': 'replay_info' } diff --git a/controllers/prototype/views/prototype.jade b/controllers/prototype/views/prototype.jade index db86657..341b1ed 100644 --- a/controllers/prototype/views/prototype.jade +++ b/controllers/prototype/views/prototype.jade @@ -14,13 +14,21 @@ block extrajs script(src="/static/js/prototype/ButtonManager.js") script(src="/static/js/prototype/Coin.js") script(src="/static/js/Logger.js") - if cameraStyle == 'arrows' - script RecommendedCamera = FixedCamera; - else if cameraStyle == 'viewports' - script RecommendedCamera = OldFixedCamera; - else if cameraStyle == 'reverse' - script RecommendedCamera = ReverseCamera - script(src="/static/js/prototype/main.js") + + if cameraStyle == 'replay' + script var params = params || {}; params.get = params.get || {}; params.get.id = #{id}; + script(src="/static/js/ReplayCamera.js") + script(src="/static/js/prototype/replay.js") + else + if cameraStyle == 'arrows' + script RecommendedCamera = FixedCamera; + else if cameraStyle == 'viewports' + script RecommendedCamera = OldFixedCamera; + else if cameraStyle == 'reverse' + script RecommendedCamera = ReverseCamera + + script(src="/static/js/prototype/main.js") + script document.getElementById('music').volume = 0.5; block extrahead diff --git a/static/js/ReplayCamera.js b/static/js/ReplayCamera.js new file mode 100644 index 0000000..eae027c --- /dev/null +++ b/static/js/ReplayCamera.js @@ -0,0 +1,120 @@ +// class camera extends THREE.PerspectiveCamera +var ReplayCamera = function() { + THREE.PerspectiveCamera.apply(this, arguments); + + this.started = false; + this.counter = 0; + + this.position = new THREE.Vector3(); + this.target = new THREE.Vector3(); + this.new_position = new THREE.Vector3(); + this.new_target = new THREE.Vector3(); + + var id = params.get.id; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "/prototype/replay_info/" + id, true); + + (function(self) { + xhr.onreadystatechange = function() { + if (xhr.readyState == 4 && xhr.status == 200) { + self.path = JSON.parse(xhr.responseText); + self.position.x = self.path[0].position.x; + self.position.y = self.path[0].position.y; + self.position.z = self.path[0].position.z; + self.target = new THREE.Vector3( + self.path[0].target.x, + self.path[0].target.y, + self.path[0].target.z + ); + self.started = true; + self.move(self.path[++self.counter]); + } + } + })(this); + xhr.send(); + + // Set Position + this.theta = Math.PI; + this.phi = Math.PI; + + +} +ReplayCamera.prototype = Object.create(THREE.PerspectiveCamera.prototype); +ReplayCamera.prototype.constructor = ReplayCamera; + +ReplayCamera.prototype.look = function() { + this.lookAt(this.target); +} + +// Update function +ReplayCamera.prototype.update = function(time) { + if (this.started) + this.linearMotion(time); +} + +ReplayCamera.prototype.linearMotion = function(time) { + var tmp = Tools.sum(Tools.mul(this.old_position, 1-this.t), Tools.mul(this.new_position, this.t)); + this.position.x = tmp.x; + this.position.y = tmp.y; + this.position.z = tmp.z; + this.target = Tools.sum(Tools.mul(this.old_target, 1-this.t), Tools.mul(this.new_target, this.t)); + this.t += 0.1 * time / 20; + + if (this.t > 1) { + this.counter++; + if (this.counter < this.path.length) { + this.move(this.path[this.counter]); + } else { + this.started = false; + } + } +} + +ReplayCamera.prototype.reset = function() { + this.resetBobomb(); + this.moving = false; + this.movingHermite = false; + // this.position.copy(new THREE.Vector3(-8.849933489419644, 9.050627639459208, 0.6192960680432451)); + // this.target.copy(new THREE.Vector3(17.945323228767702, -15.156828589982375, -16.585740412769756)); + // this.anglesFromVectors(); +} + +ReplayCamera.prototype.resetBobomb = function() { + this.position.copy(new THREE.Vector3(34.51854618261728,10.038879540840306,-21.772598201888613)); + this.target.copy(new THREE.Vector3(-2.593404107644737,8.039712770013185,-6.983870133675925)); + this.anglesFromVectors(); +} + +ReplayCamera.prototype.vectorsFromAngles = function() { + // Update direction + this.forward.y = Math.sin(this.phi); + + var cos = Math.cos(this.phi); + this.forward.z = cos * Math.cos(this.theta); + this.forward.x = cos * Math.sin(this.theta); + this.forward.normalize(); + +} + +ReplayCamera.prototype.anglesFromVectors = function() { + // Update phi and theta so that return to reality does not hurt + var forward = Tools.diff(this.target, this.position); + forward.normalize(); + + this.phi = Math.asin(forward.y); + + // Don't know why this line works... But thanks Thierry-san and + // Bastien because it seems to work... + this.theta = Math.atan2(forward.x, forward.z); +} + +ReplayCamera.prototype.move = function(otherCamera) { + this.moving = true; + this.old_target = this.target.clone(); + this.old_position = this.position.clone(); + this.new_target = new THREE.Vector3(otherCamera.target.x, otherCamera.target.y, otherCamera.target.z); + this.new_position = new THREE.Vector3(otherCamera.position.x, otherCamera.position.y, otherCamera.position.z); + this.t = 0; + +} diff --git a/static/js/prototype/replay.js b/static/js/prototype/replay.js new file mode 100644 index 0000000..f774d46 --- /dev/null +++ b/static/js/prototype/replay.js @@ -0,0 +1,272 @@ +// 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 beenFullscreen = false; +var isFullscreen = false; +var previousTime; + +var main_section = document.getElementById('main-section'); +var offset = function() { + return + document.getElementById('nav').offsetHeight + + document.getElementById('main-div').offsetHeight; +} + +console.log(document.getElementById('main-div').offsetHeight); +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; + } +}; + +console.log(container_size.width(), container_size.height()); + +init(); +animate(); + +function init() { + // 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 = new THREE.WebGLRenderer({alpha:true, antialias:true}); + 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); + previewer.domElement.style.position ="absolute"; + previewer.domElement.style.cssFloat = 'top-left'; + previewer.domElement.width = container_size.width(); + previewer.domElement.height = container_size.height(); + + // Initialize scene + scene = new THREE.Scene(); + + // 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(renderer.domElement); + + // init light + var directional_light = new THREE.DirectionalLight(0xdddddd); + directional_light.position.set(1, 2.5, 1).normalize(); + directional_light.castShadow = false; + scene.add(directional_light); + + var ambient_light = new THREE.AmbientLight(0x555555); + scene.add(ambient_light); + + // Initialize pointer camera + var camera1 = new ReplayCamera(50, container_size.width() / container_size.height(), 0.01, 100000, container); + scene.add(camera1); + + // Initialize recommendations + var otherCams = createBobombCameras(container_size.width(), container_size.height()); + cameras = new CameraContainer(camera1, otherCams); + otherCams.forEach(function(cam) { cam.addToScene(scene); }); + + // Initalize loader + var loader = new THREE.OBJMTLLoader(); + + // Load scene + // initPeachCastle(scene, collidableObjects, loader, static_path); + initBobombScene(scene, collidableObjects, loader, static_path); + coins = createBobombCoins(); + + setTimeout(function() {coins.forEach(function(coin) { coin.addToScene(scene);})}, 1000); + + // 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, cameras, buttonManager); +} + +function fullscreen() { + 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"; + + // canvas.style.position = "absolute"; + // canvas.style.cssFloat = "top-left"; + // canvas.style.top = "0px"; + // canvas.style.bottom = "0px"; + // canvas.style.left = "0px"; + // canvas.style.right = "0px"; + // canvas.width=window.innerWidth; + // canvas.height=window.innerHeight; + // canvas.style.overflow = "hidden"; + + onWindowResize(); +} + +function stopFullscreen() { + isFullscreen = false; + + 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"; + + // canvas.style.position = ""; + // canvas.style.cssFloat = ""; + // canvas.style.top = ""; + // canvas.style.bottom = ""; + // canvas.style.left = ""; + // canvas.style.right = ""; + // canvas.width = container_size.width(); + // canvas.height = container_size.height(); + // canvas.style.overflow = ""; + + onWindowResize(); +} + +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;}); +} +