diff --git a/js/TutoCamera.js b/js/TutoCamera.js new file mode 100644 index 0000000..493e9e0 --- /dev/null +++ b/js/TutoCamera.js @@ -0,0 +1,400 @@ +// class camera extends THREE.PerspectiveCamera +var TutoCamera = function() { + THREE.PerspectiveCamera.apply(this, arguments); + + if (arguments[4] === undefined) + listenerTarget = document; + else + listenerTarget = arguments[4]; + + // Set Position + this.theta = Math.PI; + this.phi = Math.PI; + + // this.keyboard = undefined; + this.moving = false; + + this.dragging = false; + this.mouse = {x: 0, y: 0}; + this.mouseMove = {x: 0, y: 0}; + + + // Stuff for rendering + this.position = new THREE.Vector3(); + this.forward = new THREE.Vector3(); + this.left = new THREE.Vector3(); + this.target = new THREE.Vector3(0,1,0); + + // Stuff for events + this.motion = {}; + + this.sensitivity = 0.05; + this.speed = 1; + + // Raycaster for collisions + this.raycaster = new THREE.Raycaster(); + + // Create history object + this.history = new History(); + + // Set events from the document + var self = this; + var onKeyDown = function(event) {self.onKeyDown(event);}; + var onKeyUp = function(event) {self.onKeyUp(event);}; + var onMouseDown = function(event) {self.onMouseDown(event); }; + var onMouseMove = function(event) {self.onMouseMove(event); }; + var onMouseUp = function(event) {self.onMouseUp(event); }; + + document.addEventListener('keydown', onKeyDown, false); + document.addEventListener('keyup', onKeyUp, false); + listenerTarget.addEventListener('mousedown', function(event) { if (event.which == 1) onMouseDown(event);}, false); + listenerTarget.addEventListener('mousemove', function(event) { if (event.which == 1) onMouseMove(event);}, false); + listenerTarget.addEventListener('mouseup', onMouseUp, false); + // listenerTarget.addEventListener('mouseup', function() { console.log("mouseup");}, false); + // listenerTarget.addEventListener('mouseout', onMouseUp, false); + + this.collisions = true; + + this.resetElements = resetBobombElements(); + + // Create tutorial + this.tutorial = new TutorialSteps(this); +} +TutoCamera.prototype = Object.create(THREE.PerspectiveCamera.prototype); +TutoCamera.prototype.constructor = TutoCamera; + +// Update function +TutoCamera.prototype.update = function(time) { + if (this.moving) { + this.linearMotion(time); + } else if (this.movingHermite) { + this.hermiteMotion(time); + } else { + this.normalMotion(time); + } +} + +TutoCamera.prototype.linearMotion = function(time) { + var position_direction = Tools.diff(this.new_position, this.position); + var target_direction = Tools.diff(this.new_target, this.target); + + this.position.add(Tools.mul(position_direction, 0.05 * time / 20)); + this.target.add(Tools.mul(target_direction, 0.05 * time / 20)); + + if (Tools.norm2(Tools.diff(this.position, this.new_position)) < 0.01 && + Tools.norm2(Tools.diff(this.target, this.new_target)) < 0.01) { + this.moving = false; + this.anglesFromVectors(); + } +} + +TutoCamera.prototype.hermiteMotion = function(time) { + var eval = this.hermitePosition.eval(this.t); + this.position.x = eval.x; + this.position.y = eval.y; + this.position.z = eval.z; + + this.target = Tools.sum(this.position, this.hermiteAngles.eval(this.t)); + + this.t += 0.01 * time / 20; + + if (this.t > 1) { + this.movingHermite = false; + this.anglesFromVectors(); + } +} + +TutoCamera.prototype.normalMotion = function(time) { + // Update angles + if (this.motion.increasePhi) {this.phi += this.sensitivity; this.changed = true; } + if (this.motion.decreasePhi) {this.phi -= this.sensitivity; this.changed = true; } + if (this.motion.increaseTheta) {this.theta += this.sensitivity; this.changed = true; } + if (this.motion.decreaseTheta) {this.theta -= this.sensitivity; this.changed = true; } + + if (this.dragging) { + this.theta += this.mouseMove.x; + this.phi -= this.mouseMove.y; + + this.mouseMove.x = 0; + this.mouseMove.y = 0; + + this.changed = true; + } + + // Clamp phi and theta + this.phi = Math.min(Math.max(-(Math.PI/2-0.1),this.phi), Math.PI/2-0.1); + this.theta = ((this.theta - Math.PI) % (2*Math.PI)) + Math.PI; + + // Compute vectors (position and target) + this.vectorsFromAngles(); + + // Update with events + var delta = 0.1; + var forward = this.forward.clone(); + forward.multiplyScalar(400.0 * delta); + var left = this.up.clone(); + left.cross(forward); + left.normalize(); + left.multiplyScalar(400.0 * delta); + + // Move only if no collisions + var speed = this.speed * time / 20; + var direction = new THREE.Vector3(); + + if (this.motion.boost) speed *= 10; + if (this.motion.moveForward) {direction.add(Tools.mul(forward, speed)); this.changed = true;} + if (this.motion.moveBackward) {direction.sub(Tools.mul(forward, speed)); this.changed = true;} + if (this.motion.moveLeft) {direction.add(Tools.mul(left, speed)); this.changed = true;} + if (this.motion.moveRight) {direction.sub(Tools.mul(left, speed)); this.changed = true;} + + if (!this.collisions || !this.isColliding(direction)) { + this.position.add(direction); + } + + // Update angle + this.target = this.position.clone(); + this.target.add(forward); +} + +TutoCamera.prototype.reset = function() { + this.resetPosition(); + this.moving = false; + this.movingHermite = false; + (new BD.Event.ResetClicked()).send(); +} + +TutoCamera.prototype.resetPosition = function() { + this.position.copy(this.resetElements.position); + this.target.copy(this.resetElements.target); + this.anglesFromVectors(); +} + +TutoCamera.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(); + +} + +TutoCamera.prototype.anglesFromVectors = function() { + 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); +} + +TutoCamera.prototype.move = function(otherCamera, toSave) { + if (toSave === undefined) + toSave = true; + + this.moving = true; + this.new_target = otherCamera.target.clone(); + this.new_position = otherCamera.position.clone(); + var t = [0,1]; + var f = [this.position.clone(), this.new_position]; + var fp = [Tools.diff(this.target, this.position), Tools.diff(this.new_target, this.new_position)]; + this.hermite = new Hermite.Polynom(t,f,fp); + this.t = 0; + + if (toSave) { + if (this.changed) { + this.save(); + this.changed = false; + } + this.history.addState({position: otherCamera.position.clone(), target: otherCamera.target.clone()}); + } +} + +TutoCamera.prototype.moveHermite = function(otherCamera, toSave) { + if (toSave === undefined) + toSave = true; + + this.movingHermite = true; + this.t = 0; + + this.hermitePosition = new Hermite.special.Polynom( + this.position.clone(), + otherCamera.position.clone(), + Tools.mul(Tools.diff(otherCamera.target, otherCamera.position).normalize(),4) + ); + + this.hermiteAngles = new Hermite.special.Polynom( + Tools.diff(this.target, this.position), + Tools.diff(otherCamera.target, otherCamera.position), + new THREE.Vector3() + ); + + if (toSave) { + if (this.changed) { + this.save(); + this.changed = false; + } + this.history.addState({position: otherCamera.position.clone(), target: otherCamera.target.clone()}); + } +} + +TutoCamera.prototype.isColliding = function(direction) { + this.raycaster.set(this.position, direction.clone().normalize()); + var intersects = this.raycaster.intersectObjects(this.collidableObjects, true); + + for (var i in intersects) { + if (intersects[i].distance < 100*this.speed) { + return true; + } + } + + return false; +} + +// Look function +TutoCamera.prototype.look = function() { + this.lookAt(this.target); +} + +TutoCamera.prototype.addToScene = function(scene) { + scene.add(this); +} + +TutoCamera.prototype.onKeyEvent = function(event, toSet) { + // Create copy of state + var motionJsonCopy = JSON.stringify(this.motion); + + if (this.allowed.keyboardTranslate) { + switch ( event.keyCode ) { + // Azerty keyboards + case 38: case 90: this.motion.moveForward = toSet; break; // up / z + case 37: case 81: this.motion.moveLeft = toSet; break; // left / q + case 40: case 83: this.motion.moveBackward = toSet; break; // down / s + case 39: case 68: this.motion.moveRight = toSet; break; // right / d + case 32: this.motion.boost = toSet; break; + + // Qwerty keyboards + case 38: case 87: this.motion.moveForward = toSet; break; // up / w + case 37: case 65: this.motion.moveLeft = toSet; break; // left / a + case 40: case 83: this.motion.moveBackward = toSet; break; // down / s + case 39: case 68: this.motion.moveRight = toSet; break; // right / d + } + } + + if (this.allowed.keyboardRotate) { + if (this.tutorial.nextAction() === 'rotate-keyboard') { + this.tutorial.nextStep(); + } + switch ( event.keyCode ) { + case 73: case 104: this.motion.increasePhi = toSet; break; // 8 Up for angle + case 75: case 98: this.motion.decreasePhi = toSet; break; // 2 Down for angle + case 74: case 100: this.motion.increaseTheta = toSet; break; // 4 Left for angle + case 76: case 102: this.motion.decreaseTheta = toSet; break; // 6 Right for angle + } + } + + switch (event.keyCode) { + case 13: if (toSet) this.log(); break; + } + + if (motionJsonCopy != JSON.stringify(this.motion)) { + // Log any change + var event = new BD.Event.KeyboardEvent(); + event.camera = this; + event.send(); + } +} + +TutoCamera.prototype.onKeyDown = function(event) { + this.onKeyEvent(event, true); +} + +TutoCamera.prototype.onKeyUp = function(event) { + this.onKeyEvent(event, false); +} + +TutoCamera.prototype.onMouseDown = function(event) { + this.mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1; + this.mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1; + + if (this.allowed.mouseRotate) { + this.dragging = true; + this.mouseMoved = false; + } +} + +TutoCamera.prototype.onMouseMove = function(event) { + if (this.dragging) { + var mouse = {x: this.mouse.x, y: this.mouse.y}; + this.mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1; + this.mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1; + + this.mouseMove.x = this.mouse.x - mouse.x; + this.mouseMove.y = this.mouse.y - mouse.y; + this.mouseMoved = true; + + if (this.tutorial.nextAction() === 'rotate-mouse') { + this.tutorial.nextStep(); + } + } +} + +TutoCamera.prototype.onMouseUp = function(event) { + this.onMouseMove(event); + + // Send log to DB + if (this.dragging && this.mouseMoved && !this.moving && !this.movingHermite) { + var event = new BD.Event.KeyboardEvent(); + event.camera = this; + event.send(); + } + + this.dragging = false; +} + +TutoCamera.prototype.log = function() { + console.log("createCamera(\nnew THREE.Vector3(" + this.position.x + "," + this.position.y + ',' + this.position.z + '),\n' + + "new THREE.Vector3(" + this.target.x + "," + this.target.y + ',' + this.target.z + ')\n)'); +} + +TutoCamera.prototype.save = function() { + var backup = {}; + backup.position = this.position.clone(); + backup.target = this.target.clone(); + this.history.addState(backup); +} + +TutoCamera.prototype.undo = function() { + var move = this.history.undo(); + if (move !== undefined) { + var event = new BD.Event.PreviousNextClicked(); + event.previous = true; + event.camera = move; + event.send(); + + this.move(move, false); + } +} + +TutoCamera.prototype.redo = function() { + var move = this.history.redo(); + if (move !== undefined) { + var event = new BD.Event.PreviousNextClicked(); + event.previous = false; + event.camera = move; + event.send(); + + this.move(move, false); + } +} + +TutoCamera.prototype.undoable = function() { + return this.history.undoable(); +} + +TutoCamera.prototype.redoable = function() { + return this.history.redoable(); +} diff --git a/js/compiler.sh b/js/compiler.sh new file mode 100755 index 0000000..de38a0a --- /dev/null +++ b/js/compiler.sh @@ -0,0 +1,22 @@ +#!/usr/bin/bash +args="" + +while [[ $# > 0 ]]; do + key="$1" + + case $key in + --js) args="$args $2" + shift + ;; + + --js_output_file) + output="$2" + shift + ;; + esac + + shift + +done + +cat $args > $output diff --git a/js/prototype/TutorialSteps.js b/js/prototype/TutorialSteps.js new file mode 100644 index 0000000..4297746 --- /dev/null +++ b/js/prototype/TutorialSteps.js @@ -0,0 +1,65 @@ +var TutorialSteps = function(tutoCamera) { + this.camera = tutoCamera; + this.step = 0; + this.camera.allowed = {}; + + this.instructions = [ + { + text:"Welcome to this tutorial ! Click on the right arrow to go to the next instruction !", + justclick:true + }, + { + text:"You can use your mouse (drag'n'drop) to rotate the camera", + justclick:false + }, + { + text:"Nice ! You can also use (2,4,6 and 8) keys or (k,j,l and i)", + justclick: true + }, + { + text:"Nice !", + justclick: true + } + ]; + +} + +TutorialSteps.prototype.nextStep = function() { + if (this.step < this.instructions.length) { + this.alert(this.instructions[this.step].text, this.instructions[this.step].justclick); + switch (this.step) { + case 0: break; + case 1: this.camera.allowed.mouseRotate = true; break; + case 2: this.camera.allowed.keyboardRotate = true; break; + case 3: this.camera.allowed.keyboardTranslate = true; break; + } + this.step++; + } +} + +TutorialSteps.prototype.nextAction = function() { + switch (this.step) { + case 2: return 'rotate-mouse'; + case 3: return 'rotate-keyboard'; + } +} + +TutorialSteps.prototype.alert = function(myString, justclicked) { + this.notify(myString, justclicked); + onWindowResize(); +} + +TutorialSteps.prototype.notify = function(myString, justclick) { + $('#alert-placeholder').html( + '
' + + (justclick ? + '' : '') + + '' + + myString + + '' + + '
' + ); +}