class Controls { constructor(object) { // object is the object controlled by the controls, in our case, the camera. this.object = object; // 3D coordinates where the camera is looking at this.target = new THREE.Vector3(0, 0, 0); // Unit vectors of the local camera frame // forward is in the z-direction // left point towards the x-direction // (a vector towards y is not necessary) // in the direction of the angles this.forward = new THREE.Vector3(0,0,0); this.left = new THREE.Vector3(0,0,0); // speed at which the camera will translate this.speed = 50; // speed at which the camera will rotate this.sensitivity = 0.004; // angles of the camera this.angles = { delta: 0, theta: 0, }; // the position of the camera is this.object.position this.vectorsFromAngles(); // currently pressed keys this.keys = { up: false, down: false, left: false, right: false, }; // whether the mouse is being pressed or not this.mouseClicked = false; // whether there is a transition running or not // if not, this.t is NaN // otherwise, this.t is in [0, 1] this.t = NaN; // calls this.keyChange(true) when a key is pressed document.addEventListener('keydown', this.keyChange(true)); // calls this.keyChange(false) when a key is released document.addEventListener('keyup', this.keyChange(false)); // calls this.mouseDown(e) when a mouse button is pressed document.addEventListener('mousedown', (e) => this.mouseDown(e)); // sets this.mouseClicked to false when a mouse button is released document.addEventListener('mouseup', () => this.mouseClicked = false); // calls this.mouseMove(e) when the mouse is moved document.addEventListener('mousemove', (e) => this.mouseMove(e)); } // rotates the camera to make it look to its target. look() { this.object.lookAt(this.target); } // updates this.forward, this.left and this.target // from this.angles.delta and this.angles.theta // this.forward is the vector moving direction // this.left is the vector moving sideways to the left // this.target is the point that the camera is looking vectorsFromAngles() { // TODO Part 1 } // computes this.angles.delta and this.angles.theta from this.forward // (may be useless) anglesFromVectors() { // TODO Part 2 } // updates this.keys when a key is pressed or released keyChange(setting) { return (event) => { switch (event.keyCode) { case 37: case 81: this.keys.left = setting; break; case 38: case 90: this.keys.up = setting; break; case 39: case 68: this.keys.right = setting; break; case 40: case 83: this.keys.down = setting; break; case 13: console.log("Position:", this.object.position); console.log("Target:", this.target); break; } }; } // sets mouseClicked to true, and searches for a bookmark being clicked mouseDown(event) { this.mouseClicked = true; let mouse = { x: ( event.clientX / window.innerWidth ) * 2 - 1, y: - ( event.clientY / window.innerHeight ) * 2 + 1, }; // update the picking ray with the camera and mouse position raycaster.setFromCamera(mouse, camera); // calculate objects intersecting the picking ray var intersects = raycaster.intersectObjects(scene.children, true); if (this.testIntersection(intersects)) { this.bookmarkClicked(intersects[0].object.parent); } } // tests that a bookmark is clicked or not // // this function filters out other objects of the scene. testIntersection(intersects) { if (intersects.length === 0) { return; } if (intersects[0].object === undefined) { return; } if (! (intersects[0].object.parent instanceof Bookmark)) { return; } if (this.object.position.distanceTo(intersects[0].object.parent.position) < 10) { return; } this.bookmarkClicked(intersects[0].object.parent); } // initializes the paramters for the transition to a bookmark. bookmarkClicked(bookmark) { // TODO Part 2 } // changes this.angles depending of event.movementX and event.movementY // // event.movementX indicates how many pixels the mouse has moved to the right // event.movementY indicates how many piels the mouse has moved to the bottom // both those values can be negative to indicate a motion in the opposite direction mouseMove(event) { // TODO Part 1 } // called at every frame update() { // No transition : use keyboard and mouse to update the camera if (isNaN(this.t)) { // TODO Part 1 if (this.keys.up) { // ... } } else { // Transition to bookmark // TODO Part 2 } // update the camera parameters this.look(); } } let container, controls; let camera, scene, renderer; let raycaster = new THREE.Raycaster(); init(); animate(); function init() { container = document.createElement('div'); document.body.appendChild(container); camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 100, 50000); camera.position.y = 1000; camera.position.z = 1000; scene = new THREE.Scene(); let ambientLight = new THREE.AmbientLight(0xcccccc, 0.4); scene.add(ambientLight); let pointLight = new THREE.PointLight(0xffffff, 0.8); camera.add(pointLight); scene.add(camera); let geometry = new THREE.PlaneGeometry(20000, 20000); for (let index = 0; index < geometry.faceVertexUvs[0].length; index++) { geometry.faceVertexUvs[0][index][0].multiplyScalar(10); geometry.faceVertexUvs[0][index][1].multiplyScalar(10); geometry.faceVertexUvs[0][index][2].multiplyScalar(10); } let texture = new THREE.TextureLoader().load('assets/windfall/water.png'); texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; let material = new THREE.MeshBasicMaterial({map: texture}); let sea = new THREE.Mesh(geometry, material); sea.rotation.x = -Math.PI / 2; scene.add(sea); // controls controls = new Controls(camera); // model let directory = "assets/windfall/"; let modelName = "Windfall"; let onProgress = function (xhr) { if (xhr.lengthComputable) { let percentComplete = xhr.loaded / xhr.total * 100; console.log(Math.round(percentComplete, 2) + '% downloaded'); } }; let onError = function () { }; let manager = new THREE.LoadingManager(); new THREE.MTLLoader(manager) .setPath(directory) .load(modelName + ".mtl", function (materials) { materials.preload(); new THREE.OBJLoader(manager) .setMaterials(materials) .setPath(directory) .load(modelName + ".obj", function (object) { scene.add(object); }, onProgress, onError); }); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(new THREE.Color(0x87ceeb)); container.appendChild(renderer.domElement); window.addEventListener('resize', onWindowResize, false); // Add a bookmark to the scene // let position = { x: -3089.6612461510463, y: 3944.8226721499614, z: -2409.0350261969947 }; // let target = { x: -3088.665295409939, y: 3944.824672148628, z: -2409.124904566436 }; // let bookmark = new Bookmark(position, target); // scene.add(bookmark); } function animate() { requestAnimationFrame(animate); controls.update(); for (let child of scene.children) { if (child instanceof Bookmark) { child.update(camera); } } renderer.render(scene, camera); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }