import { Vector3, Raycaster, Object3D, Mesh, BackSide, MeshBasicMaterial, ShaderMaterial, AdditiveBlending, JSONLoader, AxesHelper, BoxGeometry } from "./three_modules"; const overlayInfo = (function () { "use strict"; let d = document.getElementById("overlay-info"); return { animIn: function (heading, text) { d.innerHTML = "

" + heading.charAt(0).toUpperCase() + heading.slice(1) + "

" + text + "
"; d.classList.add("overlayIN"); }, animOut: function () { d.classList.remove("overlayIN"); } } })(); const helpers = { //Clamp a number to two decimals twoDecimals: function (value) { "use strict"; return Math.round((value * 100)) / 100; }, //Linear Interpolate between two numbers lerp: function (a, b, f) { "use strict"; return a + f * (b - a); }, //Clamp a number to the min and max clip: function (number, min, max) { "use strict"; return Math.max(min, Math.min(number, max)); }, //Easing Type easeInOutCubic: function (t) { "use strict"; return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; } }; function initMobileEventListeners(scope) { import("./controls/initMobileEventListeners").then(mod => { mod.default(scope); }); } function initDesktopEventListeners(scope) { import("./controls/initDesktopEventListeners").then(mod => { mod.default(scope); }); } var w = window.innerWidth; var h = window.innerHeight; window.addEventListener("resize", function () { "use strict"; w = window.innerWidth; h = window.innerHeight; }); class archVizControls { constructor(scene, camera, renderer) { "use strict"; this.scene = scene; this.camera = camera; this.domElement = (renderer.domElement !== undefined) ? renderer.domElement : document; this.debug = false; this.saveCamTransform = true; //General Stuff this.type = "fp", this.lookSpeed = 0.5, this.camera, this.target, this.dddcursor = new Mesh(new BoxGeometry(2, 2, 2), new MeshBasicMaterial()); //Limiting Angles this.maxAzimuthAngle = -1337, this.minAzimuthAngle = 1337, this.maxPolarAngle = -1, this.minPolarAngle = 1; //Auto Rotation Stuff this.autoRotate = (localStorage.autoRotate === "true") || true, this.autoRotateMaxSpeed = 0.001, //Drag Stuff this.drag = (localStorage.drag === "true") || true, this.dragStrength = 0.05, this.dragMultiplier = 2, this.dragVector = { x: 0, y: 0 }; //Mouse events this.mVector = { x: 0, y: 0 }, this.mouseMove = { x: 0, y: 0 }, this.m1 = { x: 0, y: 0 }; this.nm = [0, 0]; this.mouseArray = [...new Array(10)].fill([0, 0]); //MOBILE STUFF this.mobile = false; this.deviceorientation = (localStorage.deviceorientation === "true") || false; this.deviceorientationSpeed = 0.03; this.alpha = 0; this.beta = 0; this.gamma = 0; //Raycaster stuff this.activeObject = { name: "", uuid: "", text: "" }, this.outlineObject = new Object3D(); this.outlineObject.name = "outlineObject"; this.outlineMaterial = new ShaderMaterial({ uniforms: {}, vertexShader: ` void main() { vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); vec4 displacement = vec4( normalize( normalMatrix * normal ) * 0.02, 0.0 ) + mvPosition; gl_Position = projectionMatrix * displacement; }`, fragmentShader: ` void main() { gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ); }`, blending: AdditiveBlending, side: BackSide }); this.anim = (function () { let anim, time = 0, percent; return function (from, to, type) { let finished = function () { this.type = type; time = 0; }.bind(this); if (this.type !== "anim") { this.type = "anim"; anim = function () { time++; if (time <= 70) { window.requestAnimationFrame(anim); percent = helpers.easeInOutCubic(time / 70); this.target.position.x = helpers.lerp(from[0], to[0], percent); this.target.position.y = helpers.lerp(from[1], to[1], percent); this.target.position.z = helpers.lerp(from[2], to[2], percent); this.camera.position.x = helpers.lerp(from[3], to[3], percent); this.camera.position.y = helpers.lerp(from[4], to[4], percent); this.camera.position.z = helpers.lerp(from[5], to[5], percent); this.target.rotation.y = helpers.lerp(from[6], to[6], percent); } else { this.minPolarAngle = to[7]; this.maxPolarAngle = to[8]; this.minAzimuthAngle = to[9]; this.maxAzimuthAngle = to[10]; finished(); } }.bind(this) anim(); }; } }.bind(this))(); this.update = (function () { let applyDeviceOrientation = function () { this.mVector.x += this.alpha * this.deviceorientationSpeed * 0.1; this.mVector.y += this.beta * this.deviceorientationSpeed * 0.1; }.bind(this); let mouseOver = (function () { let obj, INTERSECTED, raycaster = new Raycaster(); raycaster.far = 50; return function () { //GET THE OBJECT UNDER THE CURSOR raycaster.setFromCamera(this.m1, this.camera); this.intersects = raycaster.intersectObjects(scene.children)[0]; if (this.intersects !== undefined) { //ALIAS IT FOR FASTER REFERENCING obj = this.intersects.object; //IF THE OBJECT UNDER THE CURSOR IS THE FLOOR -- Most likely if (obj.name === "floor" || obj.name === "dddcursor") { this.outlineObject.visible = false; this.dddcursor.visible = true; this.dddcursor.position.set(this.intersects.point.x, this.intersects.point.y, this.intersects.point.z); //IF THE OBJECT UNDER THE CURSOR WAS ALREADY HIGHLIGHTED } else if (INTERSECTED === obj.uuid) { this.dddcursor.visible = false; } else if (obj.userData.clickable === true && this.activeObject.uuid !== obj.uuid) { INTERSECTED = obj.uuid; document.body.style.cursor = "pointer"; scene.remove(this.outlineObject); this.outlineObject = obj.clone(); this.outlineObject.material = this.outlineMaterial; scene.add(this.outlineObject); } else { this.outlineObject.visible = false; this.dddcursor.visible = false; document.body.style.cursor = "default"; INTERSECTED = null; } } }.bind(this); }).bind(this)(); let mouseMove = function () { if (this.anim !== true) { this.mouseArray.push([this.nm[0], this.nm[1]]); this.mouseArray.splice(0, 1); this.m1.x = this.nm[0]; this.m1.y = this.nm[1]; if (this.pressed === true) { this.mVector.y += (this.mouseArray[0][0] - this.nm[0]) * -0.75 * this.lookSpeed; this.mVector.x += (this.mouseArray[0][1] - this.nm[1]) * 0.6 * this.lookSpeed; } else { this.mVector.x = 0; this.mVector.y = 0; } } }.bind(this); let drag = function () { if (this.pressed === false && this.dragMultiplier > 0 && this.drag === true) { this.dragMultiplier -= 0.035; if (this.type === "fp") { this.mVector.y += this.dragVector.y * helpers.easeInOutCubic(this.dragMultiplier); this.mVector.x += this.dragVector.x * helpers.easeInOutCubic(this.dragMultiplier); } else if (this.type === "orbit") { this.mVector.y -= this.dragVector.y * helpers.easeInOutCubic(this.dragMultiplier); this.mVector.x -= this.dragVector.x * helpers.easeInOutCubic(this.dragMultiplier); } } else { this.dragMultiplier = 0; } }.bind(this); let autoRotate = (function () { let autoRotateTimer; return function () { if (autoRotateTimer <= 10) { //Last Stage this.target.rotation.y = helpers.clip((this.target.rotation.y - this.autoRotateMaxSpeed), this.minAzimuthAngle, this.maxAzimuthAngle); //MAKE IT BOUNCE! //INVERT THE ROTATION IF IT REACHES MIN OR MAX ROTATION if (this.target.rotation.y === this.minAzimuthAngle) { this.autoRotateMaxSpeed = -this.autoRotateMaxSpeed; } if (this.target.rotation.y === this.maxAzimuthAngle) { this.autoRotateMaxSpeed = -this.autoRotateMaxSpeed; } } else if (autoRotateTimer <= 70) { autoRotateTimer -= 1; this.target.rotation.y = helpers.clip((this.target.rotation.y - this.autoRotateMaxSpeed * (10 / autoRotateTimer)), this.minAzimuthAngle, this.maxAzimuthAngle); } else { autoRotateTimer -= 1; } }.bind(this) }).bind(this)(); return function () { if (this.type !== "anim") { applyDeviceOrientation(); mouseOver(); mouseMove(); drag(); autoRotate(); } this.rotate(); }; }).bind(this)(); this.initialize(); } rotate() { this.target.rotation.y = helpers.clip(this.target.rotation.y + this.mVector.y, this.maxAzimuthAngle, this.minAzimuthAngle) this.target.rotation.x = helpers.clip(this.target.rotation.x + this.mVector.x, this.maxPolarAngle, this.minPolarAngle); this.mVector.x = 0; this.mVector.y = 0; }; } archVizControls.prototype.setOrbit = function (target) { if (this.type === "fp") { this.savePosition = this.target.position.clone(); } this.outlineObject.visible = false; this.activeObject.uuid = this.intersects.object.uuid; this.activeObject.name = this.intersects.object.name; this.activeObject.text = this.intersects.object.userData.text; overlayInfo.animIn(this.activeObject.name, this.activeObject.text); this.dragMultiplier = 0; if (this.lookSpeed > 0) { this.lookSpeed = -this.lookSpeed; } if (this.type !== "anim") { this.target.rotation.y = this.target.rotation.y % (Math.PI * 2); if (this.target.rotation.y > 0 && ((target.userData.camAngle[2] + target.userData.camAngle[3]) / 2) < 0) { this.target.rotation.y -= Math.PI * 2; } if (this.target.rotation.y < 0 && ((target.userData.camAngle[2] + target.userData.camAngle[3]) / 2) > 0) { this.target.rotation.y += Math.PI * 2; } this.anim( [ this.target.position.x, this.target.position.y, this.target.position.z, this.camera.position.x, this.camera.position.y, this.camera.position.z, this.target.rotation.y ], [ target.position.x, target.position.y + target.userData.fpos[1], target.position.z, 0, 0, target.userData.fpos[0], (target.userData.camAngle[2] + target.userData.camAngle[3]) / 2, target.userData.camAngle[1], target.userData.camAngle[0], target.userData.camAngle[3], target.userData.camAngle[2] ], "orbit" ); } }; archVizControls.prototype.setFP = function (target) { overlayInfo.animOut(); if (this.lookSpeed < 0) { this.lookSpeed = -this.lookSpeed; } this.dragMultiplier = 0; this.anim( [ this.target.position.x, this.target.position.y, this.target.position.z, this.camera.position.x, this.camera.position.y, this.camera.position.z, this.target.rotation.y ], [ target.point.x, target.point.y + 1.6, target.point.z, 0, 0, 0, this.target.rotation.y, 0.5, -0.5, 1337, -1337 ], "fp" ); }; archVizControls.prototype.reset = function () { let empty = new Object3D(); empty.point = new Vector3(0, 0, 0); this.setFP(empty); }; archVizControls.prototype.initialize = function () { this.camera.position.z = 0.2; this.target = new AxesHelper(); this.target.name = "ControlsTarget"; this.target.position.set(0, 1.3, 0); this.target.rotation.order = "YXZ"; if (this.saveCamTransform) { if (localStorage.camTransform) { let _trans = localStorage.camTransform.split(","); this.target.position.x = parseFloat(_trans[0]); this.target.position.y = parseFloat(_trans[1]); this.target.position.z = parseFloat(_trans[2]); this.target.rotation.x = parseFloat(_trans[3]); this.target.rotation.y = parseFloat(_trans[4]); this.target.rotation.z = 0; } } this.target.add(this.camera); this.scene.add(this.target); this.target.visible = false; this.scene.add(this.outlineObject); //ADD EVENTLISTENERS ACCORDING TO PLATTFORM if (window.isMobile) { initMobileEventListeners(this); } else { initDesktopEventListeners(this); } //LOAD THE CURSOR MODEL new JSONLoader().load("./data/cursor.json", function (geometry) { this.dddcursor = new Mesh(geometry, new MeshBasicMaterial()); this.scene.add(this.dddcursor); }.bind(this)); }; export default archVizControls;