1// (C) 2014 Minoru Akagi 2// SPDX-License-Identifier: MIT 3// https://github.com/minorua/Qgis2threejs 4 5"use strict"; 6 7var Q3D = { 8 9 VERSION: "2.7.3", 10 application: {}, 11 gui: {} 12 13}; 14 15Q3D.Config = { 16 17 // renderer 18 renderer: { 19 hiDpi: true // HD-DPI support 20 }, 21 22 texture: { 23 anisotropy: -4 // zero means max available value. negative value means max / -v. 24 }, 25 26 // scene 27 autoAdjustCameraPos: true, // automatic camera height adjustment 28 bgColor: null, // null is sky 29 30 // camera 31 orthoCamera: false, 32 viewpoint: { // z-up 33 default: { // assumed that origin is (0, 0, 0) and base extent width in 3D world coordinates is 1 34 pos: new THREE.Vector3(0, -1, 1), 35 lookAt: new THREE.Vector3() 36 } 37 }, 38 39 // light 40 lights: { 41 directional: [ 42 { 43 type: "ambient", 44 color: 0x999999, 45 intensity: 0.8 46 }, 47 { 48 type: "directional", 49 color: 0xffffff, 50 intensity: 0.7, 51 azimuth: 220, // azimuth of light, in degrees. default light azimuth of gdaldem hillshade is 315. 52 altitude: 45 // altitude angle in degrees. 53 } 54 ], 55 point: [ 56 { 57 type: "ambient", 58 color: 0x999999, 59 intensity: 0.9 60 }, 61 { 62 type: "point", 63 color: 0xffffff, 64 intensity: 0.6, 65 height: 10 66 } 67 ] 68 }, 69 70 // layer 71 allVisible: false, // set every layer visible property to true on load if set to true 72 73 line: { 74 dash: { 75 dashSize: 1, 76 gapSize: 0.5 77 } 78 }, 79 80 label: { 81 visible: true, 82 canvasHeight: 64, 83 clickable: true 84 }, 85 86 // widgets 87 navigation: { 88 enabled: true 89 }, 90 91 northArrow: { 92 color: 0x8b4513, 93 cameraDistance: 30, 94 enabled: false 95 }, 96 97 // animation 98 animation: { 99 enabled: false, 100 startOnLoad: false, 101 easingCurve: "Cubic", 102 repeat: false 103 }, 104 105 // others 106 qmarker: { 107 radius: 0.004, 108 color: 0xffff00, 109 opacity: 0.8, 110 k: 0.2 // size factor for ortho camera 111 }, 112 113 measure: { 114 marker: { 115 radius: 0.004, 116 color: 0xffff00, 117 opacity: 0.5 118 /* k: 0.2 */ 119 }, 120 line: { 121 color: 0xffff00 122 } 123 }, 124 125 coord: { 126 visible: true, 127 latlon: false 128 }, 129 130 potree: {}, 131 132 debugMode: false 133}; 134 135// consts 136Q3D.LayerType = { 137 138 DEM: "dem", 139 Point: "point", 140 Line: "line", 141 Polygon: "polygon", 142 PointCloud: "pc" 143 144}; 145 146Q3D.MaterialType = { 147 148 MeshLambert: 0, 149 MeshPhong: 1, 150 MeshToon: 2, 151 Line: 3, 152 MeshLine: 4, 153 Sprite: 5, 154 Point: 6, 155 MeshStandard: 7, 156 Unknown: -1 157 158}; 159 160Q3D.KeyframeType = { 161 162 CameraMotion: 64, 163 Opacity: 65, 164 Texture: 66, 165 GrowingLine: 67 166 167}; 168 169Q3D.uv = { 170 171 i: new THREE.Vector3(1, 0, 0), 172 j: new THREE.Vector3(0, 1, 0), 173 k: new THREE.Vector3(0, 0, 1) 174 175}; 176 177Q3D.deg2rad = Math.PI / 180; 178 179Q3D.ua = window.navigator.userAgent.toLowerCase(); 180Q3D.isTouchDevice = ("ontouchstart" in window); 181 182Q3D.E = function (id) { 183 return document.getElementById(id); 184}; 185 186 187(function () { 188 189 var app = Q3D.application, 190 gui = Q3D.gui, 191 conf = Q3D.Config, 192 E = Q3D.E; 193 194 var vec3 = new THREE.Vector3(); 195 196 /* 197 Q3D.application 198 */ 199 var listeners = {}; 200 app.dispatchEvent = function (event) { 201 var ls = listeners[event.type] || []; 202 for (var i = 0; i < ls.length; i++) { 203 ls[i](event); 204 } 205 }; 206 207 app.addEventListener = function (type, listener, prepend) { 208 listeners[type] = listeners[type] || []; 209 if (prepend) { 210 listeners[type].unshift(listener); 211 } 212 else { 213 listeners[type].push(listener); 214 } 215 }; 216 217 app.removeEventListener = function (type, listener) { 218 var array = listeners[type]; 219 if (array !== undefined) { 220 var idx = array.indexOf(listener); 221 if (idx !== -1) array.splice(idx, 1); 222 } 223 }; 224 225 app.init = function (container) { 226 227 app.container = container; 228 229 app.selectedObject = null; 230 app.highlightObject = null; 231 232 app.modelBuilders = []; 233 app._wireframeMode = false; 234 235 // URL parameters 236 var params = app.parseUrlParameters(); 237 app.urlParams = params; 238 239 if ("popup" in params) { 240 // open popup window 241 var c = window.location.href.split("?"); 242 window.open(c[0] + "?" + c[1].replace(/&?popup/, ""), "popup", "width=" + params.width + ",height=" + params.height); 243 gui.popup.show("Another window has been opened."); 244 return; 245 } 246 247 if (params.hiDpi == "no") conf.renderer.hiDpi = false; 248 if (params.anisotropy) conf.texture.anisotropy = parseFloat(params.anisotropy); 249 250 if (params.cx !== undefined) conf.viewpoint.pos = new THREE.Vector3(parseFloat(params.cx), parseFloat(params.cy), parseFloat(params.cz)); 251 if (params.tx !== undefined) conf.viewpoint.lookAt = new THREE.Vector3(parseFloat(params.tx), parseFloat(params.ty), parseFloat(params.tz)); 252 253 if (params.width && params.height) { 254 container.style.width = params.width + "px"; 255 container.style.height = params.height + "px"; 256 } 257 258 app.width = container.clientWidth; 259 app.height = container.clientHeight; 260 261 var bgcolor = conf.bgColor; 262 if (bgcolor === null) container.classList.add("sky"); 263 264 // WebGLRenderer 265 app.renderer = new THREE.WebGLRenderer({alpha: true, antialias: true}); 266 267 if (conf.renderer.hiDpi) { 268 app.renderer.setPixelRatio(window.devicePixelRatio); 269 } 270 271 app.renderer.setSize(app.width, app.height); 272 app.renderer.setClearColor(bgcolor || 0, (bgcolor === null) ? 0 : 1); 273 app.container.appendChild(app.renderer.domElement); 274 275 if (conf.texture.anisotropy <= 0) { 276 var maxAnis = app.renderer.capabilities.getMaxAnisotropy() || 1; 277 278 if (conf.texture.anisotropy == 0) { 279 conf.texture.anisotropy = maxAnis; 280 } 281 else { 282 conf.texture.anisotropy = (maxAnis > -conf.texture.anisotropy) ? -maxAnis / conf.texture.anisotropy : 1; 283 } 284 } 285 286 // outline effect
287 if (THREE.OutlineEffect !== undefined) app.effect = new THREE.OutlineEffect(app.renderer); 288 289 // scene 290 app.scene = new Q3DScene(); 291 292 app.scene.addEventListener("renderRequest", function (event) { 293 app.render(); 294 }); 295 296 app.scene.addEventListener("cameraUpdateRequest", function (event) { 297 app.camera.position.copy(event.pos); 298 app.camera.lookAt(event.focal); 299 if (app.controls.target !== undefined) app.controls.target.copy(event.focal); 300 if (app.controls.saveState !== undefined) app.controls.saveState(); 301 302 if (event.far !== undefined) { 303 app.camera.near = (app.camera.isOrthographicCamera) ? 0 : event.near; 304 app.camera.far = event.far; 305 app.camera.updateProjectionMatrix(); 306 } 307 }); 308 309 app.scene.addEventListener("lightChanged", function (event) { 310 if (event.light == "point") { 311 app.scene.add(app.camera); 312 app.camera.add(app.scene.lightGroup); 313 } 314 else { // directional 315 app.scene.remove(app.camera); 316 app.scene.add(app.scene.lightGroup); 317 } 318 }); 319 320 app.scene.addEventListener("mapRotationChanged", function (event) { 321 if (app.scene2) { 322 app.scene2.lightGroup.clear(); 323 app.scene2.buildLights(Q3D.Config.lights.directional, event.rotation); 324 } 325 }); 326 327 // camera 328 app.buildCamera(conf.orthoCamera); 329 330 // controls 331 if (THREE.OrbitControls) { 332 app.controls = new THREE.OrbitControls(app.camera, app.renderer.domElement); 333 334 app.controls.addEventListener("change", function (event) { 335 app.render(); 336 }); 337 338 app.controls.update(); 339 } 340 341 // navigation 342 if (conf.navigation.enabled && typeof ViewHelper !== "undefined") { 343 app.buildViewHelper(E("navigation")); 344 } 345 346 // north arrow 347 if (conf.northArrow.enabled) { 348 app.buildNorthArrow(E("northarrow")); 349 } 350 351 // labels 352 app.labelVisible = conf.label.visible; 353 354 // create a marker for queried point 355 var opt = conf.qmarker; 356 app.queryMarker = new THREE.Mesh(new THREE.SphereBufferGeometry(opt.radius, 32, 32), 357 new THREE.MeshLambertMaterial({color: opt.color, opacity: opt.opacity, transparent: (opt.opacity < 1)})); 358 app.queryMarker.name = "marker"; 359 360 app.queryMarker.onBeforeRender = function (renderer, scene, camera, geometry, material, group) { 361 this.scale.setScalar(this.position.distanceTo(camera.position) * ((camera.isPerspectiveCamera) ? 1 : conf.qmarker.k)); 362 this.updateMatrixWorld(); 363 }; 364 365 app.highlightMaterial = new THREE.MeshLambertMaterial({emissive: 0x999900, transparent: true, opacity: 0.5, side: THREE.DoubleSide}); 366 367 // loading manager 368 app.initLoadingManager(); 369 370 // event listeners 371 app.addEventListener("sceneLoaded", function () { 372 if (conf.viewpoint.pos === undefined && conf.autoAdjustCameraPos) {
373 app.adjustCameraPosition(); 374 } 375 app.render(); 376 377 if (conf.animation.enabled) { 378 var btn = E("animbtn"); 379 if (btn) { 380 btn.className = "playbtn"; 381 } 382 383 if (conf.animation.startOnLoad) { 384 app.animation.keyframes.start(); 385 } 386 } 387 }, true); 388 389 window.addEventListener("keydown", app.eventListener.keydown); 390 window.addEventListener("resize", app.eventListener.resize); 391 392 app.renderer.domElement.addEventListener("mousedown", app.eventListener.mousedown); 393 app.renderer.domElement.addEventListener("mouseup", app.eventListener.mouseup); 394 395 gui.init(); 396 }; 397 398 app.parseUrlParameters = function () { 399 var p, vars = {}; 400 var params = window.location.search.substring(1).split('&').concat(window.location.hash.substring(1).split('&')); 401 params.forEach(function (param) { 402 p = param.split('='); 403 vars[p[0]] = p[1]; 404 }); 405 return vars; 406 }; 407 408 app.initLoadingManager = function () { 409 if (app.loadingManager) { 410 app.loadingManager.onLoad = app.loadingManager.onProgress = app.loadingManager.onError = undefined; 411 } 412 413 app.loadingManager = new THREE.LoadingManager(function () { // onLoad 414 app.loadingManager.isLoading = false; 415 416 E("progressbar").classList.add("fadeout"); 417 418 app.dispatchEvent({type: "sceneLoaded"}); 419 }, 420 function (url, loaded, total) { // onProgress 421 E("progressbar").style.width = (loaded / total * 100) + "%"; 422 }, 423 function () { // onError 424 app.loadingManager.isLoading = false; 425 426 app.dispatchEvent({type: "sceneLoadError"}); 427 }); 428 429 app.loadingManager.onStart = function () { 430 app.loadingManager.isLoading = true; 431 }; 432 433 app.loadingManager.isLoading = false; 434 }; 435 436 app.loadFile = function (url, type, callback) { 437 438 var loader = new THREE.FileLoader(app.loadingManager); 439 loader.setResponseType(type); 440 441 var onError = function (e) { 442 if (location.protocol == "file:") { 443 gui.popup.show("This browser doesn't allow loading local files via Ajax. See <a href='https://github.com/minorua/Qgis2threejs/wiki/Browser-Support'>plugin wiki page</a> for details.", "Error", true); 444 } 445 }; 446 447 try { 448 loader.load(url, callback, undefined, onError); 449 } 450 catch (e) { // for IE 451 onError(e); 452 } 453 }; 454 455 app.loadJSONObject = function (jsonObject) { 456 app.scene.loadJSONObject(jsonObject); 457 if (jsonObject.animation !== undefined) app.animation.keyframes.load(jsonObject.animation.groups); 458 }; 459 460 app.loadJSONFile = function (url, callback) { 461 app.loadFile(url, "json", function (obj) { 462 app.loadJSONObject(obj); 463 if (callback) callback(obj); 464 }); 465 }; 466 467 app.loadSceneFile = function (url, sceneFileLoadedCallback, sceneLoadedCallback) { 468 469 var onload = function () { 470 if (sceneFileLoadedCallback) sceneFileLoadedCallback(app.scene); 471 }; 472 473 if (sceneLoadedCallback) { 474 app.addEventListener("sceneLoaded", function () { 475 sceneLoadedCallback(app.scene); 476 }); 477 } 478 479 var ext = url.split(".").pop(); 480 if (ext == "json") app.loadJSONFile(url, onload); 481 else if (ext == "js") { 482 var e = document.createElement("script"); 483 e.src = url; 484 e.onload = onload; 485 document.body.appendChild(e); 486 } 487 }; 488 489 app.loadTextureFile = function (url, callback) { 490 return new THREE.TextureLoader(app.loadingManager).load(url, callback); 491 }; 492 493 app.loadModelFile = function (url, callback) { 494 var loader, 495 ext = url.split(".").pop(); 496 497 if (ext == "dae") { 498 loader = new THREE.ColladaLoader(app.loadingManager); 499 } 500 else if (ext == "gltf" || ext == "glb") { 501 loader = new THREE.GLTFLoader(app.loadingManager); 502 } 503 else { 504 console.log("Model file type not supported: " + url); 505 return; 506 } 507 508 app.loadingManager.itemStart("M" + url); 509 510 loader.load(url, function (model) { 511 if (callback) callback(model); 512 app.loadingManager.itemEnd("M" + url); 513 }, 514 undefined, 515 function (e) { 516 console.log("Failed to load model: " + url); 517 app.loadingManager.itemError("M" + url); 518 }); 519 }; 520 521 app.loadModelData = function (data, ext, resourcePath, callback) { 522 523 if (ext == "dae") { 524 var model = new THREE.ColladaLoader(app.loadingManager).parse(data, resourcePath); 525 if (callback) callback(model); 526 } 527 else if (ext == "gltf" || ext == "glb") { 528 new THREE.GLTFLoader(app.loadingManager).parse(data, resourcePath, function (model) { 529 if (callback) callback(model); 530 }, function (e) { 531 console.log("Failed to load a glTF model: " + e); 532 }); 533 } 534 else {
535 console.log("Model file type not supported: " + ext); 536 return; 537 } 538 }; 539 540 app.mouseDownPoint = new THREE.Vector2(); 541 app.mouseUpPoint = new THREE.Vector2(); 542 543 app.eventListener = { 544 545 keydown: function (e) { 546 if (e.ctrlKey) return; 547 548 if (e.shiftKey) { 549 switch (e.keyCode) { 550 case 82: // Shift + R 551 app.controls.reset(); 552 return; 553 case 83: // Shift + S 554 gui.showPrintDialog(); 555 return; 556 } 557 return; 558 } 559 560 switch (e.keyCode) { 561 case 8: // BackSpace 562 if (app.measure.isActive) app.measure.removeLastPoint(); 563 return; 564 case 13: // Enter 565 app.animation.keyframes.resume(); 566 return; 567 case 27: // ESC 568 if (gui.popup.isVisible()) { 569 app.cleanView(); 570 } 571 else if (app.controls.autoRotate) { 572 app.setRotateAnimationMode(false); 573 } 574 return; 575 case 73: // I 576 gui.showInfo(); 577 return; 578 case 76: // L 579 app.setLabelVisible(!app.labelVisible); 580 return; 581 case 82: // R 582 app.setRotateAnimationMode(!app.controls.autoRotate); 583 return; 584 case 87: // W 585 app.setWireframeMode(!app._wireframeMode); 586 return; 587 } 588 }, 589 590 mousedown: function (e) { 591 app.mouseDownPoint.set(e.clientX, e.clientY); 592 }, 593 594 mouseup: function (e) { 595 app.mouseUpPoint.set(e.clientX, e.clientY); 596 if (app.mouseDownPoint.equals(app.mouseUpPoint)) app.canvasClicked(e); 597 }, 598 599 resize: function () { 600 app.setCanvasSize(app.container.clientWidth, app.container.clientHeight); 601 app.render(); 602 } 603 604 }; 605 606 app.setCanvasSize = function (width, height) { 607 var changed = (app.width != width || app.height != height); 608 609 app.width = width; 610 app.height = height; 611 app.camera.aspect = width / height; 612 app.camera.updateProjectionMatrix(); 613 app.renderer.setSize(width, height); 614 615 if (changed) app.dispatchEvent({type: "canvasSizeChanged"}); 616 }; 617 618 app.buildCamera = function (is_ortho) { 619 if (is_ortho) { 620 app.camera = new THREE.OrthographicCamera(-app.width / 10, app.width / 10, app.height / 10, -app.height / 10); 621 } 622 else { 623 app.camera = new THREE.PerspectiveCamera(45, app.width / app.height); 624 } 625 626 // magic to change y-up world to z-up 627 app.camera.up.set(0, 0, 1); 628 629 var be = app.scene.userData.baseExtent; 630 if (be) { 631 app.camera.near = (is_ortho) ? 0 : 0.001 * be.width; 632 app.camera.far = 100 * be.width; 633 app.camera.updateProjectionMatrix(); 634 } 635 }; 636 637 // move camera target to center of scene 638 app.adjustCameraPosition = function (force) { 639 if (!force) { 640 app.render(); 641 642 // stay at current position if rendered objects exist 643 var r = app.renderer.info.render; 644 if (r.triangles + r.points + r.lines) return; 645 } 646 var bbox = app.scene.boundingBox(); 647 if (bbox.isEmpty()) return; 648 649 bbox.getCenter(vec3); 650 app.cameraAction.zoom(vec3.x, vec3.y, (bbox.max.z + vec3.z) / 2, app.scene.userData.baseExtent.width); 651 }; 652 653 // declination: clockwise from +y, in degrees 654 app.buildNorthArrow = function (container, declination) { 655 container.style.display = "block"; 656 657 app.renderer2 = new THREE.WebGLRenderer({alpha: true, antialias: true}); 658 app.renderer2.setClearColor(0, 0); 659 app.renderer2.setSize(container.clientWidth, container.clientHeight); 660 661 app.container2 = container; 662 app.container2.appendChild(app.renderer2.domElement); 663 664 app.camera2 = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 1, 1000); 665 app.camera2.position.set(0, 0, conf.northArrow.cameraDistance); 666 app.camera2.up = app.camera.up; 667 668 app.scene2 = new Q3DScene(); 669 app.scene2.buildLights(conf.lights.directional, 0); 670 671 // an arrow object 672 var geometry = new THREE.Geometry(); 673 geometry.vertices.push( 674 new THREE.Vector3(-5, -10, 0), 675 new THREE.Vector3(0, 10, 0), 676 new THREE.Vector3(0, -7, 3), 677 new THREE.Vector3(5, -10, 0) 678 ); 679 geometry.faces.push( 680 new THREE.Face3(0, 1, 2), 681 new THREE.Face3(2, 1, 3) 682 ); 683 geometry.computeFaceNormals(); 684 685 var material = new THREE.MeshLambertMaterial({color: conf.northArrow.color, side: THREE.DoubleSide}); 686 var mesh = new THREE.Mesh(geometry, material); 687 if (declination) mesh.rotation.z = -declination * Q3D.deg2rad; 688 app.scene2.add(mesh); 689 }; 690 691 app.buildViewHelper = function (container) { 692 693 if (app.renderer3 === undefined) { 694 container.style.display = "block"; 695 696 app.renderer3 = new THREE.WebGLRenderer({alpha: true, antialias: true}); 697 app.renderer3.setClearColor(0, 0); 698 app.renderer3.setSize(container.clientWidth, container.clientHeight); 699 700 app.container3 = container; 701 app.container3.appendChild(app.renderer3.domElement); 702 } 703 704 if (app.viewHelper !== undefined) { 705 app.viewHelper.removeEventListener("requestAnimation", app.startViewHelperAnimation); 706 } 707 708 app.viewHelper = new ViewHelper(app.camera, {dom: container}); 709 app.viewHelper.controls = app.controls; 710 711 app.viewHelper.addEventListener("requestAnimation", app.startViewHelperAnimation); 712 }; 713 714 var clock = new THREE.Clock(); 715 app.startViewHelperAnimation = function () { 716 clock.start(); 717 requestAnimationFrame(app.animate); 718 }; 719 720 app.currentViewUrl = function () { 721 var c = app.scene.toMapCoordinates(app.camera.position), 722 t = app.scene.toMapCoordinates(app.controls.target), 723 hash = "#cx=" + c.x.toFixed(3) + "&cy=" + c.y.toFixed(3) + "&cz=" + c.z.toFixed(3); 724 if (t.x || t.y || t.z) hash += "&tx=" + t.x.toFixed(3) + "&ty=" + t.y.toFixed(3) + "&tz=" + t.z.toFixed(3); 725 return window.location.href.split("#")[0] + hash; 726 }; 727 728 // enable the controls 729 app.start = function () { 730 if (app.controls) app.controls.enabled = true; 731 }; 732 733 app.pause = function () { 734 app.animation.isActive = false;
735 if (app.controls) app.controls.enabled = false; 736 }; 737 738 app.resume = function () { 739 if (app.controls) app.controls.enabled = true; 740 }; 741 742 // animation loop 743 app.animate = function () { 744 745 if (app.animation.isActive) { 746 requestAnimationFrame(app.animate); 747 748 if (app.animation.keyframes.isActive) TWEEN.update(); 749 else if (app.controls.enabled) app.controls.update(); 750 } 751 else if (app.viewHelper && app.viewHelper.animating) { 752 requestAnimationFrame(app.animate); 753 754 app.viewHelper.update(clock.getDelta()); 755 } 756 757 app.render(); 758 }; 759 760 app.animation = { 761 762 isActive: false, 763 764 start: function () { 765 this.isActive = true; 766 app.animate(); 767 }, 768 769 stop: function () { 770 this.isActive = false; 771 }, 772 773 keyframes: { // keyframe animation 774 775 isActive: false, 776 777 isPaused: false, 778 779 curveFactor: 0, 780 781 easingFunction: function (easing) { 782 if (easing == 1) return TWEEN.Easing.Linear.None; 783 if (easing > 1) { 784 var f = TWEEN.Easing[Q3D.Config.animation.easingCurve]; 785 if (easing == 2) return f["InOut"]; 786 else if (easing == 3) return f["In"]; 787 else return f["Out"]; // easing == 4 788 } 789 }, 790 791 keyframeGroups: [], 792 793 clear: function () { 794 this.keyframeGroups = []; 795 }, 796 797 load: function (group) { 798 if (!Array.isArray(group)) group = [group]; 799 800 this.keyframeGroups = this.keyframeGroups.concat(group); 801 }, 802 803 start: function () { 804 805 var _this = this, 806 e = E("narrativebox"), 807 btn = E("nextbtn"), 808 currentNarElem; 809 810 this.keyframeGroups.forEach(function (group) { 811 812 var t; 813 for (var p in Q3D.Tweens) { 814 if (Q3D.Tweens[p].type == group.type) { 815 t = Q3D.Tweens[p]; 816 break; 817 } 818 } 819 if (t === undefined) { 820 console.warn("unknown animation type: " + group.type); 821 return; 822 } 823 824 var layer = (group.layerId !== undefined) ? app.scene.mapLayers[group.layerId] : undefined; 825 826 group.completed = false; 827 group.currentIndex = 0; 828 group.prop_list = []; 829 830 t.init(group, layer); 831 832 var keyframes = group.keyframes; 833 834 var showNBox = function (idx) { 835 // narrative box 836 var n = keyframes[idx].narration; 837 if (n && e) { 838 if (currentNarElem) { 839 currentNarElem.classList.remove("visible"); 840 } 841 842 currentNarElem = E(n.id); 843 if (currentNarElem) { 844 currentNarElem.classList.add("visible"); 845 } 846 else { // preview 847 E("narbody").innerHTML = n.text; 848 } 849 850 if (btn) { 851 if (idx < keyframes.length - 1) { 852 btn.className = "nextbtn"; 853 btn.innerHTML = ""; 854 } 855 else { 856 btn.className = ""; 857 btn.innerHTML = "Close"; 858 } 859 } 860 861 setTimeout(function () { 862 _this.pause(); 863 e.classList.add("visible"); 864 }, 0); 865 } 866 }; 867 868 var onStart = function () { 869 if (group.onStart) group.onStart(); 870 871 app.dispatchEvent({type: "tweenStarted", index: group.currentIndex}); 872 873 // pause if narrative box is shown 874 if (e && e.classList.contains("visible")) { 875 e.classList.remove("visible"); 876 } 877 }; 878 879 var onComplete = function (obj) { 880 if (!keyframes[group.currentIndex].easing) { 881 group.onUpdate(obj, 1); 882 } 883 884 if (group.onComplete) group.onComplete(obj); 885 886 var index = ++group.currentIndex; 887 if (index == keyframes.length - 1) { 888 group.completed = true; 889 890 var completed = true; 891 for (var i = 0; i < _this.keyframeGroups.length; i++) { 892 if (!_this.keyframeGroups[i].completed) completed = false; 893 } 894 895 if (completed) { 896 if (currentNarElem) { 897 currentNarElem.classList.remove("visible"); 898 } 899 900 if (conf.animation.repeat) { 901 setTimeout(function () { 902 _this.start(); 903 }, 0); 904 } 905 else { 906 _this.stop(); 907 } 908 } 909 } 910 911 // show narrative box if the current keyframe has a narrative content 912 showNBox(index); 913 }; 914 915 var t0, t1, t2; 916 for (var i = 0; i < keyframes.length - 1; i++) { 917 918 t2 = new TWEEN.Tween(group.prop_list[i]).delay(keyframes[i].delay).onStart(onStart) 919 .to(group.prop_list[i + 1], keyframes[i].duration).onComplete(onComplete); 920 921 if (keyframes[i].easing) { 922 t2.easing(_this.easingFunction(keyframes[i].easing)).onUp
922date(group.onUpdate); 923 } 924 925 if (i == 0) { 926 t0 = t2; 927 } 928 else { 929 t1.chain(t2); 930 } 931 t1 = t2; 932 } 933 934 showNBox(0); 935 936 t0.start(); 937 }); 938 939 app.animation.isActive = this.isActive = true; 940 app.dispatchEvent({type: "animationStarted"}); 941 app.animate(); 942 }, 943 944 stop: function () { 945 946 TWEEN.removeAll(); 947 948 app.animation.isActive = this.isActive = this.isPaused = false; 949 this._pausedTweens = null; 950 951 app.dispatchEvent({type: "animationStopped"}); 952 }, 953 954 pause: function () { 955 956 if (this.isPaused) return; 957 958 this._pausedTweens = TWEEN.getAll(); 959 960 if (this._pausedTweens.length) { 961 for (var i = 0; i < this._pausedTweens.length; i++) { 962 this._pausedTweens[i].pause(); 963 } 964 this.isPaused = true; 965 } 966 app.animation.isActive = this.isActive = false; 967 }, 968 969 resume: function () { 970 971 var box = E("narrativebox"); 972 if (box && box.classList.contains("visible")) { 973 box.classList.remove("visible"); 974 } 975 976 if (!this.isPaused) return; 977 978 for (var i = 0; i < this._pausedTweens.length; i++) { 979 this._pausedTweens[i].resume(); 980 } 981 this._pausedTweens = null; 982 983 app.animation.isActive = this.isActive = true; 984 this.isPaused = false; 985 986 app.animate(); 987 } 988 }, 989 990 orbit: { // orbit animation 991 992 isActive: false, 993 994 start: function () { 995 996 app.controls.autoRotate = true; 997 app.animation.isActive = this.isActive = true; 998 999 app.animate(); 1000 }, 1001 1002 stop: function () { 1003 1004 app.controls.autoRotate = false; 1005 app.animation.isActive = this.isActive = false; 1006 } 1007 } 1008 }; 1009 1010 app.render = function (updateControls) { 1011 if (updateControls) { 1012 app.controls.update(); 1013 } 1014 1015 if (app.camera.parent) { 1016 app.camera.updateMatrixWorld(); 1017 } 1018 1019 // render 1020 if (app.effect) { 1021 app.effect.render(app.scene, app.camera); 1022 } 1023 else { 1024 app.renderer.render(app.scene, app.camera); 1025 } 1026 1027 // North arrow 1028 if (app.renderer2) { 1029 app.scene2.quaternion.copy(app.camera.quaternion).inverse(); 1030 app.scene2.updateMatrixWorld(); 1031 1032 app.renderer2.render(app.scene2, app.camera2); 1033 } 1034 1035 // navigation widget 1036 if (app.viewHelper) { 1037 app.viewHelper.render(app.renderer3); 1038 } 1039 }; 1040 1041 (function () { 1042 var dly, rpt, times, id = null; 1043 var func = function () { 1044 app.render(); 1045 if (rpt <= ++times) { 1046 clearInterval(id); 1047 id = null; 1048 } 1049 }; 1050 app.setIntervalRender = function (delay, repeat) { 1051 if (id === null || delay != dly) { 1052 if (id !== null) { 1053 clearInterval(id); 1054 } 1055 id = setInterval(func, delay); 1056 dly = delay; 1057 } 1058 rpt = repeat; 1059 times = 0; 1060 }; 1061 })(); 1062 1063 app.setLabelVisible = function (visible) { 1064 app.labelVisible = visible; 1065 app.scene.labelGroup.visible = visible; 1066 app.scene.labelConnectorGroup.visible = visible; 1067 app.render(); 1068 }; 1069 1070 app.setRotateAnimationMode = function (enabled) { 1071 if (enabled) { 1072 app.animation.orbit.start(); 1073 } 1074 else { 1075 app.animation.orbit.stop(); 1076 } 1077 }; 1078 1079 app.setWireframeMode = function (wireframe) { 1080 if (wireframe == app._wireframeMode) return; 1081 1082 for (var id in app.scene.mapLayers) { 1083 app.scene.mapLayers[id].setWireframeMode(wireframe); 1084 } 1085 1086 app._wireframeMode = wireframe; 1087 app.render(); 1088 }; 1089 1090 app.intersectObjects = function (offsetX, offsetY) { 1091 var vec2 = new THREE.Vector2((offsetX / app.width) * 2 - 1, 1092 -(offsetY / app.height) * 2 + 1); 1093 var ray = new THREE.Raycaster(); 1094 ray.linePrecision = 0.2; 1095 ray.setFromCamera(vec2, app.camera); 1096 return ray.intersectObjects(app.scene.visibleObjects(app.labelVisible)); 1097 }; 1098 1099 app._offset = function (elm) { 1100 var top = 0, left = 0; 1101 do { 1102 top += elm.offsetTop || 0; left += elm.offsetLeft || 0; elm = elm.offsetParent; 1103 } while (elm); 1104 return {top: top, left: left}; 1105 }; 1106 1107 app.queryTargetPosition = new THREE.Vector3(); 1108 1109 app.cameraAction = { 1110 1111 move: function (x, y, z) { 1112 if (x === undefined) app.camera.position.copy(app.queryTargetPosition); 1113 else app.camera.position.set(x, y, z); 1114 1115 app.render(true); 1116 app.cleanView(); 1117 }, 1118 1119 vecZoom: new THREE.Vector3(0, -1, 1).normalize(), 1120 1121 zoom: function (x, y, z, dist) { 1122 if (x === undefined) vec3.copy(app.queryTargetPosition); 1123 else vec3.set(x, y, z); 1124 1125 if (dist === undefined) dist = app.scene.userData.baseExtent.width * 0.1; 1126
1127 app.camera.position.copy(app.cameraAction.vecZoom).multiplyScalar(dist).add(vec3); 1128 app.camera.lookAt(vec3); 1129 if (app.controls.target !== undefined) app.controls.target.copy(vec3); 1130 app.render(true); 1131 app.cleanView(); 1132 }, 1133 1134 zoomToLayer: function (layer) { 1135 if (!layer) return; 1136 1137 var bbox = layer.boundingBox(); 1138 1139 bbox.getSize(vec3); 1140 var dist = Math.max(vec3.x, vec3.y * 3 / 4) * 1.2; 1141 1142 bbox.getCenter(vec3); 1143 app.cameraAction.zoom(vec3.x, vec3.y, vec3.z, dist); 1144 }, 1145 1146 orbit: function (x, y, z) { 1147 if (app.controls.target === undefined) return; 1148 1149 if (x === undefined) app.controls.target.copy(app.queryTargetPosition); 1150 else app.controls.target.set(x, y, z); 1151 1152 app.setRotateAnimationMode(true); 1153 app.cleanView(); 1154 } 1155 1156 }; 1157 1158 app.cleanView = function () { 1159 gui.clean(); 1160 1161 app.scene.remove(app.queryMarker); 1162 app.highlightFeature(null); 1163 app.measure.clear(); 1164 app.render(); 1165 1166 app.selectedLayer = null; 1167 1168 if (app._canvasImageUrl) { 1169 URL.revokeObjectURL(app._canvasImageUrl); 1170 app._canvasImageUrl = null; 1171 } 1172 }; 1173 1174 app.highlightFeature = function (object) { 1175 if (app.highlightObject) { 1176 // remove highlight object from the scene 1177 app.scene.remove(app.highlightObject); 1178 app.selectedObject = null; 1179 app.highlightObject = null; 1180 } 1181 1182 if (object === null) return; 1183 1184 var layer = app.scene.mapLayers[object.userData.layerId]; 1185 if (!layer || layer.type == Q3D.LayerType.DEM || layer.type == Q3D.LayerType.PointCloud) return; 1186 if (layer.properties.objType == "Billboard") return; 1187 1188 // create a highlight object (if layer type is Point, slightly bigger than the object) 1189 var s = (layer.type == Q3D.LayerType.Point) ? 1.01 : 1; 1190 1191 var clone = object.clone(); 1192 clone.traverse(function (obj) { 1193 obj.material = app.highlightMaterial; 1194 }); 1195 if (s != 1) clone.scale.multiplyScalar(s); 1196 1197 // add the highlight object to the scene 1198 app.scene.add(clone); 1199 1200 app.selectedObject = object; 1201 app.highlightObject = clone; 1202 }; 1203 1204 app.canvasClicked = function (e) { 1205 1206 // button 2: right click 1207 if (e.button == 2 && app.measure.isActive) { 1208 app.measure.removeLastPoint(); 1209 return; 1210 } 1211 1212 var canvasOffset = app._offset(app.renderer.domElement); 1213 var objs = app.intersectObjects(e.clientX - canvasOffset.left, e.clientY - canvasOffset.top); 1214 1215 var obj, o, layer, layerId; 1216 for (var i = 0, l = objs.length; i < l; i++) { 1217 obj = objs[i]; 1218 1219 if (app.measure.isActive) { 1220 app.measure.addPoint(obj.point); 1221 return; 1222 } 1223 1224 // get layerId of clicked object 1225 o = obj.object; 1226 while (o) { 1227 layerId = o.userData.layerId; 1228 if (layerId !== undefined) break; 1229 o = o.parent; 1230 } 1231 1232 if (layerId === undefined) break; 1233 1234 layer = app.scene.mapLayers[layerId]; 1235 if (!layer.clickable) break; 1236 1237 app.selectedLayer = layer; 1238 app.queryTargetPosition.copy(obj.point); 1239 1240 // query marker 1241 app.queryMarker.position.copy(obj.point); 1242 app.scene.add(app.queryMarker); 1243 1244 if (o.userData.isLabel) { 1245 o = o.userData.objs[o.userData.partIdx]; // label -> object 1246 } 1247 1248 app.highlightFeature(o); 1249 app.render(); 1250 gui.showQueryResult(obj.point, layer, o, conf.coord.visible); 1251 1252 return; 1253 } 1254 if (app.measure.isActive) return; 1255 1256 app.cleanView(); 1257 1258 if (app.controls.autoRotate) { 1259 app.setRotateAnimationMode(false); 1260 } 1261 }; 1262 1263 app.saveCanvasImage = function (width, height, fill_background, saveImageFunc) { 1264 if (fill_background === undefined) fill_background = true; 1265 1266 // set canvas size 1267 var old_size; 1268 if (width && height) { 1269 old_size = [app.width, app.height]; 1270 app.setCanvasSize(width, height); 1271 } 1272 1273 // functions 1274 var saveBlob = function (blob) { 1275 var filename = "image.png"; 1276 1277 // ie 1278 if (window.navigator.msSaveBlob !== undefined) { 1279 window.navigator.msSaveBlob(blob, filename); 1280 gui.popup.hide(); 1281 } 1282 else { 1283 // create object url 1284 if (app._canvasImageUrl) URL.revokeObjectURL(app._canvasImageUrl); 1285 app._canvasImageUrl = URL.createObjectURL(blob); 1286 1287 // display a link to save the image 1288 var e = document.createElement("a"); 1289 e.className = "download-link"; 1290 e.href = app._canvasImageUrl; 1291 e.download = filename; 1292 e.innerHTML = "Save"; 1293 gui.popup.show("Click to save the image to a file." + e.outerHTML, "Image is ready"); 1294 } 1295 }; 1296 1297 var saveCanvasImage = saveImageFunc || function (canvas) { 1298 if (canvas.toBlob !== undefined) { 1299 canvas.toBlob(saveBlob); 1300 } 1301 else { // !HTMLCanvasElement.prototype.toBlob 1302 // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement.toBlob 1303 var binStr = atob(canvas.toDataURL("image/png").split(',')[1]), 1304 len = binStr.length, 1305 arr = new Uint8Array(len); 1306 1307 for (var i = 0; i < len; i++) { 1308 arr[i] = binStr.charCodeAt(i); 1309 } 1310 1311 saveBlob(new Blob([arr], {type: "image/png"})); 1312 } 1313 }; 1314 1315 var restoreCanvasSize = function () { 1316 // restore canvas size 1317 if (old_size) app.setCanvasSize(old_size[0], old_size[1]); 1318 app.render(); 1319 }; 1320 1321 // background option 1322 if (!fill_background) app.renderer.setClearColor(0, 0); 1323 1324 // render 1325 app.renderer.preserveDrawingBuffer = true; 1326 1327 if (app.effect) { 1328 app.effect.render(app.scene, app.camera); 1329 } 1330 else { 1331 app.renderer.render(app.scene, app.camera); 1332 } 1333 1334 // restore clear color 1335 var bgcolor = conf.bgColor; 1336 app.renderer.setClearColor(bgcolor || 0, (bgcolor === null) ? 0 : 1); 1337 1338 if (fill_background && bgcolor === null) { 1339 var canvas = document.createElement("canvas"); 1340 canvas.width = width; 1341 canvas.height = height; 1342 1343 var ctx = canvas.getContext("2d"); 1344 if (fill_background && bgcolor === null) { 1345 // render "sky-like" background 1346 var grad = ctx.createLinearGradient(0, 0, 0, height); 1347 grad.addColorStop(0, "#98c8f6"); 1348 grad.addColorStop(0.4, "#cbebff"); 1349 grad.addColorStop(1, "#f0f9ff"); 1350 ctx.fillStyle = grad; 1351 ctx.fillRect(0, 0, width, height); 1352 } 1353 1354 var image = new Image(); 1355 image.onload = function () { 1356 // draw webgl canvas image 1357 ctx.drawImage(image, 0, 0, width, height); 1358 1359 // save canvas image 1360 saveCanvasImage(canvas); 1361 restoreCanvasSize(); 1362 }; 1363 image.src = app.renderer.domElement.toDataURL("image/png"); 1364 } 1365 else { 1366 // save webgl canvas image 1367 saveCanvasImage(app.renderer.domElement); 1368 restoreCanvasSize(); 1369 } 1370 }; 1371 1372 (function () { 1373 1374 var path = []; 1375 1376 app.measure = { 1377 1378 isActive: false, 1379 1380 precision: 3, 1381 1382 start: function () { 1383 app.scene.remove(app.queryMarker); 1384 1385 if (!this.geom) { 1386 var opt = conf.measure.marker; 1387 this.geom = new THREE.SphereBufferGeometry(opt.radius, 32, 32); 1388 this.mtl = new THREE.MeshLambertMaterial({color: opt.color, opacity: opt.opacity, transparent: (opt.opacity < 1)}); 1389 opt = conf.measure.line; 1390 this.lineMtl = new THREE.LineBasicMaterial({color: opt.color}); 1391 this.markerGroup = new Q3DGroup(); 1392 this.markerGroup.name = "measure marker"; 1393 this.lineGroup = new Q3DGroup(); 1394 this.lineGroup.name = "measure line"; 1395 } 1396 1397 this.isActive = true; 1398 1399 app.scene.add(this.markerGroup); 1400 app.scene.add(this.lineGroup); 1401 1402 this.addPoint(app.queryTargetPosition); 1403 }, 1404 1405 addPoint: function (pt) { 1406 // add a marker 1407 var marker = new THREE.Mesh(this.geom, this.mtl); 1408 marker.position.copy(pt); 1409 marker.onBeforeRender = app.queryMarker.onBeforeRender; 1410 1411 this.markerGroup.updateMatrixWorld(); 1412 this.markerGroup.add(marker); 1413 1414 path.push(marker.position); 1415 1416 if (path.length > 1) { 1417 // add a line 1418 var v = path[path.length - 2].toArray().concat(path[path.length - 1].toArray()), 1419 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(v, 3)), 1420 line = new THREE.Line(geom, this.lineMtl); 1421 this.lineGroup.add(line); 1422 } 1423 1424 app.render(); 1425 this.showResult(); 1426 }, 1427 1428 removeLastPoint: function () { 1429 path.pop(); 1430 this.markerGroup.children.pop(); 1431 this.lineGroup.children.pop(); 1432 1433 app.render(); 1434 1435 if (path.length) this.showResult(); 1436 else app.cleanView(); 1437 }, 1438 1439 clear: function () { 1440 if (!this.isActive) return; 1441 1442 this.markerGroup.clear(); 1443 this.lineGroup.clear(); 1444 1445 app.scene.remove(this.markerGroup); 1446 app.scene.remove(this.lineGroup); 1447 1448 path = []; 1449 this.isActive = false;
1450 }, 1451 1452 formatLength: function (length) { 1453 return (length) ? length.toFixed(this.precision) : 0; 1454 }, 1455 1456 showResult: function () { 1457 var vec2 = new THREE.Vector2(), 1458 zScale = app.scene.userData.zScale; 1459 var total = 0, totalxy = 0, dz = 0; 1460 if (path.length > 1) { 1461 var dxy; 1462 for (var i = path.length - 1; i > 0; i--) { 1463 dxy = vec2.copy(path[i]).distanceTo(path[i - 1]); 1464 dz = (path[i].z - path[i - 1].z) / zScale; 1465 1466 total += Math.sqrt(dxy * dxy + dz * dz); 1467 totalxy += dxy; 1468 } 1469 dz = (path[path.length - 1].z - path[0].z) / zScale; 1470 } 1471 1472 var html = '<table class="measure">'; 1473 html += "<tr><td>Total distance:</td><td>" + this.formatLength(total) + " m</td><td></td></tr>"; 1474 html += "<tr><td>Horizontal distance:</td><td>" + this.formatLength(totalxy) + " m</td><td></td></tr>"; 1475 html += "<tr><td>Vertical difference:</td><td>" + this.formatLength(dz) + ' m</td><td><span class="tooltip tooltip-btn" data-tooltip="elevation difference between start point and end point">?</span></td></tr>'; 1476 html += "</table>"; 1477 1478 gui.popup.show(html, "Measure distance"); 1479 } 1480 }; 1481 })(); 1482 1483 1484 /* 1485 Q3D.gui 1486 */ 1487 var VIS = "visible"; 1488 1489 function CE(tagName, parent, innerHTML) { 1490 var elem = document.createElement(tagName); 1491 if (parent) parent.appendChild(elem); 1492 if (innerHTML) elem.innerHTML = innerHTML; 1493 return elem; 1494 } 1495 1496 function ON_CLICK(id, listener) { 1497 var e = document.getElementById(id); 1498 if (e) e.addEventListener("click", listener); 1499 } 1500 1501 gui.modules = []; 1502 1503 gui.init = function () { 1504 // tool buttons 1505 ON_CLICK("layerbtn", function () { 1506 if (!gui.layerPanel.initialized) gui.layerPanel.init(); 1507 1508 if (gui.layerPanel.isVisible()) { 1509 gui.layerPanel.hide(); 1510 } 1511 else { 1512 if (gui.popup.isVisible()) { 1513 gui.popup.hide(); 1514 } 1515 gui.layerPanel.show(); 1516 } 1517 }); 1518 1519 ON_CLICK("infobtn", function () { 1520 gui.layerPanel.hide(); 1521 1522 if (gui.popup.isVisible() && gui.popup.content == "pageinfo") gui.popup.hide(); 1523 else gui.showInfo(); 1524 }); 1525 1526 var btn = E("animbtn"); 1527 if (conf.animation.enabled && btn) { 1528 var anim = app.animation.keyframes; 1529 1530 var playButton = function () { 1531 btn.className = "playbtn"; 1532 }; 1533 1534 var pauseButton = function () { 1535 btn.className = "pausebtn"; 1536 }; 1537 1538 btn.onclick = function () { 1539 if (anim.isActive) { 1540 anim.pause(); 1541 playButton(); 1542 } 1543 else if (anim.isPaused) { 1544 anim.resume(); 1545 pauseButton(); 1546 } 1547 else anim.start(); 1548 }; 1549 1550 app.addEventListener('animationStarted', pauseButton); 1551 app.addEventListener('animationStopped', playButton); 1552 } 1553 1554 // popup 1555 ON_CLICK("closebtn", app.cleanView); 1556 ON_CLICK("zoomtolayer", function () { 1557 app.cameraAction.zoomToLayer(app.selectedLayer); 1558 }); 1559 ON_CLICK("zoomtopoint", function () { 1560 app.cameraAction.zoom(); 1561 }); 1562 ON_CLICK("orbitbtn", function () { 1563 app.cameraAction.orbit(); 1564 }); 1565 ON_CLICK("measurebtn", function () { 1566 app.measure.start(); 1567 }); 1568 1569 // narrative box 1570 ON_CLICK("nextbtn", function () { 1571 app.animation.keyframes.resume(); 1572 }); 1573 1574 // attribution 1575 if (typeof proj4 === "undefined") { 1576 var e = E("lib_proj4js"); 1577 if (e) e.classList.add("hidden"); 1578 } 1579 1580 // initialize modules 1581 for (var i = 0; i < gui.modules.length; i++) { 1582 gui.modules[i].init(); 1583 } 1584 }; 1585 1586 gui.clean = function () { 1587 gui.popup.hide(); 1588 if (gui.layerPanel.initialized) gui.layerPanel.hide(); 1589 }; 1590 1591 gui.popup = { 1592 1593 modal: false, 1594 1595 content: null, 1596 1597 timerId: null, 1598 1599 isVisible: function () { 1600 return E("popup").classList.contains(VIS); 1601 }, 1602 1603 // show box 1604 // obj: html, element or content id ("queryresult" or "pageinfo") 1605 // modal: boolean 1606 // duration: int [milliseconds] 1607 show: function (obj, title, modal, duration) { 1608 1609 if (modal) app.pause(); 1610 else if (this.modal) app.resume(); 1611 1612 this.content = obj; 1613 this.modal = Boolean(modal); 1614 1615 var e = E("layerpanel"); 1616 if (e) e.classList.remove(VIS); 1617 1618 var content = E("popupcontent"); 1619 [content, E("queryresult"), E("pageinfo")].forEach(function (e) { 1620 if (e) e.classList.remove(VIS); 1621 }); 1622 1623 if (obj == "queryresult" || obj == "pageinfo") { 1624 E(obj).classList.add(VIS); 1625 } 1626 else { 1627 if (obj instanceof HTMLElement) { 1628 content.innerHTML = ""; 1629 content.appendChild(obj); 1630 } 1631 else { 1632 content.innerHTML = obj; 1633 } 1634 content.classList.add(VIS); 1635 } 1636 E("popupbar").innerHTML = title || ""; 1637 E("popup").classList.add(VIS); 1638 1639 if (this.timerId !== null) { 1640 clearTimeout(this.timerId); 1641 this.timerId = null; 1642 } 1643 1644 if (duration) { 1645 this.timerId = setTimeout(function () { 1646 gui.popup.hide(); 1647 }, duration); 1648 } 1649 }, 1650 1651 hide: function () { 1652 E("popup").classList.remove(VIS); 1653 if (this.timerId !== null) clearTimeout(this.timerId); 1654 this.timerId = null; 1655 this.content = null; 1656 if (this.modal) app.resume(); 1657 } 1658 1659 }; 1660 1661 gui.showInfo = function () { 1662 var e = E("urlbox"); 1663 if (e) e.value = app.currentViewUrl(); 1664 gui.popup.show("pageinfo"); 1665 }; 1666 1667 gui.showQueryResult = function (point, layer, obj, show_coords) { 1668 // layer name 1669 var e = E("qr_layername"); 1670 if (layer && e) e.innerHTML = layer.properties.name; 1671 1672 // clicked coordinates 1673 e = E("qr_coords_table"); 1674 if (e) { 1675 if (show_coords) { 1676 e.classList.remove("hidden"); 1677 1678 var pt = app.scene.toMapCoordinates(point); 1679 1680 e = E("qr_coords"); 1681 1682 if (conf.coord.latlon) { 1683 var lonLat = proj4(app.scene.userData.proj).inverse([pt.x, pt.y]); 1684 e.innerHTML = Q3D.Utils.convertToDMS(lonLat[1], lonLat[0]) + ", Elev. " + pt.z.toFixed(2); 1685 } 1686 else { 1687 e.innerHTML = [pt.x.toFixed(2), pt.y.toFixed(2), pt.z.toFixed(2)].join(", "); 1688 } 1689 1690 if (conf.debugMode) { 1691 var p = app.scene.userData, 1692 be = p.baseExtent; 1693 e.innerHTML += "<br>WLD: " + [point.x.toFixed(8), point.y.toFixed(8), point.z.toFixed(8)].join(", "); 1694 e.innerHTML += "<br><br>ORG: " + [p.origin.x.toFixed(8), p.origin.y.toFixed(8), p.origin.z.toFixed(8)].join(", "); 1695 e.innerHTML += "<br>BE CNTR: " + [be.cx.toFixed(8), be.cy.toFixed(8)].join(", "); 1696 e.innerHTML += "<br>BE SIZE: " + [be.width.toFixed(8), be.height.toFixed(8)].join(", "); 1697 e.innerHTML += "<br>ROT: " + be.rotation + "<br>Z SC: " + p.zScale; 1698 } 1699 } 1700 else { 1701 e.classList.add("hidden"); 1702 } 1703 } 1704 1705 e = E("qr_attrs_table"); 1706 if (e) { 1707 for (var i = e.children.length - 1; i >= 0; i--) { 1708 if (e.children[i].tagName.toUpperCase() == "TR") e.removeChild(e.children[i]); 1709 } 1710 1711 if (layer && layer.properties.propertyNames !== undefined) { 1712 var row; 1713 for (var i = 0, l = layer.properties.propertyNames.length; i < l; i++) {
1714 row = document.createElement("tr"); 1715 row.innerHTML = "<td>" + layer.properties.propertyNames[i] + "</td>" + 1716 "<td>" + obj.userData.properties[i] + "</td>"; 1717 e.appendChild(row); 1718 } 1719 e.classList.remove("hidden"); 1720 } 1721 else { 1722 e.classList.add("hidden"); 1723 } 1724 } 1725 gui.popup.show("queryresult"); 1726 }; 1727 1728 gui.showPrintDialog = function () { 1729 1730 var f = CE("form"); 1731 f.className = "print"; 1732 1733 var d1 = CE("div", f, "Image Size"); 1734 d1.style.textDecoration = "underline"; 1735 1736 var d2 = CE("div", f), 1737 l1 = CE("label", d2, "Width:"), 1738 width = CE("input", d2); 1739 d2.style.cssFloat = "left"; 1740 l1.htmlFor = width.id = width.name = "printwidth"; 1741 width.type = "text"; 1742 width.value = app.width; 1743 CE("span", d2, "px,"); 1744 1745 var d3 = CE("div", f), 1746 l2 = CE("label", d3, "Height:"), 1747 height = CE("input", d3); 1748 l2.htmlFor = height.id = height.name = "printheight"; 1749 height.type = "text"; 1750 height.value = app.height; 1751 CE("span", d3, "px"); 1752 1753 var d4 = CE("div", f), 1754 ka = CE("input", d4); 1755 ka.type = "checkbox"; 1756 ka.checked = true; 1757 CE("span", d4, "Keep Aspect Ratio"); 1758 1759 var d5 = CE("div", f, "Option"); 1760 d5.style.textDecoration = "underline"; 1761 1762 var d6 = CE("div", f), 1763 bg = CE("input", d6); 1764 bg.type = "checkbox"; 1765 bg.checked = true;
1766 CE("span", d6, "Fill Background"); 1767 1768 var d7 = CE("div", f), 1769 ok = CE("span", d7, "OK"), 1770 cancel = CE("span", d7, "Cancel"); 1771 d7.className = "buttonbox"; 1772 1773 CE("input", f).type = "submit"; 1774 1775 // event handlers 1776 // width and height boxes 1777 var aspect = app.width / app.height; 1778 1779 width.oninput = function () { 1780 if (ka.checked) height.value = Math.round(width.value / aspect); 1781 }; 1782 1783 height.oninput = function () { 1784 if (ka.checked) width.value = Math.round(height.value * aspect); 1785 }; 1786 1787 ok.onclick = function () { 1788 gui.popup.show("Rendering..."); 1789 window.setTimeout(function () { 1790 app.saveCanvasImage(width.value, height.value, bg.checked); 1791 }, 10); 1792 }; 1793 1794 cancel.onclick = app.cleanView; 1795 1796 // enter key pressed 1797 f.onsubmit = function () { 1798 ok.onclick(); 1799 return false; 1800 }; 1801 1802 gui.popup.show(f, "Save Image", true); // modal 1803 }; 1804 1805 gui.layerPanel = { 1806 1807 init: function () { 1808 var panel = E("layerpanel"); 1809 1810 var p, item, e, slider, o, select, i; 1811 Object.keys(app.scene.mapLayers).forEach(function (layerId) { 1812 1813 var layer = app.scene.mapLayers[layerId]; 1814 p = layer.properties; 1815 item = CE("div", panel); 1816 item.className = "layer"; 1817 1818 // visible 1819 e = CE("div", item, "<input type='checkbox'" + ((p.visible) ? " checked" : "") + ">" + p.name); 1820 e.querySelector("input[type=checkbox]").addEventListener("change", function () { 1821 layer.visible = this.checked; 1822 }); 1823 1824 // opacity slider 1825 e = CE("div", item, "Opacity: <input type='range'><output></output>"); 1826 slider = e.querySelector("input[type=range]"); 1827 1828 var label = e.querySelector("output"); 1829 1830 o = parseInt(layer.opacity * 100); 1831 slider.value = o; 1832 slider.addEventListener("input", function () { 1833 label.innerHTML = this.value + " %"; 1834 }); 1835 slider.addEventListener("change", function () { 1836 label.innerHTML = this.value + " %"; 1837 layer.opacity = this.value / 100; 1838 }); 1839 label.innerHTML = o + " %"; 1840 1841 // material dropdown 1842 if (p.mtlNames && p.mtlNames.length > 1) { 1843 select = CE("select", CE("div", item, "Material: ")); 1844 for (i = 0; i < p.mtlNames.length; i++) { 1845 CE("option", select, p.mtlNames[i]).setAttribute("value", i); 1846 } 1847 select.value = p.mtlIdx; 1848 select.addEventListener("change", function () { 1849 layer.setCurrentMaterial(this.value); 1850 }); 1851 } 1852 }); 1853 gui.layerPanel.initialized = true; 1854 }, 1855 1856 isVisible: function () { 1857 return E("layerpanel").classList.contains(VIS); 1858 }, 1859 1860 show: function () { 1861 E("layerpanel").classList.add(VIS); 1862 }, 1863 1864 hide: function () { 1865 E("layerpanel").classList.remove(VIS); 1866 } 1867 1868 }; 1869 1870})(); 1871 1872 1873// Q3D classes 1874 1875class Q3DGroup extends THREE.Group { 1876 1877 add(object) { 1878 super.add(object); 1879 object.updateMatrixWorld(); 1880 } 1881 1882 clear() { 1883 for (var i = this.children.length - 1; i >= 0; i--) { 1884 this.remove(this.children[i]); 1885 } 1886 } 1887 1888} 1889 1890 1891/* 1892Q3DScene 1893.userData 1894 - baseExtent(cx, cy, width, height, rotation): map base extent in map coordinates. center is (cx, cy). 1895 - origin: origin of 3D world in map coordinates 1896 - zScale: vertical scale factor 1897 - proj: (optional) proj string. used to display clicked position in long/lat. 1898*/ 1899class Q3DScene extends THREE.Scene { 1900 1901 constructor() { 1902 super(); 1903 1904 this.autoUpdate = false; 1905 1906 this.mapLayers = {}; // map layers contained in this scene. key is layerId. 1907 1908 this.lightGroup = new Q3DGroup(); 1909 this.lightGroup.name = "light"; 1910 this.add(this.lightGroup); 1911 1912 this.labelGroup = new Q3DGroup(); 1913 this.labelGroup.name = "label"; 1914 this.add(this.labelGroup); 1915 1916 this.labelConnectorGroup = new Q3DGroup(); 1917 this.labelConnectorGroup.name = "label connector"; 1918 this.add(this.labelConnectorGroup); 1919 } 1920 1921 add(object) { 1922 super.add(object); 1923 object.updateMatrixWorld(); 1924 } 1925 1926 loadJSONObject(jsonObject) { 1927 if (jsonObject.type == "scene") { 1928 var p = jsonObject.properties; 1929 if (p !== undefined) { 1930 // fog 1931 if (p.fog) { 1932 this.fog = new THREE.FogExp2(p.fog.color, p.fog.density); 1933 } 1934 1935 // light 1936 var rotation0 = (this.userData.baseExtent) ? this.userData.baseExtent.rotation : 0; 1937 if (p.light != this.userData.light || p.baseExtent.rotation != rotation0) { 1938 this.lightGroup.clear(); 1939 this.buildLights(Q3D.Config.lights[p.light] || Q3D.Config.lights.directional, p.baseExtent.rotation); 1940 this.dispatchEvent({type: "lightChanged", light: p.light}); 1941 } 1942 1943 var be = p.baseExtent; 1944 p.pivot = new THREE.Vector3(be.cx, be.cy, p.origin.z).sub(p.origin);
1944 // 2D center of extent in 3D world coordinates 1945 1946 // set initial camera position and parameters 1947 if (this.userData.origin === undefined) { 1948 1949 var s = be.width, 1950 v = Q3D.Config.viewpoint, 1951 pos, focal; 1952 1953 if (v.pos === undefined) { 1954 v = v.default; 1955 if (be.rotation) { 1956 v = { 1957 pos: v.pos.clone().applyAxisAngle(Q3D.uv.k, be.rotation * Q3D.deg2rad), 1958 lookAt: v.lookAt.clone().applyAxisAngle(Q3D.uv.k, be.rotation * Q3D.deg2rad) 1959 }; 1960 } 1961 pos = v.pos.clone().multiplyScalar(s).add(p.pivot); 1962 focal = v.lookAt.clone().multiplyScalar(s).add(p.pivot); 1963 } 1964 else { 1965 pos = new THREE.Vector3().copy(v.pos).sub(p.origin); 1966 focal = new THREE.Vector3().copy(v.lookAt).sub(p.origin); 1967 } 1968 1969 pos.z *= p.zScale; 1970 focal.z *= p.zScale; 1971 1972 var near = 0.001 * s, 1973 far = 100 * s; 1974 1975 this.requestCameraUpdate(pos, focal, near, far); 1976 } 1977 1978 if (p.baseExtent.rotation != rotation0) { 1979 this.dispatchEvent({type: "mapRotationChanged", rotation: p.baseExtent.rotation}); 1980 } 1981 1982 this.userData = p; 1983 } 1984 1985 // load layers 1986 if (jsonObject.layers !== undefined) { 1987 jsonObject.layers.forEach(function (layer) { 1988 this.loadJSONObject(layer); 1989 }, this); 1990 } 1991 } 1992 else if (jsonObject.type == "layer") { 1993 var layer = this.mapLayers[jsonObject.id]; 1994 if (layer === undefined) { 1995 // create a layer 1996 var type = jsonObject.properties.type; 1997 if (type == "dem") layer = new Q3DDEMLayer(); 1998 else if (type == "point") layer = new Q3DPointLayer(); 1999 else if (type == "line") layer = new Q3DLineLayer(); 2000 else if (type == "polygon") layer = new Q3DPolygonLayer(); 2001 else if (type == "pc") layer = new Q3DPointCloudLayer(); 2002 else { 2003 console.error("unknown layer type:" + type); 2004 return; 2005 } 2006 layer.id = jsonObject.id; 2007 layer.objectGroup.userData.layerId = jsonObject.id; 2008 layer.addEventListener("renderRequest", this.requestRender.bind(this)); 2009 2010 this.mapLayers[jsonObject.id] = layer; 2011 this.add(layer.objectGroup); 2012 } 2013 2014 layer.loadJSONObject(jsonObject, this); 2015 2016 this.requestRender(); 2017 } 2018 else if (jsonObject.type == "block") { 2019 var layer = this.mapLayers[jsonObject.layer]; 2020 if (layer === undefined) { 2021 // console.error("layer not exists:" + jsonObject.layer); 2022 return; 2023 } 2024 layer.loadJSONObject(jsonObject, this); 2025 2026 this.requestRender(); 2027 } 2028 } 2029 2030 buildLights(lights, rotation) { 2031 var p, light; 2032 for (var i = 0; i < lights.length; i++) { 2033 p = lights[i]; 2034 if (p.type == "ambient") { 2035 light = new THREE.AmbientLight(p.color, p.intensity); 2036 } 2037 else if (p.type == "directional") { 2038 light = new THREE.DirectionalLight(p.color, p.intensity); 2039 light.position.copy(Q3D.uv.j) 2040 .applyAxisAngle(Q3D.uv.i, p.altitude * Q3D.deg2rad) 2041 .applyAxisAngle(Q3D.uv.k, (rotation - p.azimuth) * Q3D.deg2rad); 2042 } 2043 else if (p.type == "point") { 2044 light = new THREE.PointLight(p.color, p.intensity); 2045 light.position.set(0, 0, p.height); 2046 } 2047 else { 2048 continue; 2049 } 2050 this.lightGroup.add(light); 2051 } 2052 } 2053 2054 requestRender() { 2055 this.dispatchEvent({type: "renderRequest"}); 2056 } 2057 2058 requestCameraUpdate(pos, focal, near, far) { 2059 this.dispatchEvent({type: "cameraUpdateRequest", pos: pos, focal: focal, near: near, far: far}); 2060 } 2061 2062 visibleObjects(labelVisible) { 2063 var layer, objs = []; 2064 for (var id in this.mapLayers) { 2065 layer = this.mapLayers[id]; 2066 if (layer.visible) { 2067 objs = objs.concat(layer.visibleObjects()); 2068 if (labelVisible && layer.labels) objs = objs.concat(layer.labels); 2069 } 2070 } 2071 return objs; 2072 } 2073 2074 // 3D world coordinates to map coordinates 2075 toMapCoordinates(pt) { 2076 var p = this.userData; 2077 return { 2078 x: p.origin.x + pt.x, 2079 y: p.origin.y + pt.y, 2080 z: p.origin.z + pt.z / p.zScale 2081 }; 2082 } 2083 2084 // map coordinates to 3D world coordinates 2085 toWorldCoordinates(pt, isLonLat) { 2086 var p = this.userData; 2087 if (isLonLat && typeof proj4 !== "undefined") { 2088 // WGS84 long,lat to map coordinates 2089 var t = proj4(p.proj).forward([pt.x, pt.y]); 2090 pt = {x: t[0], y: t[1], z: pt.z}; 2091 } 2092 2093 return { 2094 x: pt.x - p.origin.x, 2095 y: pt.y - p.origin.y, 2096 z: (pt.z - p.origin.z) * p.zScale 2097 }; 2098 } 2099 2100 // return bounding box in 3d world coordinates 2101 boundingBox() { 2102 var box = new THREE.Box3(); 2103 for (var id in this.mapLayers) { 2104 if (this.mapLayers[id].visible) { 2105 box.union(this.mapLayers[id].boundingBox()); 2106 } 2107 } 2108 return box; 2109 } 2110 2111} 2112 2113 2114class Q3DMaterial { 2115 2116 constructor() { 2117 this.loaded = false;
2118 } 2119 2120 // material: a THREE.Material-based object 2121 set(material) { 2122 this.mtl = material; 2123 this.origProp = {}; 2124 return this; 2125 } 2126 2127 // callback is called when material has been completely loaded 2128 loadJSONObject(jsonObject, callback) { 2129 this.origProp = jsonObject; 2130 this.groupId = jsonObject.mtlIndex; 2131 2132 var m = jsonObject, opt = {}, defer = false; 2133 2134 if (m.ds) opt.side = THREE.DoubleSide; 2135 2136 if (m.flat) opt.flatShading = true; 2137 2138 // texture 2139 if (m.image !== undefined) { 2140 var _this = this; 2141 if (m.image.url !== undefined) { 2142 opt.map = Q3D.application.loadTextureFile(m.image.url, function () { 2143 _this._loadCompleted(callback); 2144 }); 2145 defer = true; 2146 } 2147 else if (m.image.object !== undefined) { // WebKit Bridge 2148 opt.map = new THREE.Texture(m.image.object.toImageData()); 2149 opt.map.needsUpdate = true; 2150 2151 delete m.image.object; 2152 } 2153 else { // base64 2154 var img = new Image(); 2155 img.onload = function () { 2156 opt.map.needsUpdate = true; 2157 _this._loadCompleted(callback); 2158 }; 2159 img.src = m.image.base64; 2160 opt.map = new THREE.Texture(img); 2161 defer = true; 2162 2163 delete m.image.base64; 2164 } 2165 opt.map.anisotropy = Q3D.Config.texture.anisotropy; 2166 } 2167 2168 if (m.c !== undefined) opt.color = m.c; 2169 2170 if (m.o !== undefined && m.o < 1) { 2171 opt.opacity = m.o; 2172 opt.transparent = true; 2173 } 2174 2175 if (m.t) opt.transparent = true; 2176 2177 if (m.w) opt.wireframe = true; 2178 2179 if (m.bm) { 2180 this.mtl = new THREE.MeshBasicMaterial(opt); 2181 } 2182 else if (m.type == Q3D.MaterialType.MeshLambert) { 2183 this.mtl = new THREE.MeshLambertMaterial(opt); 2184 } 2185 else if (m.type == Q3D.MaterialType.MeshPhong) { 2186 this.mtl = new THREE.MeshPhongMaterial(opt); 2187 } 2188 else if (m.type == Q3D.MaterialType.MeshToon) { 2189 this.mtl = new THREE.MeshToonMaterial(opt); 2190 } 2191 else if (m.type == Q3D.MaterialType.Point) { 2192 opt.size = m.s; 2193 this.mtl = new THREE.PointsMaterial(opt); 2194 } 2195 else if (m.type == Q3D.MaterialType.Line) { 2196 2197 if (m.dashed) { 2198 opt.dashSize = Q3D.Config.line.dash.dashSize; 2199 opt.gapSize = Q3D.Config.line.dash.gapSize; 2200 this.mtl = new THREE.LineDashedMaterial(opt); 2201 } 2202 else { 2203 this.mtl = new THREE.LineBasicMaterial(opt); 2204 } 2205 } 2206 else if (m.type == Q3D.MaterialType.MeshLine) { 2207 2208 opt.lineWidth = m.thickness; 2209 if (m.dashed) { 2210 opt.dashArray = 0.03; 2211 opt.dashRatio = 0.45; 2212 opt.dashOffset = 0.015; 2213 opt.transparent = true; 2214 } 2215 // opt.sizeAttenuation = 1; 2216 2217 var mtl = this.mtl = new MeshLineMaterial(opt); 2218 var updateAspect = this._listener = function () { 2219 mtl.resolution = new THREE.Vector2(Q3D.application.width, Q3D.application.height); 2220 }; 2221 2222 updateAspect(); 2223 Q3D.application.addEventListener("canvasSizeChanged", updateAspect); 2224 } 2225 else if (m.type == Q3D.MaterialType.Sprite) { 2226 opt.color = 0xffffff; 2227 this.mtl = new THREE.SpriteMaterial(opt); 2228 } 2229 else { 2230 if (m.roughness !== undefined) opt.roughness = m.roughness; 2231 if (m.metalness !== undefined) opt.metalness = m.metalness; 2232 2233 this.mtl = new THREE.MeshStandardMaterial(opt); 2234 } 2235 2236 if (!defer) this._loadCompleted(callback); 2237 } 2238 2239 _loadCompleted(anotherCallback) { 2240 this.loaded = true; 2241 2242 if (this._callbacks !== undefined) { 2243 for (var i = 0; i < this._callbacks.length; i++) { 2244 this._callbacks[i](); 2245 } 2246 this._callbacks = []; 2247 } 2248 2249 if (anotherCallback) anotherCallback(); 2250 } 2251 2252 callbackOnLoad(callback) { 2253 if (this.loaded) return callback(); 2254 2255 if (this._callbacks === undefined) this._callbacks = []; 2256 this._callbacks.push(callback); 2257 } 2258 2259 dispose() { 2260 if (!this.mtl) return; 2261 2262 if (this.mtl.map) this.mtl.map.dispose(); // dispose of texture 2263 this.mtl.dispose(); 2264 this.mtl = null; 2265 2266 if (this._listener) { 2267 Q3D.application.removeEventListener("canvasSizeChanged", this._listener); 2268 this._listener = undefined; 2269 } 2270 } 2271} 2272 2273 2274class Q3DMaterials extends THREE.EventDispatcher { 2275 2276 constructor() { 2277 super(); 2278 this.array = []; 2279 } 2280 2281 // material: instance of Q3DMaterial object or THREE.Material-based object 2282 add(material) { 2283 if (material instanceof Q3DMaterial) { 2284 this.array.push(material); 2285 } 2286 else { 2287 this.array.push(new Q3DMaterial().set(material)); 2288 } 2289 } 2290 2291 get(index) { 2292 return this.array[index]; 2293 } 2294 2295 mtl(index) { 2296 return this.array[index].mtl; 2297 } 2298 2299 loadJSONObject(jsonObject) { 2300 var _this = this, iterated = false;
2301 var callback = function () { 2302 if (iterated) _this.dispatchEvent({type: "renderRequest"}); 2303 }; 2304 2305 for (var i = 0, l = jsonObject.length; i < l; i++) { 2306 var mtl = new Q3DMaterial(); 2307 mtl.loadJSONObject(jsonObject[i], callback); 2308 this.add(mtl); 2309 } 2310 iterated = true; 2311 } 2312 2313 dispose() { 2314 for (var i = 0, l = this.array.length; i < l; i++) { 2315 this.array[i].dispose(); 2316 } 2317 this.array = []; 2318 } 2319 2320 addFromObject3D(object) { 2321 var mtls = []; 2322 2323 object.traverse(function (obj) { 2324 if (obj.material === undefined) return; 2325 ((obj.material instanceof Array) ? obj.material : [obj.material]).forEach(function (mtl) { 2326 if (mtls.indexOf(mtl) == -1) { 2327 mtls.push(mtl); 2328 } 2329 }); 2330 }); 2331 2332 for (var i = 0, l = mtls.length; i < l; i++) { 2333 this.array.push(new Q3DMaterial().set(mtls[i])); 2334 } 2335 } 2336 2337 // opacity 2338 opacity() { 2339 if (this.array.length == 0) return 1; 2340 2341 var sum = 0; 2342 for (var i = 0, l = this.array.length; i < l; i++) { 2343 sum += this.array[i].mtl.opacity; 2344 } 2345 return sum / this.array.length; 2346 } 2347 2348 setOpacity(opacity) { 2349 var m; 2350 for (var i = 0, l = this.array.length; i < l; i++) { 2351 m = this.array[i]; 2352 m.mtl.transparent = Boolean(m.origProp.t) || (opacity < 1); 2353 m.mtl.opacity = opacity; 2354 } 2355 } 2356 2357 // wireframe: boolean 2358 setWireframeMode(wireframe) { 2359 var m; 2360 for (var i = 0, l = this.array.length; i < l; i++) { 2361 m = this.array[i]; 2362 if (m.origProp.w || m.mtl instanceof THREE.LineBasicMaterial) continue; 2363 m.mtl.wireframe = wireframe; 2364 } 2365 } 2366 2367 removeItem(material, dispose) { 2368 for (var i = this.array.length - 1; i >= 0; i--) { 2369 if (this.array[i].mtl === material) { 2370 this.array.splice(i, 1); 2371 break; 2372 } 2373 } 2374 if (dispose) material.dispose(); 2375 } 2376 2377 removeGroupItems(groupId) { 2378 for (var i = this.array.length - 1; i >= 0; i--) { 2379 if (this.array[i].groupId === groupId) { 2380 this.array.splice(i, 1); 2381 } 2382 } 2383 } 2384 2385} 2386 2387 2388class Q3DDEMBlockBase { 2389 2390 constructor() { 2391 this.materials = []; 2392 this.currentMtlIndex = 0; 2393 } 2394 2395 loadJSONObject(obj, layer, callback) { 2396 this.data = obj; 2397 2398 // load material 2399 var m, mtl; 2400 for (var i = 0, l = (obj.materials || []).length; i < l; i++) { 2401 m = obj.materials[i]; 2402 2403 mtl = new Q3DMaterial(); 2404 mtl.loadJSONObject(m, function () { 2405 layer.requestRender(); 2406 }); 2407 this.materials[m.mtlIndex] = mtl; 2408 2409 if (m.useNow) { 2410 this.currentMtlIndex = m.mtlIndex; 2411 if (this.obj) { 2412 layer.materials.removeItem(this.obj.material, true); 2413 2414 this.obj.material = mtl.mtl; 2415 layer.requestRender(); 2416 } 2417 layer.materials.add(mtl); 2418 } 2419 } 2420 } 2421 2422} 2423 2424 2425class Q3DDEMBlock extends Q3DDEMBlockBase { 2426 2427 loadJSONObject(obj, layer, callback) { 2428 super.loadJSONObject(obj, layer, callback); 2429 2430 if (obj.grid === undefined) return; 2431 2432 // create a plane geometry 2433 var geom, grid = obj.grid; 2434 if (layer.geometryCache) { 2435 var params = layer.geometryCache.parameters || {}; 2436 if (params.width === obj.width && params.height === obj.height && 2437 params.widthSegments === grid.width - 1 && params.heightSegments === grid.height - 1) { 2438 2439 geom = layer.geometryCache.clone(); 2440 geom.parameters = layer.geometryCache.parameters; 2441 } 2442 } 2443 geom = geom || new THREE.PlaneBufferGeometry(obj.width, obj.height, grid.width - 1, grid.height - 1); 2444 layer.geometryCache = geom; 2445 2446 // create a mesh 2447 var mesh = new THREE.Mesh(geom, (this.materials[this.currentMtlIndex] || {}).mtl); 2448 mesh.position.fromArray(obj.translate); 2449 mesh.scale.z = obj.zScale; 2450 layer.addObject(mesh); 2451 2452 // set z values 2453 var buildGeometry = function (grid_values) { 2454 var vertices = geom.attributes.position.array; 2455 for (var i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) { 2456 vertices[j + 2] = grid_values[i]; 2457 } 2458 geom.attributes.position.needsUpdate = true; 2459 geom.computeVertexNormals(); 2460 2461 if (callback) callback(mesh); 2462 }; 2463 2464 if (grid.url !== undefined) { 2465 Q3D.application.loadFile(grid.url, "arraybuffer", function (buf) { 2466 grid.array = new Float32Array(buf); 2467 buildGeometry(grid.array); 2468 }); 2469 } 2470 else { 2471 if (grid.binary !== undefined) { 2472 // WebKit Bridge 2473 grid.array = new Float32Array(grid.binary.buffer, 0, grid.width * grid.height); 2474 } 2475 buildGeometry(grid.array); 2476 } 2477 2478 this.obj = mesh; 2479 return mesh; 2480 } 2481 2482 buildSides(layer, parent, material, z0) {
2483 var planeWidth = this.data.width, 2484 planeHeight = this.data.height, 2485 grid = this.data.grid, 2486 grid_values = grid.array, 2487 w = grid.width, 2488 h = grid.height, 2489 k = w * (h - 1); 2490 2491 var band_width = -2 * z0; 2492 2493 // front and back 2494 var geom_fr = new THREE.PlaneBufferGeometry(planeWidth, band_width, w - 1, 1), 2495 geom_ba = geom_fr.clone(); 2496 2497 var vertices_fr = geom_fr.attributes.position.array, 2498 vertices_ba = geom_ba.attributes.position.array; 2499 2500 var i, mesh; 2501 for (i = 0; i < w; i++) { 2502 vertices_fr[i * 3 + 1] = grid_values[k + i]; 2503 vertices_ba[i * 3 + 1] = grid_values[w - 1 - i]; 2504 } 2505 mesh = new THREE.Mesh(geom_fr, material); 2506 mesh.rotation.x = Math.PI / 2; 2507 mesh.position.y = -planeHeight / 2; 2508 mesh.name = "side"; 2509 parent.add(mesh); 2510 2511 mesh = new THREE.Mesh(geom_ba, material); 2512 mesh.rotation.x = Math.PI / 2; 2513 mesh.rotation.y = Math.PI; 2514 mesh.position.y = planeHeight / 2; 2515 mesh.name = "side"; 2516 parent.add(mesh); 2517 2518 // left and right 2519 var geom_le = new THREE.PlaneBufferGeometry(band_width, planeHeight, 1, h - 1), 2520 geom_ri = geom_le.clone(); 2521 2522 var vertices_le = geom_le.attributes.position.array, 2523 vertices_ri = geom_ri.attributes.position.array; 2524 2525 for (i = 0; i < h; i++) { 2526 vertices_le[(i * 2 + 1) * 3] = grid_values[w * i]; 2527 vertices_ri[i * 2 * 3] = -grid_values[w * (i + 1) - 1]; 2528 } 2529 mesh = new THREE.Mesh(geom_le, material); 2530 mesh.rotation.y = -Math.PI / 2; 2531 mesh.position.x = -planeWidth / 2; 2532 mesh.name = "side"; 2533 parent.add(mesh); 2534 2535 mesh = new THREE.Mesh(geom_ri, material); 2536 mesh.rotation.y = Math.PI / 2; 2537 mesh.position.x = planeWidth / 2; 2538 mesh.name = "side"; 2539 parent.add(mesh); 2540 2541 // bottom 2542 var geom = new THREE.PlaneBufferGeometry(planeWidth, planeHeight, 1, 1); 2543 mesh = new THREE.Mesh(geom, material); 2544 mesh.rotation.x = Math.PI; 2545 mesh.position.z = z0; 2546 mesh.name = "bottom"; 2547 parent.add(mesh); 2548 2549 parent.updateMatrixWorld(); 2550 } 2551 2552 addEdges(layer, parent, material, z0) { 2553 2554 var i, x, y, 2555 grid = this.data.grid, 2556 grid_values = grid.array, 2557 w = grid.width, 2558 h = grid.height, 2559 k = w * (h - 1), 2560 planeWidth = this.data.width, 2561 planeHeight = this.data.height, 2562 hpw = planeWidth / 2, 2563 hph = planeHeight / 2, 2564 psw = planeWidth / (w - 1), 2565 psh = planeHeight / (h - 1); 2566 2567 var vl = []; 2568 2569 // terrain edges 2570 var vl_fr = [], 2571 vl_bk = [], 2572 vl_le = [], 2573 vl_ri = []; 2574 2575 for (i = 0; i < w; i++) { 2576 x = -hpw + psw * i; 2577 vl_fr.push(x, -hph, grid_values[k + i]); 2578 vl_bk.push(x, hph, grid_values[i]); 2579 } 2580 2581 for (i = 0; i < h; i++) { 2582 y = hph - psh * i; 2583 vl_le.push(-hpw, y, grid_values[w * i]); 2584 vl_ri.push(hpw, y, grid_values[w * (i + 1) - 1]); 2585 } 2586 2587 vl.push(vl_fr, vl_bk, vl_le, vl_ri); 2588 2589 if (z0 !== undefined) { 2590 // horizontal rectangle at bottom 2591 vl.push([-hpw, -hph, z0, 2592 hpw, -hph, z0, 2593 hpw, hph, z0, 2594 -hpw, hph, z0, 2595 -hpw, -hph, z0]); 2596 2597 // vertical lines at corners 2598 [[-hpw, -hph, grid_values[grid_values.length - w]], 2599 [ hpw, -hph, grid_values[grid_values.length - 1]], 2600 [ hpw, hph, grid_values[w - 1]], 2601 [-hpw, hph, grid_values[0]]].forEach(function (v) { 2602 2603 vl.push([v[0], v[1], v[2], v[0], v[1], z0]); 2604 2605 }); 2606 } 2607 2608 vl.forEach(function (v) { 2609 2610 var geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(v, 3)); 2611 var obj = new THREE.Line(geom, material); 2612 obj.name = "frame"; 2613 parent.add(obj); 2614 2615 }); 2616 2617 parent.updateMatrixWorld(); 2618 } 2619 2620 // add quad wireframe 2621 addWireframe(layer, parent, material) { 2622 2623 var grid = this.data.grid, 2624 grid_values = grid.array, 2625 w = grid.width, 2626 h = grid.height, 2627 planeWidth = this.data.width, 2628 planeHeight = this.data.height, 2629 hpw = planeWidth / 2, 2630 hph = planeHeight / 2, 2631 psw = planeWidth / (w - 1), 2632 psh = planeHeight / (h - 1); 2633 2634 var v, geom, x, y, vx, vy, group = new THREE.Group(); 2635 2636 for (x = w - 1; x >= 0; x--) { 2637 v = []; 2638 vx = -hpw + psw * x; 2639 2640 for (y = h - 1; y >= 0; y--) { 2641 v.push(vx, hph - psh * y, grid_values[x + w * y]); 2642 } 2643 2644 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(v, 3)); 2645 2646 group.add(new THREE.Line(geom, material)); 2647 } 2648 2649 for (y = h - 1; y >= 0; y--) { 2650 v = []; 2651 vy = hph - psh * y; 2652
2653 for (x = w - 1; x >= 0; x--) { 2654 v.push(-hpw + psw * x, vy, grid_values[x + w * y]); 2655 } 2656 2657 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(v, 3)); 2658 2659 group.add(new THREE.Line(geom, material)); 2660 } 2661 2662 parent.add(group); 2663 parent.updateMatrixWorld(); 2664 } 2665 2666 getValue(x, y) { 2667 var grid = this.data.grid; 2668 if (0 <= x && x < grid.width && 0 <= y && y < grid.height) return grid.array[x + grid.width * y]; 2669 return null; 2670 } 2671 2672 contains(x, y) { 2673 var translate = this.data.translate, 2674 xmin = translate[0] - this.data.width / 2, 2675 xmax = translate[0] + this.data.width / 2, 2676 ymin = translate[1] - this.data.height / 2, 2677 ymax = translate[1] + this.data.height / 2; 2678 if (xmin <= x && x <= xmax && ymin <= y && y <= ymax) return true; 2679 return false; 2680 } 2681} 2682 2683 2684class Q3DClippedDEMBlock extends Q3DDEMBlockBase { 2685 2686 loadJSONObject(obj, layer, callback) { 2687 super.loadJSONObject(obj, layer, callback); 2688 2689 if (obj.geom === undefined) return; 2690 2691 var geom = new THREE.BufferGeometry(), 2692 mesh = new THREE.Mesh(geom, (this.materials[this.currentMtlIndex] || {}).mtl); 2693 mesh.position.fromArray(obj.translate); 2694 mesh.scale.z = obj.zScale; 2695 layer.addObject(mesh); 2696 2697 var _this = this; 2698 var buildGeometry = function (obj) { 2699 2700 var v = obj.triangles.v, 2701 origin = layer.sceneData.origin, 2702 be = layer.sceneData.baseExtent, 2703 base_width = be.width, 2704 base_height = be.height, 2705 x0 = be.cx - origin.x - base_width * 0.5, 2706 y0 = be.cy - origin.y - base_height * 0.5; 2707 2708 var normals = [], uvs = []; 2709 for (var i = 0, l = v.length; i < l; i += 3) { 2710 normals.push(0, 0, 1); 2711 uvs.push((v[i] - x0) / base_width, (v[i + 1] - y0) / base_height); 2712 } 2713 2714 geom.setIndex(obj.triangles.f); 2715 geom.setAttribute("position", new THREE.Float32BufferAttribute(v, 3)); 2716 geom.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3)); 2717 geom.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2)); 2718 geom.computeVertexNormals(); 2719 2720 geom.attributes.position.needsUpdate = true; 2721 geom.attributes.normal.needsUpdate = true; 2722 geom.attributes.uv.needsUpdate = true; 2723 2724 _this.data.polygons = obj.polygons; 2725 if (callback) callback(mesh); 2726 }; 2727 2728 if (obj.geom.url !== undefined) { 2729 Q3D.application.loadFile(obj.geom.url, "json", function (obj) { 2730 buildGeometry(obj); 2731 }); 2732 } 2733 else { // local mode or WebKit Bridge 2734 buildGeometry(obj.geom); 2735 } 2736 2737 this.obj = mesh; 2738 return mesh; 2739 } 2740 2741 buildSides(layer, parent, material, z0) { 2742 var polygons = this.data.polygons,
2743 bzFunc = function (x, y) { return z0; }; 2744 2745 // make back-side material for bottom 2746 var mat_back = material.clone(); 2747 mat_back.side = THREE.BackSide; 2748 layer.materials.add(mat_back); 2749 2750 var geom, mesh, shape; 2751 for (var i = 0, l = polygons.length; i < l; i++) { 2752 var bnds = polygons[i]; 2753 2754 // sides 2755 for (var j = 0, m = bnds.length; j < m; j++) { 2756 geom = Q3D.Utils.createWallGeometry(bnds[j], bzFunc, true); 2757 mesh = new THREE.Mesh(geom, material); 2758 mesh.name = "side"; 2759 parent.add(mesh); 2760 } 2761 2762 // bottom 2763 shape = new THREE.Shape(Q3D.Utils.flatArrayToVec2Array(bnds[0], 3)); 2764 for (j = 1, m = bnds.length; j < m; j++) { 2765 shape.holes.push(new THREE.Path(Q3D.Utils.flatArrayToVec2Array(bnds[j], 3))); 2766 } 2767 geom = new THREE.ShapeBufferGeometry(shape); 2768 mesh = new THREE.Mesh(geom, mat_back); 2769 mesh.position.z = z0; 2770 mesh.name = "bottom"; 2771 parent.add(mesh); 2772 } 2773 parent.updateMatrixWorld(); 2774 } 2775 2776 // not implemented 2777 getValue(x, y) { 2778 return null; 2779 } 2780 2781 // not implemented 2782 contains(x, y) { 2783 return false; 2784 } 2785 2786} 2787 2788 2789class Q3DMapLayer extends THREE.EventDispatcher { 2790 2791 constructor() { 2792 super(); 2793 2794 this.properties = {}; 2795 2796 this.materials = new Q3DMaterials(); 2797 this.materials.addEventListener("renderRequest", this.requestRender.bind(this)); 2798 2799 this.objectGroup = new Q3DGroup(); 2800 this.objectGroup.name = "layer"; 2801 this.objects = []; 2802 } 2803 2804 addObject(object) { 2805 object.userData.layerId = this.id; 2806 this.objectGroup.add(object); 2807 2808 var o = this.objects; 2809 object.traverse(function (obj) { 2810 o.push(obj); 2811 }); 2812 return this.objectGroup.children.length - 1; 2813 } 2814 2815 addObjects(objects) { 2816 for (var i = 0; i < objects.length; i++) { 2817 this.addObject(objects[i]); 2818 } 2819 } 2820 2821 clearObjects() { 2822 // dispose of geometries 2823 this.objectGroup.traverse(function (obj) { 2824 if (obj.geometry) obj.geometry.dispose(); 2825 }); 2826 2827 // dispose of materials 2828 this.materials.dispose(); 2829 2830 // remove all child objects from object group 2831 for (var i = this.objectGroup.children.length - 1; i >= 0; i--) { 2832 this.objectGroup.remove(this.objectGroup.children[i]); 2833 } 2834 this.objects = []; 2835 } 2836 2837 visibleObjects() { 2838 return (this.visible) ? this.objects : []; 2839 } 2840 2841 loadJSONObject(jsonObject, scene) { 2842 if (jsonObject.type == "layer") { 2843 // properties 2844 if (jsonObject.properties !== undefined) { 2845 this.properties = jsonObject.properties; 2846 this.visible = (jsonObject.properties.visible || Q3D.Config.allVisible) ? true : false; 2847 } 2848 2849 if (jsonObject.data !== undefined) { 2850 this.clearObjects(); 2851 2852 // materials 2853 if (jsonObject.data.materials !== undefined) { 2854 this.materials.loadJSONObject(jsonObject.data.materials); 2855 } 2856 } 2857 2858 this.sceneData = scene.userData; 2859 } 2860 } 2861 2862 get clickable() { 2863 return this.properties.clickable; 2864 } 2865 2866 get opacity() { 2867 return this.materials.opacity(); 2868 } 2869 2870 set opacity(value) { 2871 this.materials.setOpacity(value); 2872 2873 if (this.labelGroup) { 2874 this.labelGroup.traverse(function (obj) { 2875 if (obj.material) obj.material.opacity = value; 2876 }); 2877 } 2878 2879 if (this.labelConnectorGroup && this.labelConnectorGroup.children.length) { 2880 this.labelConnectorGroup.children[0].material.opacity = value; 2881 } 2882 2883 this.requestRender(); 2884 } 2885 2886 get visible() { 2887 return this.objectGroup.visible; 2888 } 2889 2890 set visible(value) { 2891 this.objectGroup.visible = value; 2892 this.requestRender(); 2893 } 2894 2895 boundingBox() { 2896 return new THREE.Box3().setFromObject(this.objectGroup); 2897 } 2898 2899 setWireframeMode(wireframe) { 2900 this.materials.setWireframeMode(wireframe); 2901 } 2902 2903 requestRender() { 2904 this.dispatchEvent({type: "renderRequest"}); 2905 } 2906 2907} 2908 2909 2910class Q3DDEMLayer extends Q3DMapLayer { 2911 2912 constructor() { 2913 super(); 2914 this.type = Q3D.LayerType.DEM; 2915 this.blocks = []; 2916 } 2917 2918 loadJSONObject(jsonObject, scene) { 2919 var old_blockIsClipped = this.properties.clipped; 2920 2921 super.loadJSONObject(jsonObject, scene); 2922 if (jsonObject.type == "layer") { 2923 if (old_blockIsClipped !== jsonObject.properties.clipped) { 2924 // DEM type changed 2925 this.blocks = []; 2926 } 2927 2928 var p = scene.userData, 2929 rotation = p.baseExtent.rotation; 2930 2931 if (jsonObject.properties.clipped) { 2932 this.objectGroup.position.set(0, 0, 0); 2933 this.objectGroup.rotation.z = 0; 2934 2935 if (rotation) { 2936 // rotate around center of base extent 2937 this.objectGroup.position.copy(p.pivot).negate(); 2938 this.objectGroup.position.applyAxisAngle(Q3D.uv.k, rotation * Q3D.deg2rad); 2939 this.objectGroup.position.add(p.pivot); 2940 this.objectGroup.rotateOnAxis(Q3D.uv.k, rotation * Q3D.deg2rad); 2941 } 2942 } 2943 else { 2944 this.objectGroup.position.copy(p.pivot); 2945 this.objectGroup.position.z *= p.zScale; 2946 this.objectGroup.rotation.z = rotation * Q3D.deg2rad; 2947 } 2948 this.objectGroup.updateMatrixWorld(); 2949 2950 if (jsonObject.data !== undefined) { 2951 jsonObject.data.forEach(function (obj) { 2952 this.buildBlock(obj, scene, this); 2953 }, this); 2954 } 2955 } 2956 else if (jsonObject.type == "block") { 2957 this.buildBlock(jsonObject, scene, this); 2958 } 2959 } 2960 2961 buildBlock(jsonObject, scene, layer) { 2962 var _this = this, 2963 block = this.blocks[jsonObject.block]; 2964 2965 if (block === undefined) { 2966 block = (layer.properties.clipped) ? (new Q3DClippedDEMBlock()) : (ne
2966w Q3DDEMBlock()); 2967 this.blocks[jsonObject.block] = block; 2968 } 2969 2970 block.loadJSONObject(jsonObject, this, function (mesh) { 2971 2972 var material; 2973 if (jsonObject.wireframe) { 2974 material = new Q3DMaterial(); 2975 material.loadJSONObject(jsonObject.wireframe.mtl); 2976 _this.materials.add(material); 2977 2978 block.addWireframe(_this, mesh, material.mtl); 2979 2980 var mtl = block.material.mtl; 2981 mtl.polygonOffset = true; 2982 mtl.polygonOffsetFactor = 1; 2983 mtl.polygonOffsetUnits = 1; 2984 } 2985 2986 if (jsonObject.sides) { 2987 // sides and bottom 2988 material = new Q3DMaterial(); 2989 material.loadJSONObject(jsonObject.sides.mtl); 2990 _this.materials.add(material); 2991 2992 block.buildSides(_this, mesh, material.mtl, jsonObject.sides.bottom); 2993 _this.sideVisible = true; 2994 } 2995 2996 if (jsonObject.edges) { 2997 material = new Q3DMaterial(); 2998 material.loadJSONObject(jsonObject.edges.mtl); 2999 _this.materials.add(material); 3000 3001 block.addEdges(_this, mesh, material.mtl, (jsonObject.sides) ? jsonObject.sides.bottom : undefined); 3002 } 3003 3004 _this.requestRender(); 3005 }); 3006 } 3007 3008 // calculate elevation at the coordinates (x, y) on triangle face 3009 getZ(x, y) { 3010 for (var i = 0, l = this.blocks.length; i < l; i++) { 3011 var block = this.blocks[i], 3012 data = block.data; 3013 3014 if (!block.contains(x, y)) continue; 3015 3016 var ix = data.width / (data.grid.width - 1), 3017 iy = data.height / (data.grid.height - 1); 3018 3019 var xmin = data.translate[0] - data.width / 2, 3020 ymax = data.translate[1] + data.height / 2; 3021 3022 var mx0 = Math.floor((x - xmin) / ix), 3023 my0 = Math.floor((ymax - y) / iy); 3024 3025 var z = [block.getValue(mx0, my0), 3026 block.getValue(mx0 + 1, my0), 3027 block.getValue(mx0, my0 + 1), 3028 block.getValue(mx0 + 1, my0 + 1)]; 3029 3030 var px0 = xmin + ix * mx0, 3031 py0 = ymax - iy * my0; 3032 3033 var sdx = (x - px0) / ix, 3034 sdy = (py0 - y) / iy; 3035 3036 if (sdx <= 1 - sdy) return z[0] + (z[1] - z[0]) * sdx + (z[2] - z[0]) * sdy; 3037 else return z[3] + (z[2] - z[3]) * (1 - sdx) + (z[1] - z[3]) * (1 - sdy); 3038 } 3039 return null; 3040 } 3041 3042 segmentizeLineString(lineString, zFunc) { 3043 // does not support multiple blocks 3044 if (zFunc === undefined) zFunc = function () { return 0; }; 3045 var width = this.sceneData.width, 3046 height = this.sceneData.height; 3047 var xmin = -width / 2, 3048 ymax = height / 2; 3049 var grid = this.blocks[0].data.grid, 3050 ix = width / (grid.width - 1), 3051 iy = height / (grid.height - 1); 3052 var sort_func = function (a, b) { return a - b; }; 3053 3054 var pts = []; 3055 for (var i = 1, l = lineString.length; i < l; i++) { 3056 var pt1 = lineString[i - 1], pt2 = lineString[i]; 3057 var x1 = pt1[0], x2 = pt2[0], y1 = pt1[1], y2 = pt2[1], z1 = pt1[2], z2 = pt2[2]; 3058 var nx1 = (x1 - xmin) / ix, 3059 nx2 = (x2 - xmin) / ix; 3060 var ny1 = (ymax - y1) / iy, 3061 ny2 = (ymax - y2) / iy; 3062 var ns1 = Math.abs(ny1 + nx1), 3063 ns2 = Math.abs(ny2 + nx2); 3064 3065 var p = [0], nvp = [[nx1, nx2], [ny1, ny2], [ns1, ns2]]; 3066 for (var j = 0; j < 3; j++) { 3067 var v1 = nvp[j][0], v2 = nvp[j][1]; 3068 if (v1 == v2) continue; 3069 var k = Math.ceil(Math.min(v1, v2)); 3070 var n = Math.floor(Math.max(v1, v2)); 3071 for (; k <= n; k++) { 3072 p.push((k - v1) / (v2 - v1)); 3073 } 3074 } 3075 3076 p.sort(sort_func); 3077 3078 var x, y, z, lp = null; 3079 for (var j = 0, m = p.length; j < m; j++) { 3080 if (lp === p[j]) continue; 3081 if (p[j] == 1) break; 3082 3083 x = x1 + (x2 - x1) * p[j]; 3084 y = y1 + (y2 - y1) * p[j]; 3085 3086 if (z1 === undefined || z2 === undefined) z = zFunc(x, y); 3087 else z = z1 + (z2 - z1) * p[j]; 3088 3089 pts.push(new THREE.Vector3(x, y, z)); 3090 3091 // Q3D.Utils.putStick(x, y, zFunc); 3092 3093 lp = p[j]; 3094 } 3095 } 3096 // last point (= the first point) 3097 var pt = lineString[lineString.length - 1]; 3098 pts.push(new THREE.Vector3(pt[0], pt[1], (pt[2] === undefined) ? zFunc(pt[0], pt[1]) : pt[2])); 3099 3100 /* 3101 for (var i = 0, l = lineString.length - 1; i < l; i++) { 3102 Q3D.Utils.putStick(lineString[i][0], lineString[i][1], zFunc, 0.8); 3103 } 3104 */ 3105 return pts; 3106 } 3107 3108 setCurrentMaterial(mtlIndex) { 3109 3110 this.materials.removeGroupItems(this.currentMtlIndex); 3111 3112 this.currentMtlIndex = mtlIndex; 3113 3114 var b, m; 3115 for (var i = 0, l = this.blocks.length; i < l; i++) { 3116 b = this.blocks[i]; 3117 m = b.materials[mtlIndex]; 3118 if (m !== undefined) { 3119 b.obj.material = m.mtl; 3120 this.materials.add(m); 3121 } 3122 } 3123 this.requestRender(); 3124 } 3125 3126 setSideVisible(visible) { 3127 this.sideVisible = visible; 3128 this.objectGroup.traverse(function (obj) { 3129 if (obj.name == "side" || obj.name == "bottom") obj.visible = visible; 3130 }); 3131 } 3132 3133 // texture animation 3134 prepareTexAnimation(from, to) { 3135 3136 function imageData2Canvas(img) { 3137 var cnvs = document.createElement("canvas"); 3138 cnvs.width = img.width;
3139 cnvs.height = img.height; 3140 3141 var ctx = cnvs.getContext("2d"); 3142 ctx.putImageData(img, 0, 0); 3143 return cnvs; 3144 } 3145 3146 this.anim = []; 3147 3148 var m, canvas, ctx, opt, mtl; 3149 var img_from, img_to; 3150 for (var i = 0; i < this.blocks.length; i++) { 3151 3152 m = this.blocks[i].obj.material; 3153 3154 img_from = this.blocks[i].materials[from].mtl.map.image; 3155 img_to = this.blocks[i].materials[to].mtl.map.image; 3156 3157 canvas = document.createElement("canvas"); 3158 canvas.width = (img_from.width > img_to.width) ? img_from.width : img_to.width; 3159 canvas.height = (img_from.width > img_to.width) ? img_from.height : img_to.height; 3160 3161 ctx = canvas.getContext("2d"); 3162 3163 opt = {}; 3164 opt.map = new THREE.CanvasTexture(canvas); 3165 opt.map.anisotropy = Q3D.Config.texture.anisotropy; 3166 opt.transparent = true; 3167 3168 mtl = undefined; 3169 if (m) { 3170 if (m.isMeshToonMaterial) { 3171 mtl = new THREE.MeshToonMaterial(opt); 3172 } 3173 else if (m.isMeshPhongMaterial) { 3174 mtl = new THREE.MeshPhongMaterial(opt); 3175 } 3176 } 3177 if (mtl === undefined) { 3178 mtl = new THREE.MeshLambertMaterial(opt); 3179 } 3180 3181 if (img_from instanceof ImageData) { // WebKit Bridge 3182 img_from = imageData2Canvas(img_from); 3183 img_to = imageData2Canvas(img_to); 3184 } 3185 3186 this.blocks[i].obj.material = mtl; 3187 3188 this.materials.add(mtl); 3189 3190 this.anim.push({ 3191 img_from: img_from, 3192 img_to: img_to, 3193 ctx: ctx, 3194 tex: mtl.map 3195 }); 3196 } 3197 } 3198 3199 setTextureAt(progress, effect) { 3200 3201 if (this.anim === undefined) return; 3202 3203 var a, w, h, w0, h0, w1, h1, ew, ew1; 3204 for (var i = 0; i < this.anim.length; i++) { 3205 a = this.anim[i]; 3206 w = a.ctx.canvas.width; 3207 h = a.ctx.canvas.height; 3208 w0 = a.img_from.width; 3209 h0 = a.img_from.height; 3210 w1 = a.img_to.width; 3211 h1 = a.img_to.height; 3212 3213 if (effect == 0) { // fade in 3214 a.ctx.globalAlpha = 1; 3215 a.ctx.drawImage(a.img_from, 0, 0, w0, h0, 3216 0, 0, w, h); 3217 a.ctx.globalAlpha = progress; 3218 a.ctx.drawImage(a.img_to, 0, 0, w1, h1, 3219 0, 0, w, h); 3220 } 3221 else if (effect == 2) { // slide to left (not used) 3222 if (progress === null) { 3223 a.ctx.drawImage(a.img_from, 0, 0, w0, h0, 3224 0, 0, w, h); 3225 } 3226 else { 3227 ew1 = w1 * progress; 3228 ew = w * progress; 3229 a.ctx.drawImage(a.img_to, w1 - ew1, 0, ew1, h1, 3230 w - ew, 0, ew, h); 3231 } 3232 } 3233 a.tex.needsUpdate = true; 3234 } 3235 } 3236} 3237 3238 3239class Q3DVectorLayer extends Q3DMapLayer { 3240 3241 constructor() { 3242 super(); 3243 this.features = []; 3244 this.labels = []; 3245 } 3246 3247 // build(features, startIndex) {} 3248 3249 addFeature(featureIdx, f, objs) { 3250 super.addObjects(objs); 3251 3252 for (var i = 0; i < objs.length; i++) { 3253 objs[i].userData.featureIdx = featureIdx; 3254 } 3255 f.objs = objs; 3256 3257 this.features[featureIdx] = f; 3258 return f; 3259 } 3260 3261 clearLabels() { 3262 this.labels = []; 3263 if (this.labelGroup) this.labelGroup.clear(); 3264 if (this.labelConnectorGroup) this.labelConnectorGroup.clear(); 3265 } 3266 3267 buildLabels(features, getPointsFunc) { 3268 if (this.properties.label === undefined || getPointsFunc === undefined) return; 3269 3270 var _this = this, 3271 p = this.properties, 3272 label = p.label, 3273 bs = this.sceneData.baseExtent.width * 0.016, 3274 sc = bs * Math.pow(1.2, label.size); 3275 3276 var hasOtl = (label.olcolor !== undefined), 3277 hasConn = (label.cncolor !== undefined); 3278 3279 if (hasConn) { 3280 var line_mtl = new THREE.LineBasicMaterial({ 3281 color: label.cncolor, 3282 opacity: this.materials.opacity(), 3283 transparent: true 3284 }); 3285 } 3286 3287 var hasUnderline = Boolean(hasConn && label.underline); 3288 if (hasUnderline) { 3289 var ul_geom = new THREE.BufferGeometry(); 3290 ul_geom.setAttribute("position", new THREE.Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3)); 3291 3292 var onBeforeRender = function (renderer, scene, camera, geometry, material, group) { 3293 this.quaternion.copy(camera.quaternion); 3294 this.updateMatrixWorld(); 3295 }; 3296 } 3297 3298 var canvas = document.createElement("canvas"), 3299 ctx = canvas.getContext("2d"); 3300 3301 var font, tw, th, cw, ch; 3302 th = ch = Q3D.Config.label.canvasHeight; 3303 font = th + "px " + (label.font || "sans-serif"); 3304 3305 canvas.height = ch; 3306 3307 var f, text, partIdx, vec, sprite, mtl, geom, conn, x, y, j, sc, opacity; 3308 var underline; 3309 3310 for (var i = 0, l = features.length; i < l; i++) { 3311 f = features[i]; 3312 text = f.lbl; 3313 if (!text) continue; 3314 3315 opacity = this.materials.mtl(f.mtl).opacity; 3316 3317 partIdx = 0; 3318 getPointsFunc(f).forEach(function (pt) { 3319 3320 // label position 3321 vec = new THREE.Vector3(pt[0], pt[1], (label.relative) ? pt[2] + f.lh : f.lh); 3322 3323 // render label text 3324 ctx.font = font; 3325 tw = ctx.measureText(text).width + 2; 3326 cw = THREE.Math.ceilPowerOfTwo(tw); 3327 x = cw / 2; 3328 y = ch / 2; 3329 3330 canvas.width = cw; 3331 ctx.clearRect(0, 0, cw, ch); 3332 3333 if (label.bgcolor !== undefined) { 3334 ctx.fillStyle = label.bgcolor; 3335 ctx.roundRect((cw - tw) / 2, (ch - th) / 2, tw, th, 4).fill(); // definition is in this file 3336 } 3337 3338 ctx.font = font; 3339 ctx.textAlign = "center"; 3340 ctx.textBaseline = "middle"; 3341 3342 if (hasOtl) { 3343 // outline effect
3344 ctx.fillStyle = label.olcolor; 3345 for (j = 0; j < 9; j++) { 3346 if (j != 4) ctx.fillText(text, x + Math.floor(j / 3) - 1, y + j % 3 - 1); 3347 } 3348 } 3349 3350 ctx.fillStyle = label.color; 3351 ctx.fillText(text, x, y); 3352 3353 mtl = new THREE.SpriteMaterial({ 3354 map: new THREE.TextureLoader().load(canvas.toDataURL(), function () { _this.requestRender(); }), 3355 opacity: opacity, 3356 transparent: true 3357 }); 3358 3359 sprite = new THREE.Sprite(mtl); 3360 if (hasUnderline) { 3361 sprite.center.set((1 - tw / cw) * 0.5, 0); 3362 } 3363 else { 3364 sprite.center.set(0.5, 0); 3365 } 3366 sprite.position.copy(vec); 3367 sprite.scale.set(sc * cw / ch, sc, 1); 3368 3369 sprite.userData.layerId = this.id; 3370 sprite.userData.properties = f.prop; 3371 sprite.userData.objs = f.objs; 3372 sprite.userData.partIdx = partIdx; 3373 sprite.userData.isLabel = true; 3374 3375 this.labelGroup.add(sprite); 3376 3377 if (Q3D.Config.label.clickable) this.labels.push(sprite); 3378 3379 if (hasConn) { 3380 // a connector 3381 geom = new THREE.BufferGeometry(); 3382 geom.setAttribute("position", new THREE.Float32BufferAttribute(vec.toArray().concat(pt), 3)); 3383 3384 conn = new THREE.Line(geom, line_mtl); 3385 conn.userData = sprite.userData; 3386 3387 this.labelConnectorGroup.add(conn); 3388 3389 if (hasUnderline) { 3390 underline = new THREE.Line(ul_geom, line_mtl); 3391 underline.position.copy(vec); 3392 underline.scale.x = sc * tw / th; 3393 underline.updateMatrixWorld(); 3394 underline.onBeforeRender = onBeforeRender; 3395 conn.add(underline); 3396 } 3397 } 3398 partIdx++; 3399 }, this); 3400 } 3401 } 3402 3403 loadJSONObject(jsonObject, scene) { 3404 super.loadJSONObject(jsonObject, scene); 3405 if (jsonObject.type == "layer") { 3406 if (jsonObject.data !== undefined) { 3407 this.features = []; 3408 this.clearLabels(); 3409 3410 // build labels 3411 if (this.properties.label !== undefined) { 3412 // create a label group and a label connector group 3413 if (this.labelGroup === undefined) { 3414 this.labelGroup = new Q3DGroup(); 3415 this.labelGroup.userData.layerId = this.id; 3416 this.labelGroup.visible = this.visible; 3417 scene.labelGroup.add(this.labelGroup); 3418 } 3419 3420 if (this.labelConnectorGroup === undefined) { 3421 this.labelConnectorGroup = new Q3DGroup(); 3422 this.labelConnectorGroup.userData.layerId = this.id; 3423 this.labelConnectorGroup.visible = this.visible; 3424 scene.labelConnectorGroup.add(this.labelConnectorGroup); 3425 } 3426 } 3427 3428 (jsonObject.data.blocks || []).forEach(function (block) { 3429 if (block.url !== undefined) Q3D.application.loadJSONFile(block.url); 3430 else { 3431 this.build(block.features, block.startIndex); 3432 if (this.properties.label !== undefined) this.buildLabels(block.features); 3433 } 3434 }, this); 3435 } 3436 } 3437 else if (jsonObject.type == "block") { 3438 this.build(jsonObject.features, jsonObject.startIndex); 3439 if (this.properties.label !== undefined) this.buildLabels(jsonObject.features); 3440 } 3441 } 3442 3443 get visible() { 3444 return this.objectGroup.visible; 3445 // return super.visible; 3446 } 3447 3448 set visible(value) { 3449 if (this.labelGroup) this.labelGroup.visible = value; 3450 if (this.labelConnectorGroup) this.labelConnectorGroup.visible = value; 3451 3452 this.objectGroup.visible = value; 3453 this.requestRender(); 3454 // super.visible = value; 3455 } 3456 3457} 3458 3459 3460class Q3DPointLayer extends Q3DVectorLayer { 3461 3462 constructor() { 3463 super(); 3464 this.type = Q3D.LayerType.Point; 3465 } 3466 3467 loadJSONObject(jsonObject, scene) { 3468 if (jsonObject.type == "layer" && jsonObject.properties.objType == "3D Model" && jsonObject.data !== undefined) { 3469 if (this.models === undefined) { 3470 var _this = this; 3471 3472 this.models = new Q3DModels(); 3473 this.models.addEventListener("modelLoaded", function (event) { 3474 _this.materials.addFromObject3D(event.model.scene); 3475 _this.requestRender(); 3476 }); 3477 } 3478 else { 3479 this.models.clear(); 3480 } 3481 this.models.loadJSONObject(jsonObject.data.models); 3482 } 3483 3484 super.loadJSONObject(jsonObject, scene); 3485 } 3486 3487 build(features, startIndex) { 3488 var objType = this.properties.objType; 3489 if (objType == "Point") { 3490 return this.buildPoints(features, startIndex); 3491 } 3492 else if (objType == "Billboard") { 3493 return this.buildBillboards(features, startIndex); 3494 } 3495 else if (objType == "3D Model") { 3496 return this.buildModels(features, startIndex); 3497 } 3498 3499 var unitGeom, transform; 3500 if (this.cachedGeometryType === objType) { 3501 unitGeom = this.geometryCache; 3502 transform = this.transformCache; 3503 } 3504 else { 3505 var gt = this.geomAndTransformFunc(objType); 3506 unitGeom = gt[0]; 3507 transform = gt[1]; 3508 } 3509 3510 var f, mtl, pts, i, l, mesh, meshes; 3511 for (var fidx = 0; fidx < features.length; fidx++) { 3512 f = features[fidx]; 3513 pts = f.geom.pts; 3514 mtl = this.materials.mtl(f.mtl); 3515 3516 meshes = []; 3517 for (i = 0, l = pts.length; i < l; i++) { 3518 mesh = new THREE.Mesh(unitGeom, mtl); 3519 transform(mesh, f.geom, pts[i]); 3520 3521 mesh.userData.properties = f.prop; 3522 3523 meshes.push(mesh); 3524 } 3525 this.addFeature(fidx + startIndex, f, meshes); 3526 } 3527 3528 this.cachedGeometryType = objType; 3529 this.geometryCache = unitGeom; 3530 this.transformCache = transform; 3531 } 3532 3533 geomAndTransformFunc(objType) { 3534 3535 var deg2rad = Q3D.deg2rad, 3536 rx = 90 * deg2rad; 3537 3538 if (objType == "Sphere") { 3539 return [ 3540 new THREE.SphereBufferGeometry(1, 32, 32), 3541 function (mesh, geom, pt) { 3542 mesh.scale.setScalar(geom.r); 3543 mesh.position.fromArray(pt); 3544 } 3545 ]; 3546 } 3547 else if (objType == "Box") { 3548 return [ 3549 new THREE.BoxBufferGeometry(1, 1, 1), 3550 function (mesh, geom, pt) { 3551 mesh.scale.set(geom.w, geom.h, geom.d); 3552 mesh.rotation.x = rx; 3553 mesh.position.set(pt[0], pt[1], pt[2] + geom.h / 2); 3554 } 3555 ]; 3556 } 3557 else if (objType == "Disk") { 3558 var sz = this.sceneData.zScale; 3559 return [ 3560 new THREE.CircleBufferGeometry(1, 32), 3561 function (mesh, geom, pt) { 3562 mesh.scale.set(geom.r, geom.r * sz, 1); 3563 mesh.rotateOnWorldAxis(Q3D.uv.i, -geom.d * deg2rad); 3564 mesh.rotateOnWorldAxis(Q3D.uv.k, -geom.dd * deg2rad); 3565 mesh.position.fromArray(pt); 3566 } 3567 ]; 3568 } 3569 else if (objType == "Plane") { 3570 var sz = this.sceneData.zScale; 3571 return [ 3572 new THREE.PlaneBufferGeometry(1, 1, 1, 1), 3573 function (mesh, geom, pt) { 3574 mesh.scale.set(geom.w, geom.l * sz, 1); 3575 mesh.rotateOnWorldAxis(Q3D.uv.i, -geom.d * deg2rad); 3576 mesh.rotateOnWorldAxis(Q3D.uv.k, -geom.dd * deg2rad); 3577 mesh.position.fromArray(pt); 3578 } 3579 ]; 3580 } 3581 3582 // Cylinder or Cone 3583 var radiusTop = (objType == "Cylinder") ? 1 : 0; 3584 return [ 3585 new THREE.CylinderBufferGeometry(radiusTop, 1, 1, 32), 3586 function (mesh, geom, pt) { 3587 mesh.scale.set(geom.r, geom.h, geom.r); 3588 mesh.rotation.x = rx; 3589 mesh.position.set(pt[0], pt[1], pt[2] + geom.h / 2); 3590 } 3591 ]; 3592 } 3593 3594 buildPoints(features, startIndex) { 3595 var f, geom, mtl, obj; 3596 for (var fidx = 0; fidx < features.length; fidx++) { 3597 f = features[fidx]; 3598 3599 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(f.geom.pts, 3)); 3600 mtl = this.materials.mtl(f.mtl); 3601 3602 obj = new THREE.Points(geom, mtl); 3603 obj.userData.properties = f.prop; 3604 3605 this.addFeature(fidx + startIndex, f, [obj]); 3606 } 3607 } 3608 3609 buildBillboards(features, startIndex) { 3610
3611 features.forEach(function (f, fidx) { 3612 3613 var material = this.materials.get(f.mtl); 3614 3615 var sprite, sprites = []; 3616 for (var i = 0; i < f.geom.pts.length; i++) { 3617 sprite = new THREE.Sprite(material.mtl); 3618 sprite.position.fromArray(f.geom.pts[i]); 3619 sprite.userData.properties = f.prop; 3620 3621 sprites.push(sprite); 3622 } 3623 3624 material.callbackOnLoad(function () { 3625 var img = material.mtl.map.image; 3626 for (var i = 0; i < sprites.length; i++) { 3627 sprites[i].scale.set(f.geom.size, 3628 f.geom.size * img.height / img.width, 3629 1); 3630 sprites[i].updateMatrixWorld(); 3631 } 3632 }); 3633 3634 this.addFeature(fidx + startIndex, f, sprites); 3635 }, this); 3636 } 3637 3638 buildModels(features, startIndex) { 3639 var q = new THREE.Quaternion(), 3640 e = new THREE.Euler(), 3641 deg2rad = Q3D.deg2rad; 3642 3643 features.forEach(function (f, fidx) { 3644 3645 var model = this.models.get(f.model); 3646 if (!model) { 3647 console.log("3D Model: There is a missing model."); 3648 return; 3649 } 3650 3651 var parents = []; 3652 var pt, pts = f.geom.pts; 3653 for (var i = 0; i < pts.length; i++) { 3654 pt = pts[i]; 3655 3656 var parent = new Q3DGroup(); 3657 parent.position.fromArray(pt); 3658 parent.scale.set(1, 1, this.sceneData.zScale); 3659 3660 parent.userData.properties = f.prop; 3661 3662 parents.push(parent); 3663 } 3664 3665 model.callbackOnLoad(function (m) { 3666 var parent, obj; 3667 for (var i = 0; i < parents.length; i++) { 3668 parent = parents[i]; 3669 3670 obj = m.scene.clone(); 3671 obj.scale.setScalar(f.geom.scale); 3672 3673 if (obj.rotation.x) { 3674 // reset coordinate system to z-up and specified rotation 3675 obj.rotation.set(0, 0, 0); 3676 obj.quaternion.multiply(q.setFromEuler(e.set(f.geom.rotateX * deg2rad, 3677 f.geom.rotateY * deg2rad, 3678 f.geom.rotateZ * deg2rad, 3679 f.geom.rotateO || "XYZ"))); 3680 } 3681 else { 3682 // y-up to z-up and specified rotation 3683 obj.quaternion.multiply(q.setFromEuler(e.set(f.geom.rotateX * deg2rad, 3684 f.geom.rotateY * deg2rad, 3685 f.geom.rotateZ * deg2rad, 3686 f.geom.rotateO || "XYZ"))); 3687 obj.quaternion.multiply(q.setFromEuler(e.set(Math.PI / 2, 0, 0))); 3688 } 3689 parent.add(obj); 3690 } 3691 }); 3692 3693 this.addFeature(fidx + startIndex, f, parents); 3694 }, this); 3695 } 3696 3697 buildLabels(features) { 3698 super.buildLabels(features, function (f) { return f.geom.pts; }); 3699 } 3700 3701} 3702 3703 3704class Q3DLineLayer extends Q3DVectorLayer { 3705 3706 constructor() { 3707 super(); 3708 this.type = Q3D.LayerType.Line; 3709 } 3710 3711 clearObjects() { 3712 super.clearObjects(); 3713 3714 if (this.origMtls) { 3715 this.origMtls.dispose(); 3716 this.origMtls = undefined; 3717 } 3718 } 3719 3720 build(features, startIndex) { 3721 3722 if (this._lastObjType !== this.properties.objType) this._createObject = null; 3723 3724 var createObject = this._createObject || this.createObjFunc(this.properties.objType); 3725 3726 var f, i, lines, obj, objs; 3727 for (var fidx = 0; fidx < features.length; fidx++) { 3728 f = features[fidx]; 3729 lines = f.geom.lines; 3730 3731 objs = []; 3732 for (i = 0; i < lines.length; i++) { 3733 obj = createObject(f, lines[i]); 3734 obj.userData.properties = f.prop; 3735 obj.userData.mtl = f.mtl; 3736 3737 objs.push(obj); 3738 } 3739 this.addFeature(fidx + startIndex, f, objs); 3740 } 3741 3742 this._lastObjType = this.properties.objType; 3743 this._createObject = createObject; 3744 } 3745 3746 createObjFunc(objType) { 3747 var materials = this.materials; 3748 3749 if (objType == "Line") { 3750 return function (f, vertices) { 3751 var geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3)); 3752 3753 var obj = new THREE.Line(geom, materials.mtl(f.mtl)); 3754 if (obj.material instanceof THREE.LineDashedMaterial) obj.computeLineDistances(); 3755 return obj; 3756 }; 3757 } 3758 else if (objType == "Thick Line") { 3759 return function (f, vertices) { 3760 var line = new MeshLine(); 3761 line.setPoints(vertices); 3762 3763 return new THREE.Mesh(line, materials.mtl(f.mtl)); 3764 }; 3765 } 3766 else if (objType == "Pipe" || objType == "Cone") { 3767 var jointGeom, cylinGeom; 3768 if (objType == "Pipe") { 3769 jointGeom = new THREE.SphereBufferGeometry(1, 32, 32); 3770 cylinGeom = new THREE.CylinderBufferGeometry(1, 1, 1, 32); 3771 } 3772 else { 3773 cylinGeom = new THREE.CylinderBufferGeometry(0, 1, 1, 32); 3774 } 3775 3776 var group, mesh, axis = Q3D.uv.j; 3777 var pt0 = new THREE.Vector3(), pt1 = new THREE.Vector3(), sub = new THREE.Vector3(); 3778 3779 return function (f, points) { 3780 group = new Q3DGroup(); 3781 3782 pt0.fromArray(points[0]); 3783 for (var i = 1, l = points.length; i < l; i++) { 3784 pt1.fromArray(points[i]); 3785 3786 mesh = new THREE.Mesh(cylinGeom, materials.mtl(f.mtl)); 3787 mesh.scale.set(f.geom.r, pt0.distanceTo(pt1), f.geom.r); 3788 mesh.position.set((pt0.x + pt1.x) / 2, (pt0.y + pt1.y) / 2, (pt0.z + pt1.z) / 2); 3789 mesh.quaternion.setFromUnitVectors(axis, sub.subVectors(pt1, pt0).normalize()); 3790 group.add(mesh); 3791 3792 if (jointGeom && i < l - 1) { 3793 mesh = new THREE.Mesh(jointGeom, materials.mtl(f.mtl)); 3794 mesh.scale.setScalar(f.geom.r); 3795 mesh.position.copy(pt1); 3796 group.add(mesh); 3797 } 3798 3799 pt0.copy(pt1); 3800 } 3801 return group; 3802 }; 3803 } 3804 else if (objType == "Box") { 3805 // In this method, box corners are exposed near joint when both azimuth and slope of 3806 // the segments of both sides are different. Also, some unnecessary faces are created. 3807 var faces = [], vi; 3808 vi = [[0, 5, 4], [4, 5, 1], // left turn - top, side, bottom 3809 [3, 0, 7], [7, 0, 4], 3810 [6, 3, 2], [2, 3, 7], 3811 [4, 1, 0], [0, 1, 5], // right turn - top, side, bottom 3812 [1, 2, 5], [5, 2, 6], 3813 [2, 7, 6], [6, 7, 3]]; 3814 3815 for (var j = 0; j < 12; j++) { 3816 faces.push(new THREE.Face3(vi[j][0], vi[j][1], vi[j][2])); 3817 } 3818 3819 return function (f, points) { 3820 var geometry = new THREE.Geometry(); 3821 3822 var geom, dist, rx, rz, wh4, vb4, vf4; 3823 var pt0 = new THREE.Vector3(), pt1 = new THREE.Vector3(), sub = new THREE.Vector3(), 3824 pt = new THREE.Vector3(), ptM = new THREE.Vector3(), scale1 = new THREE.Vector3(1, 1, 1), 3825 matrix = new THREE.Matrix4(), quat = new THREE.Quaternion(); 3826 3827 pt0.fromArray(points[0]); 3828 for (var i = 1, l = points.length; i < l; i++) { 3829 pt1.fromArray(points[i]); 3830 dist = pt0.distanceTo(pt1); 3831 sub.subVectors(pt1, pt0); 3832 rx = Math.atan2(sub.z, Math.sqrt(sub.x * sub.x + sub.y * sub.y)); 3833 rz = Math.atan2(sub.y, sub.x) - Math.PI / 2; 3834 ptM.set((pt0.x + pt1.x) / 2, (pt0.y + pt1.y) / 2, (pt0.z + pt1.z) / 2); // midpoint 3835 quat.setFromEuler(new THREE.Euler(rx, 0, rz, "ZXY")); 3836 matrix.compose(ptM, quat, scale1); 3837 3838 // place a box to the segment 3839 geom = new THREE.BoxGeometry(f.geom.w, dist, f.geom.h);
3840 geom.applyMatrix(matrix); 3841 geometry.merge(geom); 3842 3843 // joint 3844 // 4 vertices of backward side of current segment 3845 wh4 = [[-f.geom.w / 2, f.geom.h / 2], 3846 [f.geom.w / 2, f.geom.h / 2], 3847 [f.geom.w / 2, -f.geom.h / 2], 3848 [-f.geom.w / 2, -f.geom.h / 2]]; 3849 vb4 = []; 3850 for (j = 0; j < 4; j++) { 3851 pt.set(wh4[j][0], -dist / 2, wh4[j][1]); 3852 pt.applyMatrix4(matrix); 3853 vb4.push(pt.clone()); 3854 } 3855 3856 if (vf4) { 3857 geom = new THREE.Geometry(); 3858 geom.vertices = vf4.concat(vb4); 3859 geom.faces = faces; 3860 geometry.merge(geom); 3861 } 3862 3863 // 4 vertices of forward side 3864 vf4 = []; 3865 for (j = 0; j < 4; j++) { 3866 pt.set(wh4[j][0], dist / 2, wh4[j][1]); 3867 pt.applyMatrix4(matrix); 3868 vf4.push(new THREE.Vector3(pt.x, pt.y, pt.z)); 3869 } 3870 3871 pt0.copy(pt1); 3872 } 3873 3874 geometry.faceVertexUvs = [[]]; 3875 geometry.mergeVertices(); 3876 geometry.computeFaceNormals(); 3877 return new THREE.Mesh(geometry, materials.mtl(f.mtl)); 3878 }; 3879 } 3880 else if (objType == "Wall") { 3881 return function (f, vertices) { 3882 var bzFunc = function (x, y) { return f.geom.bh; }; 3883 return new THREE.Mesh(Q3D.Utils.createWallGeometry(vertices, bzFunc), 3884 materials.mtl(f.mtl)); 3885 }; 3886 } 3887 } 3888 3889 buildLabels(features) { 3890 // Line layer doesn't support label 3891 // super.buildLabels(features); 3892 } 3893 3894 // prepare for growing line animation 3895 prepareAnimation(sequential) { 3896 3897 if (this.origMtls !== undefined) return; 3898 3899 function computeLineDistances(obj) { 3900 if (!obj.material.isLineDashedMaterial) return; 3901 3902 obj.computeLineDistances(); 3903 3904 var dists = obj.geometry.attributes.lineDistance.array; 3905 obj.lineLength = dists[dists.length - 1]; 3906 3907 for (var i = 0; i < dists.length; i++) { 3908 dists[i] /= obj.lineLength; 3909 } 3910 } 3911 3912 this.origMtls = new Q3DMaterials(); 3913 this.origMtls.array = this.materials.array; 3914 3915 this.materials.array = []; 3916 3917 if (sequential) { 3918 var f, m, mtl, j; 3919 for (var i = 0; i < this.features.length; i++) { 3920 f = this.features[i]; 3921 m = f.objs[0].material; 3922 3923 if (m.isMeshLineMaterial) { 3924 mtl = new MeshLineMaterial(); 3925 mtl.color = m.color; 3926 mtl.opacity = m.opacity; 3927 mtl.lineWidth = m.lineWidth; 3928 mtl.dashArray = 2; 3929 mtl.transparent = true; 3930 } 3931 else { 3932 if (m.isLineDashedMaterial) { 3933 mtl = m.clone(); 3934 } 3935 else { 3936 mtl = new THREE.LineDashedMaterial({color: m.color, opacity: m.opacity}); 3937 } 3938 mtl.gapSize = 1; 3939 } 3940 3941 for (j = 0; j < f.objs.length; j++) { 3942 f.objs[j].material = mtl; 3943 computeLineDistances(f.objs[j]); 3944 } 3945 this.materials.add(mtl); 3946 } 3947 } 3948 else { 3949 var mtl, mtls = this.origMtls.array; 3950 3951 for (var i = 0; i < mtls.length; i++) { 3952 mtl = mtls[i].mtl; 3953 3954 if (mtl.isLineDashedMaterial) { 3955 mtl.gapSize = 1; 3956 } 3957 else if (mtl.isMeshLineMaterial) { 3958 mtl.dashArray = 2; 3959 mtl.transparent = true; 3960 } 3961 else if (mtl.isLineBasicMaterial) { 3962 mtl = new THREE.LineDashedMaterial({color: mtl.color, opacity: mtl.opacity}); 3963 } 3964 3965 this.materials.add(mtl); 3966 } 3967 3968 var _this = this; 3969 this.objectGroup.traverse(function (obj) { 3970 3971 if (obj.userData.mtl !== undefined) { 3972 obj.material = _this.materials.mtl(obj.userData.mtl); 3973 computeLineDistances(obj); 3974 } 3975 3976 }); 3977 } 3978 } 3979 3980 // length: number [0 - 1] 3981 setLineLength(length, featureIdx) { 3982 3983 if (this.origMtls === undefined) return; 3984 3985 var mtl; 3986 if (featureIdx === undefined) { 3987 var mtls = this.materials.array; 3988 for (var i = 0; i < mtls.length; i++) { 3989 mtl = mtls[i].mtl; 3990 if (mtl.isLineDashedMaterial) { 3991 mtl.dashSize = length; 3992 } 3993 else if (mtl.isMeshLineMaterial) { 3994 mtl.uniforms.dashOffset.value = -length; 3995 } 3996 } 3997 } 3998 else { 3999 mtl = this.features[featureIdx].objs[0].material; 4000 if (mtl.isLineDashedMaterial) { 4001 mtl.dashSize = length; 4002 } 4003 else if (mtl.isMeshLineMaterial) { 4004 mtl.uniforms.dashOffset.value = -length; 4005 } 4006 } 4007 } 4008 4009} 4010 4011 4012class Q3DPolygonLayer extends Q3DVectorLayer { 4013 4014 constructor() { 4015 super(); 4016 4017 this.type = Q3D.LayerType.Polygon; 4018 4019 // for overlay 4020 this.borderVisible = true; 4021 this.sideVisible = true; 4022 } 4023 4024 build(features, startIndex) { 4025 4026 if (this.properties.objType !== this._lastObjType) this._createObject = null; 4027 4028 var createObject = this._createObject || this.createObjFunc(this.properties.objType); 4029 4030 var f, obj; 4031 for (var fidx = 0; fidx < features.length; fidx++) { 4032 f = features[fidx]; 4033 obj = createObject(f); 4034 obj.userData.properties = f.prop; 4035 4036 this.addFeature(fidx + startIndex, f, [obj]); 4037 } 4038 4039 this._lastObjType = this.properties.objType; 4040 this._createObject = createObject; 4041 } 4042 4043 createObjFunc(objType) { 4044 4045 var materials = this.materials; 4046 4047 if (objType == "Polygon") { 4048 return function (f) { 4049 var geom = new THREE.BufferGeometry(); 4050 geom.setAttribute("position", new THREE.Float32BufferAttribute(f.geom.triangles.v, 3)); 4051 geom.setIndex(f.geom.triangles.f); 4052 geom = new THREE.Geometry().fromBufferGeometry(geom); // Flat shading doesn't work with combination of 4053 // BufferGeometry and Lambert/Toon material. 4054 return new THREE.Mesh(geom, materials.mtl(f.mtl)); 4055 }; 4056 } 4057 else if (objType == "Extruded") { 4058 var createSubObject = function (f, polygon, z) { 4059 var i, l, j, m; 4060 4061 var shape = new THREE.Shape(Q3D.Utils.arrayToVec2Array(polygon[0])); 4062 for (i = 1, l = polygon.length; i < l; i++) { 4063 shape.holes.push(new THREE.Path(Q3D.Utils.arrayToVec2Array(polygon[i]))); 4064 } 4065 4066 // extruded geometry 4067 var geom = new THREE.ExtrudeBufferGeometry(shape, {bevelEnabled: false, depth: f.geom.h}); 4068 var mesh = new THREE.Mesh(geom, materials.mtl(f.mtl.face)); 4069 mesh.position.z = z; 4070 4071 if (f.mtl.edge !== undefined) { 4072 // edge 4073 var edge, bnd, v, 4074 h = f.geom.h, 4075 mtl = materials.mtl(f.mtl.edge); 4076 4077 for (i = 0, l = polygon.length; i < l; i++) { 4078 bnd = polygon[i]; 4079 4080 v = []; 4081 for (j = 0, m = bnd.length; j < m; j++) { 4082 v.push(bnd[j][0], bnd[j][1], 0); 4083 } 4084 4085 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(v, 3)); 4086 4087 edge = new THREE.Line(geom, mtl); 4088 mesh.add(edge); 4089 4090 edge = new THREE.Line(geom, mtl); 4091 edge.position.z = h; 4092 mesh.add(edge); 4093 4094 // vertical lines 4095 for (j = 0, m = bnd.length - 1; j < m; j++) { 4096 v = [bnd[j][0], bnd[j][1], 0, 4097 bnd[j][0], bnd[j][1], h]; 4098 4099 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(v, 3)); 4100 4101 edge = new THREE.Line(geom, mtl); 4102 mesh.add(edge); 4103 } 4104 } 4105 } 4106 return mesh; 4107 }; 4108 4109 var polygons, centroids; 4110 4111 return function (f) { 4112 polygons = f.geom.polygons; 4113 centroids = f.geom.centroids; 4114 4115 if (polygons.length == 1) return createSubObject(f, polygons[0], centroids[0][2]); 4116 4117 var group = new THREE.Group(); 4118 for (var i = 0, l = polygons.length; i < l; i++) { 4119 group.add(createSubObject(f, polygons[i], centroids[i][2])); 4120 } 4121 return group; 4122 }; 4123 } 4124 else if (objType == "Overlay") { 4125 4126 var _this = this; 4127 4128 return function (f) { 4129 4130 var geom = new THREE.BufferGeometry(); 4131 geom.setIndex(f.geom.triangles.f); 4132 geom.setAttribute("position", new THREE.Float32BufferAttribute(f.geom.triangles.v, 3)); 4133 geom.computeVertexNormals(); 4134 4135 var mesh = new THREE.Mesh(geom, materials.mtl(f.mtl.face)); 4136 4137 var rotation = _this.sceneData.baseExtent.rotation; 4138 if (rotation) { 4139 // rotate around center of base extent 4140 mesh.position.copy(_this.sceneData.pivot).negate(); 4141 mesh.position.applyAxisAngle(Q3D.uv.k, rotation * Q3D.deg2rad); 4142 mesh.position.add(_this.sceneData.pivot); 4143 mesh.rotateOnAxis(Q3D.uv.k, rotation * Q3D.deg2rad); 4144 } 4145 4146 // borders 4147 if (f.geom.brdr !== undefined) { 4148 var bnds, i, l, j, m; 4149 for (i = 0, l = f.geom.brdr.length; i < l; i++) { 4150 bnds = f.geom.brdr[i]; 4151 for (j = 0, m = bnds.length; j < m; j++) { 4152 geom = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(bnds[j], 3)); 4153 4154 mesh.add(new THREE.Line(geom, materials.mtl(f.mtl.brdr))); 4155 } 4156 } 4157 } 4158 return mesh; 4159 }; 4160 } 4161 } 4162 4163 buildLabels(features) { 4164 super.buildLabels(features, function (f) { return f.geom.centroids; }); 4165 } 4166 4167 setBorderVisible(visible) { 4168 if (this.properties.objType != "Overlay") return; 4169
4170 this.objectGroup.children.forEach(function (parent) { 4171 for (var i = 0, l = parent.children.length; i < l; i++) { 4172 var obj = parent.children[i]; 4173 if (obj instanceof THREE.Line) obj.visible = visible; 4174 } 4175 }); 4176 this.borderVisible = visible; 4177 } 4178 4179 setSideVisible(visible) { 4180 if (this.properties.objType != "Overlay") return; 4181 4182 this.objectGroup.children.forEach(function (parent) { 4183 for (var i = 0, l = parent.children.length; i < l; i++) { 4184 var obj = parent.children[i]; 4185 if (obj instanceof THREE.Mesh) obj.visible = visible; 4186 } 4187 }); 4188 this.sideVisible = visible; 4189 } 4190 4191} 4192 4193 4194class Q3DModel { 4195 4196 constructor() { 4197 this.loaded = false; 4198 } 4199 4200 // callback is called when model has been completely loaded 4201 load(url, callback) { 4202 var _this = this; 4203 Q3D.application.loadModelFile(url, function (model) { 4204 _this.model = model; 4205 _this._loadCompleted(callback); 4206 }); 4207 } 4208 4209 loadData(data, ext, resourcePath, callback) { 4210 var _this = this; 4211 Q3D.application.loadModelData(data, ext, resourcePath, function (model) { 4212 _this.model = model; 4213 _this._loadCompleted(callback); 4214 }); 4215 } 4216 4217 loadJSONObject(jsonObject, callback) { 4218 if (jsonObject.url !== undefined) { 4219 this.load(jsonObject.url, callback); 4220 } 4221 else { 4222 var b = atob(jsonObject.base64), 4223 len = b.length, 4224 bytes = new Uint8Array(len); 4225 4226 for (var i = 0; i < len; i++) { 4227 bytes[i] = b.charCodeAt(i); 4228 } 4229 4230 this.loadData(bytes.buffer, jsonObject.ext, jsonObject.resourcePath, callback); 4231 } 4232 } 4233 4234 _loadCompleted(anotherCallback) { 4235 this.loaded = true; 4236 4237 if (this._callbacks !== undefined) { 4238 for (var i = 0; i < this._callbacks.length; i++) { 4239 this._callbacks[i](this.model); 4240 } 4241 this._callbacks = []; 4242 } 4243 4244 if (anotherCallback) anotherCallback(this.model); 4245 } 4246 4247 callbackOnLoad(callback) { 4248 if (this.loaded) return callback(this.model); 4249 4250 if (this._callbacks === undefined) this._callbacks = []; 4251 this._callbacks.push(callback); 4252 } 4253 4254} 4255 4256 4257class Q3DModels extends THREE.EventDispatcher { 4258 4259 constructor() { 4260 super(); 4261 4262 this.models = []; 4263 this.cache = {}; 4264 } 4265 4266 loadJSONObject(jsonObject) { 4267 var _this = this; 4268 var callback = function (model) { 4269 _this.dispatchEvent({type: "modelLoaded", model: model}); 4270 }; 4271 4272 var model, url; 4273 for (var i = 0, l = jsonObject.length; i < l; i++) { 4274 4275 url = jsonObject[i].url; 4276 if (url !== undefined && this.cache[url] !== undefined) { 4277 model = this.cache[url]; 4278 } 4279 else { 4280 model = new Q3DModel(); 4281 model.loadJSONObject(jsonObject[i], callback); 4282 4283 if (url !== undefined) this.cache[url] = model; 4284 } 4285 this.models.push(model); 4286 } 4287 } 4288 4289 get(index) { 4290 return this.models[index]; 4291 } 4292 4293 clear() { 4294 this.models = []; 4295 } 4296 4297} 4298 4299Q3D.Group = Q3DGroup; 4300Q3D.Scene = Q3DScene; 4301Q3D.Material = Q3DMaterial; 4302Q3D.Materials = Q3DMaterials; 4303Q3D.DEMBlock = Q3DDEMBlock; 4304Q3D.ClippedDEMBlock = Q3DClippedDEMBlock; 4305Q3D.MapLayer = Q3DMapLayer; 4306Q3D.DEMLayer = Q3DDEMLayer; 4307Q3D.VectorLayer = Q3DVectorLayer; 4308Q3D.PointLayer = Q3DPointLayer; 4309Q3D.LineLayer = Q3DLineLayer; 4310Q3D.PolygonLayer = Q3DPolygonLayer; 4311Q3D.Model = Q3DModel; 4312Q3D.Models = Q3DModels; 4313 4314 4315// Q3D.Utils - Utilities 4316Q3D.Utils = {}; 4317 4318// Put a stick to given position (for debug) 4319Q3D.Utils.putStick = function (x, y, zFunc, h) { 4320 if (Q3D.Utils._stick_mat === undefined) Q3D.Utils._stick_mat = new THREE.LineBasicMaterial({color: 0xff0000}); 4321 if (h === undefined) h = 0.2; 4322 if (zFunc === undefined) { 4323 zFunc = function (x, y) { return Q3D.application.scene.mapLayers[0].getZ(x, y); }; 4324 } 4325 var z = zFunc(x, y); 4326 var geom = new THREE.Geometry(); 4327 geom.vertices.push(new THREE.Vector3(x, y, z + h), new THREE.Vector3(x, y, z)); 4328 var stick = new THREE.Line(geom, Q3D.Utils._stick_mat); 4329 Q3D.application.scene.add(stick); 4330}; 4331 4332// convert latitude and longitude in degrees to the following format 4333// Ndd°mmâ²ss.ssâ³, Eddd°mmâ²ss.ssâ³ 4334Q3D.Utils.convertToDMS = function (lat, lon) { 4335 function toDMS(degrees) { 4336 var deg = Math.floor(degrees), 4337 m = (degrees - deg) * 60, 4338 min = Math.floor(m), 4339 sec = (m - min) * 60; 4340 return deg + "°" + ("0" + min).slice(-2) + "â²" + ((sec < 10) ? "0" : "") + sec.toFixed(2) + "â³"; 4341 } 4342 4343 return ((lat < 0) ? "S" : "N") + toDMS(Math.abs(lat)) + ", " + 4344 ((lon < 0) ? "W" : "E") + toDMS(Math.abs(lon)); 4345}; 4346 4347Q3D.Utils.createWallGeometry = function (vert, bzFunc, buffer_geom) { 4348 var geom = new THREE.Geometry(); 4349 for (var i = 0, l = vert.length; i < l; i += 3) { 4350 geom.vertices.push( 4351 new THREE.Vector3(vert[i], vert[i + 1], vert[i + 2]), 4352 new THREE.Vector3(vert[i], vert[i + 1], bzFunc(vert[i], vert[i + 1])) 4353 ); 4354 } 4355 4356 for (var i = 1, i2 = 1, l = vert.length / 3; i < l; i++, i2 += 2) { 4357 geom.faces.push( 4358 new THREE.Face3(i2 - 1, i2, i2 + 1), 4359 new THREE.Face3(i2 + 1, i2, i2 + 2) 4360 ); 4361 } 4362 4363 geom.computeFaceNormals(); 4364 4365 if (buffer_geom) { 4366 return new THREE.BufferGeometry().fromGeometry(geom); 4367 } 4368 return geom; 4369}; 4370 4371Q3D.Utils.arrayToVec2Array = function (points) { 4372 var pt, pts = []; 4373 for (var i = 0, l = points.length; i < l; i++) { 4374 pt = points[i]; 4375 pts.push(new THREE.Vector2(pt[0], pt[1])); 4376 } 4377 return pts; 4378}; 4379 4380Q3D.Utils.flatArrayToVec2Array = function (vertices, itemSize) { 4381 itemSize = itemSize || 2; 4382 var pts = []; 4383 for (var i = 0, l = vertices.length; i < l; i += itemSize) {
4384 pts.push(new THREE.Vector2(vertices[i], vertices[i + 1])); 4385 } 4386 return pts; 4387}; 4388 4389Q3D.Utils.setGeometryUVs = function (geom, base_width, base_height) { 4390 var face, v, uvs = []; 4391 for (var i = 0, l = geom.vertices.length; i < l; i++) { 4392 v = geom.vertices[i]; 4393 uvs.push(new THREE.Vector2(v.x / base_width + 0.5, v.y / base_height + 0.5)); 4394 } 4395 4396 geom.faceVertexUvs[0] = []; 4397 for (var i = 0, l = geom.faces.length; i < l; i++) { 4398 face = geom.faces[i]; 4399 geom.faceVertexUvs[0].push([uvs[face.a], uvs[face.b], uvs[face.c]]); 4400 } 4401}; 4402 4403 4404// Q3D.Tweens 4405Q3D.Tweens = {}; 4406 4407Q3D.Tweens.cameraMotion = { 4408 4409 type: Q3D.KeyframeType.CameraMotion, 4410 4411 curveFactor: 0, 4412 4413 init: function (group) { 4414 4415 var app = Q3D.application, 4416 zScale = app.scene.userData.zScale, 4417 keyframes = group.keyframes, 4418 prop_list = []; 4419 4420 var c = this.curveFactor, p, p0, phi, theta, dist, dist_list = []; 4421 var vec3 = new THREE.Vector3(), 4422 o = app.scene.userData.origin; 4423 4424 for (var i = 0; i < keyframes.length; i++) { 4425 p = keyframes[i].camera; 4426 vec3.set(p.x - p.fx, p.y - p.fy, (p.z - p.fz) * zScale); 4427 dist = vec3.length(); 4428 theta = Math.acos(vec3.z / dist); 4429 phi = Math.atan2(vec3.y, vec3.x); 4430 p.phi = phi; 4431 prop_list.push({p: i, fx: p.fx - o.x, fy: p.fy - o.y, fz: (p.fz - o.z) * zScale, d: dist, theta: theta}); // map to 3D world 4432 4433 if (i > 0) { 4434 dist_list.push(Math.sqrt((p.x - p0.x) * (p.x - p0.x) + (p.y - p0.y) * (p.y - p0.y))); 4435 } 4436 p0 = p; 4437 } 4438 group.prop_list = prop_list; 4439 4440 var phi0, phi1, dz; 4441 group.onUpdate = function (obj, elapsed, is_first) { 4442 4443 p = obj.p - group.currentIndex; 4444 phi0 = keyframes[group.currentIndex].camera.phi; 4445 phi1 = (is_first) ? phi0 : keyframes[group.currentIndex + 1].camera.phi; 4446 4447 if (Math.abs(phi1 - phi0) > Math.PI) { // take the shortest orbiting path 4448 phi1 += Math.PI * ((phi1 > phi0) ? -2 : 2); 4449 } 4450 4451 phi = phi0 * (1 - p) + phi1 * p; 4452 4453 vec3.set(Math.cos(phi) * Math.sin(obj.theta), 4454 Math.sin(phi) * Math.sin(obj.theta), 4455 Math.cos(obj.theta)).setLength(obj.d); 4456 4457 dz = (c) ? (1 - Math.pow(2 * p - 1, 2)) * dist_list[group.currentIndex] * c : 0; 4458 4459 app.camera.position.set(obj.fx + vec3.x, obj.fy + vec3.y, obj.fz + vec3.z + dz); 4460 app.camera.lookAt(obj.fx, obj.fy, obj.fz); 4461 app.controls.target.set(obj.fx, obj.fy, obj.fz); 4462 }; 4463 4464 // initial position 4465 group.onUpdate(group.prop_list[0], 1, true); 4466 } 4467 4468}; 4469 4470Q3D.Tweens.opacity = { 4471 4472 type: Q3D.KeyframeType.Opacity, 4473 4474 init: function (group, layer) { 4475 4476 var keyframes = group.keyframes; 4477 4478 for (var i = 0; i < keyframes.length; i++) { 4479 group.prop_list.push({opacity: keyframes[i].opacity}); 4480 } 4481 4482 group.onUpdate = function (obj, elapsed) { 4483 layer.opacity = obj.opacity; 4484 }; 4485 4486 // initial opacity 4487 group.onUpdate(group.prop_list[0]); 4488 } 4489 4490}; 4491 4492Q3D.Tweens.texture = { 4493 4494 type: Q3D.KeyframeType.Texture, 4495 4496 init: function (group, layer) { 4497 4498 var keyframes = group.keyframes; 4499 4500 var idx_from, from, to, effect; 4501 4502 group.onStart = function () { 4503 idx_from = group.currentIndex; 4504 effect = keyframes[idx_from].effect; 4505 from = keyframes[idx_from].mtlIndex; 4506 to = keyframes[idx_from + 1].mtlIndex; 4507 4508 layer.prepareTexAnimation(from, to); 4509 layer.setTextureAt(null, effect); 4510 }; 4511 4512 group.onUpdate = function (obj, elapsed) { 4513 layer.setTextureAt(obj.p - group.currentIndex, effect); 4514 }; 4515 4516 for (var i = 0; i < keyframes.length; i++) { 4517 group.prop_list.push({p: i}); 4518 } 4519 } 4520}; 4521 4522Q3D.Tweens.lineGrowing = { 4523 4524 type: Q3D.KeyframeType.GrowingLine, 4525 4526 init: function (group, layer) { 4527 if (group._keyframes === undefined) group._keyframes = group.keyframes; 4528 4529 var effectItem = group._keyframes[0]; 4530 if (effectItem.sequential) { 4531 group.keyframes = []; 4532 4533 var item; 4534 for (var i = 0; i < layer.features.length; i++) { 4535 item = layer.features[i].anim; 4536 item.easing = effectItem.easing; 4537 group.keyframes.push(item); 4538 group.prop_list.push({p: i}); 4539 } 4540 group.keyframes.push({}); 4541 group.prop_list.push({p: i}); 4542 4543 group.onUpdate = function (obj, elapsed) { 4544 layer.setLineLength(obj.p - group.currentIndex, group.currentIndex); 4545 }; 4546 } 4547 else { 4548 group.keyframes = [effectItem, {}]; 4549 group.prop_list = [{p: 0}, {p: 1}]; 4550 4551 group.onUpdate = function (obj, elapsed) { 4552 layer.setLineLength(obj.p); 4553 }; 4554 } 4555 4556 layer.prepareAnimation(effectItem.sequential); 4557 layer.setLineLength(0); 4558 } 4559 4560}; 4561 4562 4563// https://stackoverflow.com/a/7838871 4564CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { 4565 if (w < 2 * r) r = w / 2; 4566 if (h < 2 * r) r = h / 2; 4567 this.beginPath(); 4568 this.moveTo(x + r, y); 4569 this.arcTo(x + w, y, x + w, y + h, r); 4570 this.arcTo(x + w, y + h, x, y + h, r); 4571 this.arcTo(x, y + h, x, y, r); 4572 this.arcTo(x, y, x + w, y, r); 4573 this.closePath(); 4574 return this; 4575};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.