· 8 years ago · Nov 29, 2017, 02:48 AM
1/*eslint-disable */
2"use strict";
3
4// Qgis2threejs.js
5// (C) 2014 Minoru Akagi | MIT License
6// https://github.com/minorua/Qgis2threejs
7
8var Q3D = {VERSION: "1.4.2"};
9Q3D.Options = {
10 bgcolor: null,
11 light: {
12 directional: {
13 azimuth: 220, // note: default light azimuth of gdaldem hillshade is 315.
14 altitude: 45 // altitude angle
15 }
16 },
17 side: {color: 0xc7ac92, bottomZ: -1.5},
18 frame: {color: 0, bottomZ: -1.5},
19 label: {visible: true, connectorColor: 0xc0c0d0, autoSize: false, minFontSize: 10},
20 qmarker: {r: 0.25, c: 0xffff00, o: 0.8},
21 debugMode: false,
22 exportMode: false,
23 jsonLoader: "JSONLoader" // JSONLoader or ObjectLoader
24};
25
26Q3D.LayerType = {DEM: "dem", Point: "point", Line: "line", Polygon: "polygon"};
27Q3D.MaterialType = {MeshLambert: 0, MeshPhong: 1, LineBasic: 2, Sprite: 3, Unknown: -1};
28Q3D.uv = {i: new THREE.Vector3(1, 0, 0), j: new THREE.Vector3(0, 1, 0), k: new THREE.Vector3(0, 0, 1)};
29
30Q3D.ua = window.navigator.userAgent.toLowerCase();
31Q3D.isIE = (Q3D.ua.indexOf("msie") != -1 || Q3D.ua.indexOf("trident") != -1);
32Q3D.isTouchDevice = ("ontouchstart" in window);
33
34Q3D.$ = function (elementId) {
35 return document.getElementById(elementId);
36};
37
38/*
39Q3D.Group -> THREE.Scene -> THREE.Object3D
40*/
41Q3D.Group = function () {
42 THREE.Group.call(this);
43};
44
45Q3D.Group.prototype = Object.create(THREE.Group.prototype);
46Q3D.Group.prototype.constructor = Q3D.Group;
47
48Q3D.Group.prototype.add = function (object) {
49 THREE.Group.prototype.add.call(this, object);
50 object.updateMatrixWorld();
51};
52
53Q3D.Group.prototype.clear = function () {
54 for (var i = this.children.length - 1 ; i >= 0; i--) {
55 this.remove(this.children[i]);
56 }
57};
58
59
60/*
61Q3D.Scene -> THREE.Scene -> THREE.Object3D
62
63.mapLayers: an object that holds map layers contained in this scene. the key is layerId.
64 use .loadJSONObject() to add a map layer to this scene.
65.userData: an object that holds metadata (crs, proj, baseExtent, rotation, width, zExaggeration, zShift, wgs84Center)
66 properties of the scene object in JSON data?
67
68.add(object):
69.getObjectByName(layerId): returns the layer object specified by the layer id.
70
71--
72custom function
73.loadJSONObject(json_obj):
74.toMapCoordinates(x, y, z): converts world coordinates to map coordinates
75._rotatePoint(point, degrees, origin):
76*/
77Q3D.Scene = function () {
78 THREE.Scene.call(this);
79 this.autoUpdate = false;
80
81 // scene is z-up
82 this.rotateX(-Math.PI / 2);
83 this.updateMatrixWorld();
84
85 this.mapLayers = {};
86
87 this.lightGroup = new Q3D.Group();
88 this.add(this.lightGroup);
89
90 this.labelConnectorGroup = new Q3D.Group();
91 this.add(this.labelConnectorGroup);
92
93 this.labelRootElement = null;
94};
95
96Q3D.Scene.prototype = Object.create(THREE.Scene.prototype);
97Q3D.Scene.prototype.constructor = Q3D.Scene;
98
99Q3D.Scene.prototype.add = function (object) {
100 THREE.Scene.prototype.add.call(this, object);
101 object.updateMatrixWorld();
102};
103
104Q3D.Scene.prototype.loadJSONObject = function (jsonObject) {
105 var _this = this;
106 if (jsonObject.type == "scene") {
107 // set properties
108 if (jsonObject.properties !== undefined) {
109 this.userData = jsonObject.properties;
110
111 var w = (this.userData.baseExtent[2] - this.userData.baseExtent[0]),
112 h = (this.userData.baseExtent[3] - this.userData.baseExtent[1]);
113
114 this.userData.scale = this.userData.width / w;
115 this.userData.zScale = this.userData.scale * this.userData.zExaggeration;
116
117 this.userData.origin = {x: this.userData.baseExtent[0] + w / 2,
118 y: this.userData.baseExtent[1] + h / 2,
119 z: -this.userData.zShift};
120 }
121
122 // load lights
123 if (jsonObject.lights !== undefined) {
124 // remove all existing lights
125 this.lightGroup.clear();
126
127 // TODO: load light settings and build lights
128 }
129
130 // create default lights if this scene has no lights
131 if (this.lightGroup.children.length == 0) this.buildDefaultLights();
132
133 // load layers
134 if (jsonObject.layers !== undefined) {
135 jsonObject.layers.forEach(function (layer) {
136 _this.loadJSONObject(layer);
137 });
138 }
139 }
140 else if (jsonObject.type == "layer") {
141 var layer = this.mapLayers[jsonObject.id];
142 if (layer === undefined) {
143 // console.assert(jsonObject.properties !== undefined);
144
145 // create a layer
146 var type = jsonObject.properties.type
147 if (type == "dem") layer = new Q3D.DEMLayer();
148 else if (type == "point") layer = new Q3D.PointLayer();
149 else if (type == "line") layer = new Q3D.LineLayer();
150 else if (type == "polygon") layer = new Q3D.PolygonLayer();
151 else {
152 // console.error("unknown layer type:" + type);
153 return;
154 }
155 layer.id = jsonObject.id;
156 layer.addEventListener("renderRequest", function () {
157 _this.requestRender();
158 });
159
160 this.mapLayers[jsonObject.id] = layer;
161 this.add(layer.objectGroup);
162 }
163
164 layer.loadJSONObject(jsonObject, this);
165
166 this.requestRender();
167
168 /* TODO: load models
169 // load models
170 if (project.models.length > 0) {
171 project.models.forEach(function (model, index) {
172 if (model.type == "COLLADA") {
173 app.modelBuilders[index] = new Q3D.ModelBuilder.COLLADA(app.project, model);
174 }
175 else if (Q3D.Options.jsonLoader == "ObjectLoader") {
176 app.modelBuilders[index] = new Q3D.ModelBuilder.JSONObject(app.project, model);
177 }
178 else {
179 app.modelBuilders[index] = new Q3D.ModelBuilder.JSON(app.project, model);
180 }
181 });
182 }
183 */
184 }
185 else if (jsonObject.type == "block") {
186 var layer = this.mapLayers[jsonObject.layer];
187 if (layer === undefined) {
188 // console.error("layer not exists:" + jsonObject.layer);
189 return;
190 }
191 layer.loadJSONObject(jsonObject, this);
192
193 this.requestRender();
194 }
195};
196
197Q3D.Scene.prototype.buildDefaultLights = function () {
198 // ambient light
199 this.lightGroup.add(new THREE.AmbientLight(0x999999));
200
201 // directional lights
202 var opt = Q3D.Options.light.directional, deg2rad = Math.PI / 180;
203 var lambda = (90 - opt.azimuth) * deg2rad;
204 var phi = opt.altitude * deg2rad;
205
206 var x = Math.cos(phi) * Math.cos(lambda),
207 y = Math.cos(phi) * Math.sin(lambda),
208 z = Math.sin(phi);
209
210 var light1 = new THREE.DirectionalLight(0xffffff, 0.5);
211 light1.position.set(x, y, z);
212 this.lightGroup.add(light1);
213
214 // thin light from the opposite direction
215 var light2 = new THREE.DirectionalLight(0xffffff, 0.1);
216 light2.position.set(-x, -y, -z);
217 this.lightGroup.add(light2);
218};
219
220Q3D.Scene.prototype.requestRender = function () {
221 this.dispatchEvent({type: "renderRequest"});
222};
223
224Q3D.Scene.prototype.queryableObjects = function () {
225 var objs = [];
226 for (var id in this.mapLayers) {
227 objs = objs.concat(this.mapLayers[id].queryableObjects());
228 }
229 return objs;
230};
231
232Q3D.Scene.prototype.toMapCoordinates = function (x, y, z) {
233 if (this.userData.rotation) {
234 var pt = this._rotatePoint({x: x, y: y}, this.userData.rotation);
235 x = pt.x;
236 y = pt.y;
237 }
238 return {x: x / this.userData.scale + this.userData.origin.x,
239 y: y / this.userData.scale + this.userData.origin.y,
240 z: z / this.userData.zScale + this.userData.origin.z};
241};
242
243// Rotate a point around an origin
244Q3D.Scene.prototype._rotatePoint = function (point, degrees, origin) {
245 var theta = degrees * Math.PI / 180,
246 c = Math.cos(theta),
247 s = Math.sin(theta),
248 x = point.x,
249 y = point.y;
250
251 if (origin) {
252 x -= origin.x;
253 y -= origin.y;
254 }
255
256 // rotate counter-clockwise
257 var xd = x * c - y * s,
258 yd = x * s + y * c;
259
260 if (origin) {
261 xd += origin.x;
262 yd += origin.y;
263 }
264 return {x: xd, y: yd};
265};
266
267
268/*
269Q3D.application
270
271limitations:
272- one renderer
273- one scene
274*/
275(function () {
276 // the application
277 var app = {};
278 Q3D.application = app;
279
280 app.init = function (container) {
281 app.container = container;
282 app.running = false; // if true, animation loop is continued.
283
284 // URL parameters
285 app.urlParams = app.parseUrlParameters();
286 if ("popup" in app.urlParams) {
287 // open popup window
288 var c = window.location.href.split("?");
289 window.open(c[0] + "?" + c[1].replace(/&?popup/, ""), "popup", "width=" + app.urlParams.width + ",height=" + app.urlParams.height);
290 app.popup.show("Another window has been opened.");
291 return;
292 }
293
294 if (app.urlParams.width && app.urlParams.height) {
295 // set container size
296 container.style.width = app.urlParams.width + "px";
297 container.style.height = app.urlParams.height + "px";
298 }
299
300 if (container.clientWidth && container.clientHeight) {
301 app.width = container.clientWidth;
302 app.height = container.clientHeight;
303 app._fullWindow = false;
304 } else {
305 app.width = window.innerWidth;
306 app.height = window.innerHeight;
307 app._fullWindow = true;
308 }
309
310 // WebGLRenderer
311 var bgcolor = Q3D.Options.bgcolor;
312 app.renderer = new THREE.WebGLRenderer({alpha: true, antialias: true});
313 app.renderer.setSize(app.width, app.height);
314 app.renderer.setClearColor(bgcolor || 0, (bgcolor === null) ? 0 : 1);
315 app.container.appendChild(app.renderer.domElement);
316
317 // scene
318 app.scene = new Q3D.Scene();
319 app.scene.addEventListener("renderRequest", function (event) {
320 app.render();
321 });
322
323 // camera
324 app.buildDefaultCamera();
325
326 // restore view (camera position and its target) from URL parameters
327 var vars = app.urlParams;
328 if (vars.cx !== undefined) app.camera.position.set(parseFloat(vars.cx), parseFloat(vars.cy), parseFloat(vars.cz));
329 if (vars.ux !== undefined) app.camera.up.set(parseFloat(vars.ux), parseFloat(vars.uy), parseFloat(vars.uz));
330 if (vars.tx !== undefined) app.camera.lookAt(parseFloat(vars.tx), parseFloat(vars.ty), parseFloat(vars.tz));
331
332 // orbit controls
333 app.controls = new THREE.OrbitControls(app.camera, app.renderer.domElement);
334 var controls = app.controls;
335 controls.enableKeys = false;
336
337 var offset = new THREE.Vector3();
338 var spherical = new THREE.Spherical();
339
340 // orbit controls - custom functions
341 controls.moveForward = function (delta) {
342 offset.copy(controls.object.position).sub(controls.target);
343 var targetDistance = offset.length() * Math.tan((controls.object.fov / 2) * Math.PI / 180.0);
344 offset.y = 0;
345 offset.normalize();
346 offset.multiplyScalar(-2 * delta * targetDistance / app.renderer.domElement.clientHeight);
347
348 controls.object.position.add(offset);
349 controls.target.add(offset);
350 };
351 controls.cameraRotate = function (thetaDelta, phiDelta) {
352 offset.copy(controls.target).sub(controls.object.position);
353 spherical.setFromVector3(offset);
354
355 spherical.theta += thetaDelta;
356 spherical.phi -= phiDelta;
357
358 // restrict theta/phi to be between desired limits
359 spherical.theta = Math.max(controls.minAzimuthAngle, Math.min(controls.maxAzimuthAngle, spherical.theta));
360 spherical.phi = Math.max(controls.minPolarAngle, Math.min(controls.maxPolarAngle, spherical.phi));
361 spherical.makeSafe();
362
363 offset.setFromSpherical(spherical);
364 controls.target.copy(controls.object.position).add(offset);
365 controls.object.lookAt(controls.target);
366 };
367
368 controls.update();
369
370 /*
371 if (vars.tx !== undefined) {
372 app.controls.target.set(parseFloat(vars.tx), parseFloat(vars.ty), parseFloat(vars.tz));
373 app.controls.target0.copy(app.controls.target); // for reset
374 }
375 */
376
377 app.labelVisibility = Q3D.Options.label.visible;
378
379 // root element of labels
380 app.scene.labelRootElement = document.getElementById("labels");
381 app.scene.labelRootElement.style.display = (app.labelVisibility) ? "block" : "none";
382
383 // create a marker for queried point
384 var opt = Q3D.Options.qmarker;
385 app.queryMarker = new THREE.Mesh(new THREE.SphereGeometry(opt.r),
386 new THREE.MeshLambertMaterial({color: opt.c, opacity: opt.o, transparent: (opt.o < 1)}));
387 app.queryMarker.visible = false;
388 app.scene.add(app.queryMarker);
389
390 app.highlightMaterial = new THREE.MeshLambertMaterial({emissive: 0x999900, transparent: true, opacity: 0.5});
391 if (!Q3D.isIE) app.highlightMaterial.side = THREE.DoubleSide; // Shader compilation error occurs with double sided material on IE11
392
393 app.selectedObject = null;
394 app.highlightObject = null;
395
396 app.modelBuilders = [];
397 app._wireframeMode = false;
398
399 // TODO:
400 // wireframe mode setting
401 // if ("wireframe" in app.urlParams) app.setWireframeMode(true);
402 };
403
404 app.parseUrlParameters = function () {
405 var p, vars = {};
406 var params = window.location.search.substring(1).split('&').concat(window.location.hash.substring(1).split('&'));
407 params.forEach(function (param) {
408 p = param.split('=');
409 vars[p[0]] = p[1];
410 });
411 return vars;
412 };
413
414 app.loadJSONObject = function (jsonObject) {
415 app.scene.loadJSONObject(jsonObject);
416 };
417
418 app.loadJSONFromURL = function (url) {
419
420 var xhr = new XMLHttpRequest();
421 xhr.open("GET", url, true);
422 xhr.responseType = "json";
423 xhr.onload = function () {
424 app.loadJSONObject(this.response);
425 };
426 xhr.send(null);
427
428 /* latest three.js has THREE.FileLoader()
429 var loader = new THREE.FileLoader();
430 loader.setResponseType("json");
431 loader.load(url, app.loadJSON, // url, onLoad
432 // onProcess
433 function (xhr) {
434 console.log((xhr.loaded / xhr.total * 100) + "% loaded");
435 },
436 // onError
437 function (xhr) {
438 console.error("An error happened");
439 });
440 */
441 };
442
443 app.addEventListeners = function () {
444 window.addEventListener("keydown", app.eventListener.keydown);
445 window.addEventListener("resize", app.eventListener.resize);
446
447 app.renderer.domElement.addEventListener("mousedown", app.eventListener.mousedown);
448 app.renderer.domElement.addEventListener("mouseup", app.eventListener.mouseup);
449
450 app.controls.addEventListener("change", function (event) {
451 app.render();
452 });
453
454 var e = Q3D.$("closebtn");
455 if (e) e.addEventListener("click", app.closePopup);
456 };
457
458 app.mouseDownPoint = new THREE.Vector2();
459 app.mouseUpPoint = new THREE.Vector2();
460
461 app.eventListener = {
462
463 keydown: function (e) {
464 var controls = app.controls, keyPressed = e.which;
465 var panDelta = 3, rotateAngle = 2 * Math.PI / 180;
466 if (e.shiftKey && e.ctrlKey) {
467 switch (e.keyCode) {
468 case 38: // Shift + Ctrl + UP
469 controls.dollyOut(controls.getZoomScale());
470 break;
471 case 40: // Shift + Ctrl + DOWN
472 controls.dollyIn(controls.getZoomScale());
473 break;
474 default:
475 return;
476 }
477 } else if (e.shiftKey) {
478 switch (e.keyCode) {
479 case 37: // LEFT
480 controls.rotateLeft(rotateAngle);
481 break;
482 case 38: // UP
483 controls.rotateUp(rotateAngle);
484 break;
485 case 39: // RIGHT
486 controls.rotateLeft(-rotateAngle);
487 break;
488 case 40: // DOWN
489 controls.rotateUp(-rotateAngle);
490 break;
491 case 82: // Shift + R
492 controls.reset();
493 break;
494 case 83: // Shift + S
495 app.showPrintDialog();
496 return;
497 default:
498 return;
499 }
500 } else if (e.ctrlKey) {
501 switch (e.keyCode) {
502 case 37: // Ctrl + LEFT
503 controls.cameraRotate(rotateAngle, 0);
504 break;
505 case 38: // Ctrl + UP
506 controls.cameraRotate(0, rotateAngle);
507 break;
508 case 39: // Ctrl + RIGHT
509 controls.cameraRotate(-rotateAngle, 0);
510 break;
511 case 40: // Ctrl + DOWN
512 controls.cameraRotate(0, -rotateAngle);
513 break;
514 default:
515 return;
516 }
517 } else {
518 switch (e.keyCode) {
519 case 37: // LEFT
520 controls.panLeft(panDelta, controls.object.matrix);
521 break;
522 case 38: // UP
523 controls.moveForward(3 * panDelta) // horizontally forward
524 break;
525 case 39: // RIGHT
526 controls.panLeft(-panDelta, controls.object.matrix);
527 break;
528 case 40: // DOWN
529 controls.moveForward(-3 * panDelta);
530 break;
531 case 27: // ESC
532 app.closePopup();
533 return;
534 case 73: // I
535 app.showInfo();
536 return;
537 case 76: // L
538 app.setLabelVisibility(!app.labelVisibility);
539 return;
540 case 87: // W
541 app.setWireframeMode(!app._wireframeMode);
542 return;
543 default:
544 return;
545 }
546 }
547 app.controls.update();
548 },
549
550 mousedown: function (e) {
551 app.mouseDownPoint.set(e.clientX, e.clientY);
552 },
553
554 mouseup: function (e) {
555 app.mouseUpPoint.set(e.clientX, e.clientY);
556 if (app.mouseDownPoint.equals(app.mouseUpPoint)) app.canvasClicked(e);
557 },
558
559 resize: function () {
560 if (app._fullWindow) app.setCanvasSize(window.innerWidth, window.innerHeight);
561 app.render();
562 }
563
564 };
565
566 app.setCanvasSize = function (width, height) {
567 app.width = width;
568 app.height = height;
569 app.camera.aspect = width / height;
570 app.camera.updateProjectionMatrix();
571 app.renderer.setSize(width, height);
572 };
573
574 app.buildDefaultCamera = function () {
575 app.camera = new THREE.PerspectiveCamera(45, app.width / app.height, 0.1, 1000);
576 app.camera.position.set(0, 100, 100);
577 };
578
579 app.currentViewUrl = function () {
580 var c = app.camera.position, t = app.controls.target, u = app.camera.up;
581 var hash = "#cx=" + c.x + "&cy=" + c.y + "&cz=" + c.z;
582 if (t.x || t.y || t.z) hash += "&tx=" + t.x + "&ty=" + t.y + "&tz=" + t.z;
583 if (u.x || u.y || u.z != 1) hash += "&ux=" + u.x + "&uy=" + u.y + "&uz=" + u.z;
584 return window.location.href.split("#")[0] + hash;
585 };
586
587 // enable the controls
588 app.start = function () {
589 if (app.controls) app.controls.enabled = true;
590 };
591
592 app.pause = function () {
593 app.running = false;
594 if (app.controls) app.controls.enabled = false;
595 };
596
597 app.resume = function () {
598 if (app.controls) app.controls.enabled = true;
599 };
600
601 // animation loop
602 app.animate = function () {
603 if (app.running) requestAnimationFrame(app.animate);
604 app.render(true);
605 };
606
607 app.render = function (updateControls) {
608 if (updateControls && app.controls.update()) return; // changeEvent handler calls app.render()
609 app.renderer.render(app.scene, app.camera);
610 app.updateLabelPosition();
611 };
612
613 // update label position
614 app.updateLabelPosition = function () {
615 var rootGroup = app.scene.labelConnectorGroup;
616 if (!app.labelVisibility || rootGroup.children.length == 0) return;
617
618 var camera = app.camera,
619 camera_pos = camera.position,
620 pt = new THREE.Vector3();
621
622 // make list of [connector object, distance to camera]
623 var obj_dist = [];
624 var layerGroup, conn, pt0;
625 for (var i = 0, l = rootGroup.children.length; i < l; i++) {
626 layerGroup = rootGroup.children[i];
627 if (layerGroup.visible) {
628 for (var k = 0, m = layerGroup.children.length; k < m; k++) {
629 conn = layerGroup.children[k];
630 pt0 = conn.geometry.vertices[0];
631 pt.set(pt0.x, pt0.z, -pt0.y);
632 obj_dist.push([conn, camera_pos.distanceTo(pt)]);
633 }
634 }
635 }
636
637 if (obj_dist.length == 0) return;
638
639 // sort label objects in descending order of distances
640 obj_dist.sort(function (a, b) {
641 if (a[1] < b[1]) return 1;
642 if (a[1] > b[1]) return -1;
643 return 0;
644 });
645
646 var widthHalf = app.width / 2,
647 heightHalf = app.height / 2,
648 autosize = Q3D.Options.label.autoSize,
649 c2t = app.controls.target.clone().sub(camera_pos),
650 c2l = new THREE.Vector3(),
651 v = new THREE.Vector3();
652
653 var label, e, x, y, pt, dist, fontSize;
654 var minFontSize = Q3D.Options.label.minFontSize;
655 for (var i = 0, l = obj_dist.length; i < l; i++) {
656 label = obj_dist[i][0];
657 dist = obj_dist[i][1];
658
659 e = label.userData.elem;
660 pt0 = label.geometry.vertices[0];
661 pt.set(pt0.x, pt0.z, -pt0.y);
662 if (c2l.subVectors(pt, camera_pos).dot(c2t) > 0) {
663 // label is in front
664 // calculate label position
665 v.copy(pt).project(camera);
666 x = (v.x * widthHalf) + widthHalf;
667 y = -(v.y * heightHalf) + heightHalf;
668
669 // set label position
670 e.style.display = "block";
671 e.style.left = (x - (e.offsetWidth / 2)) + "px";
672 e.style.top = (y - (e.offsetHeight / 2)) + "px";
673 e.style.zIndex = i + 1;
674
675 // set font size
676 if (autosize) {
677 if (dist < 10) dist = 10;
678 fontSize = Math.max(Math.round(1000 / dist), minFontSize);
679 e.style.fontSize = fontSize + "px";
680 }
681 }
682 else {
683 // label is in back
684 e.style.display = "none";
685 }
686 }
687 };
688
689 app.setLabelVisibility = function (visible) {
690 app.labelVisibility = visible;
691 app.scene.labelRootElement.style.display = (visible) ? "block" : "none";
692 app.scene.labelConnectorGroup.visible = visible;
693 app.render();
694 };
695
696 app.setWireframeMode = function (wireframe) {
697 if (wireframe == app._wireframeMode) return;
698
699 for (var id in app.scene.mapLayers) {
700 app.scene.mapLayers[id].setWireframeMode(wireframe);
701 }
702
703 app._wireframeMode = wireframe;
704 };
705
706 app.intersectObjects = function (offsetX, offsetY) {
707 var vec2 = new THREE.Vector2((offsetX / app.width) * 2 - 1,
708 -(offsetY / app.height) * 2 + 1);
709 var ray = new THREE.Raycaster();
710 ray.linePrecision = 0.2;
711 ray.setFromCamera(vec2, app.camera);
712 return ray.intersectObjects(app.scene.queryableObjects());
713 };
714
715 app._offset = function (elm) {
716 var top = 0, left = 0;
717 do {
718 top += elm.offsetTop || 0; left += elm.offsetLeft || 0; elm = elm.offsetParent;
719 } while (elm);
720 return {top: top, left: left};
721 };
722
723 app.help = function () {
724 var lines = (Q3D.Controls === undefined) ? [] : Q3D.Controls.keyList;
725 if (lines.indexOf("* Keys") == -1) lines.push("* Keys");
726 lines = lines.concat([
727 "I : Show Information About Page",
728 "L : Toggle Label Visibility",
729 "W : Wireframe Mode",
730 "Shift + R : Reset View",
731 "Shift + S : Save Image"
732 ]);
733 var html = '<table>';
734 lines.forEach(function (line) {
735 if (line.trim() == "") return;
736
737 if (line[0] == "*") {
738 html += '<tr><td colspan="2" class="star">' + line.substr(1).trim() + "</td></tr>";
739 }
740 else if (line.indexOf(":") == -1) {
741 html += '<tr><td colspan="2">' + line.trim() + "</td></tr>";
742 }
743 else {
744 var p = line.split(":");
745 html += "<tr><td>" + p[0].trim() + "</td><td>" + p[1].trim() + "</td></tr>";
746 }
747 });
748 html += "</table>";
749 return html;
750 };
751
752 app.popup = {
753
754 modal: false,
755
756 // show box
757 // obj: html or element
758 show: function (obj, title, modal) {
759
760 if (modal) app.pause();
761 else if (this.modal) app.resume();
762
763 this.modal = Boolean(modal);
764
765 var content = Q3D.$("popupcontent");
766 if (obj === undefined) {
767 // show page info
768 content.style.display = "none";
769 Q3D.$("pageinfo").style.display = "block";
770 }
771 else {
772 Q3D.$("pageinfo").style.display = "none";
773 if (obj instanceof HTMLElement) {
774 content.innerHTML = "";
775 content.appendChild(obj);
776 }
777 else {
778 content.innerHTML = obj;
779 }
780 content.style.display = "block";
781 }
782 Q3D.$("popupbar").innerHTML = title || "";
783 Q3D.$("popup").style.display = "block";
784 },
785
786 hide: function () {
787 Q3D.$("popup").style.display = "none";
788 if (this.modal) app.resume();
789 }
790
791 };
792
793 app.showInfo = function () {
794 Q3D.$("urlbox").value = app.currentViewUrl();
795 Q3D.$("usage").innerHTML = app.help();
796 app.popup.show();
797 };
798
799 // TODO: obj
800 app.showQueryResult = function (point, object) {
801 var layer = app.scene.mapLayers[object.userData.layerId], r = [];
802 if (layer) {
803 // layer name
804 r.push('<table class="layer">');
805 r.push("<caption>Layer name</caption>");
806 r.push("<tr><td>" + layer.properties.name + "</td></tr>");
807 r.push("</table>");
808 }
809
810 // clicked coordinates
811 var pt = app.scene.toMapCoordinates(point.x, point.y, point.z);
812 r.push('<table class="coords">');
813 r.push("<caption>Clicked coordinates</caption>");
814 r.push("<tr><td>");
815
816 if (typeof proj4 === "undefined") r.push([pt.x.toFixed(2), pt.y.toFixed(2), pt.z.toFixed(2)].join(", "));
817 else {
818 var lonLat = proj4(app.scene.userData.proj).inverse([pt.x, pt.y]);
819 r.push(Q3D.Utils.convertToDMS(lonLat[1], lonLat[0]) + ", Elev. " + pt.z.toFixed(2));
820 }
821
822 r.push("</td></tr></table>");
823
824 if (layer.properties.propertyNames !== undefined) {
825 // attributes
826 r.push('<table class="attrs">');
827 r.push("<caption>Attributes</caption>");
828 for (var i = 0, l = layer.properties.propertyNames.length; i < l; i++) {
829 r.push("<tr><td>" + layer.properties.propertyNames[i] + "</td><td>" + object.userData.properties[i] + "</td></tr>");
830 }
831 r.push("</table>");
832 }
833 app.popup.show(r.join(""));
834 };
835
836 app.showPrintDialog = function () {
837
838 function e (tagName, parent, innerHTML) {
839 var elem = document.createElement(tagName);
840 if (parent) parent.appendChild(elem);
841 if (innerHTML) elem.innerHTML = innerHTML;
842 return elem;
843 }
844
845 var f = e("form");
846 f.className = "print";
847
848 var d1 = e("div", f, "Image Size");
849 d1.style.textDecoration = "underline";
850
851 var d2 = e("div", f),
852 l1 = e("label", d2, "Width:"),
853 width = e("input", d2);
854 d2.style.cssFloat = "left";
855 l1.htmlFor = width.id = width.name = "printwidth";
856 width.type = "text";
857 width.value = app.width;
858 e("span", d2, "px,")
859
860 var d3 = e("div", f),
861 l2 = e("label", d3, "Height:"),
862 height = e("input", d3);
863 l2.htmlFor = height.id = height.name = "printheight";
864 height.type = "text";
865 height.value = app.height;
866 e("span", d3, "px");
867
868 var d4 = e("div", f),
869 ka = e("input", d4);
870 ka.type = "checkbox";
871 ka.checked = true;
872 e("span", d4, "Keep Aspect Ratio");
873
874 var d5 = e("div", f, "Option");
875 d5.style.textDecoration = "underline";
876
877 var d6 = e("div", f),
878 bg = e("input", d6);
879 bg.type = "checkbox";
880 bg.checked = true;
881 e("span", d6, "Fill Background");
882
883 var d7 = e("div", f),
884 ok = e("span", d7, "OK"),
885 cancel = e("span", d7, "Cancel");
886 d7.className = "buttonbox";
887
888 e("input", f).type = "submit";
889
890 // event handlers
891 // width and height boxes
892 var aspect = app.width / app.height;
893
894 width.oninput = function () {
895 if (ka.checked) height.value = Math.round(width.value / aspect);
896 };
897
898 height.oninput = function () {
899 if (ka.checked) width.value = Math.round(height.value * aspect);
900 };
901
902 ok.onclick = function () {
903 app.popup.show("Rendering...");
904 window.setTimeout(function () {
905 app.saveCanvasImage(width.value, height.value, bg.checked);
906 }, 10);
907 };
908
909 cancel.onclick = app.closePopup;
910
911 // enter key pressed
912 f.onsubmit = function () {
913 ok.onclick();
914 return false;
915 }
916
917 app.popup.show(f, "Save Image", true); // modal
918 };
919
920 app.closePopup = function () {
921 app.popup.hide();
922 app.queryMarker.visible = false;
923 app.highlightFeature(null);
924 if (app._canvasImageUrl) {
925 URL.revokeObjectURL(app._canvasImageUrl);
926 app._canvasImageUrl = null;
927 }
928 };
929
930 app.highlightFeature = function (object) {
931 if (app.highlightObject) {
932 // remove highlight object from the scene
933 app.scene.remove(app.highlightObject);
934 app.selectedObject = null;
935 app.highlightObject = null;
936 }
937
938 if (object === null) return;
939
940 var layer = app.scene.mapLayers[object.userData.layerId];
941 if (layer === undefined || layer.type == Q3D.LayerType.DEM) return;
942 if (["Icon", "JSON model", "COLLADA model"].indexOf(layer.objType) != -1) return;
943
944 // create a highlight object (if layer type is Point, slightly bigger than the object)
945 // var highlightObject = new Q3D.Group();
946 var s = (layer.type == Q3D.LayerType.Point) ? 1.01 : 1;
947 // TODO: use polygonOffset?
948
949 var clone = object.clone()
950 clone.traverse(function (obj) {
951 obj.material = app.highlightMaterial;
952 });
953 if (s != 1) clone.scale.set(clone.scale.x * s, clone.scale.y * s, clone.scale.z * s);
954 // highlightObject.add(clone);
955
956 // add the highlight object to the scene
957 app.scene.add(clone);
958
959 app.selectedObject = object;
960 app.highlightObject = clone;
961 };
962
963 app.canvasClicked = function (e) {
964 var canvasOffset = app._offset(app.renderer.domElement);
965 var objs = app.intersectObjects(e.clientX - canvasOffset.left, e.clientY - canvasOffset.top);
966 var obj, pt;
967
968 for (var i = 0, l = objs.length; i < l; i++) {
969 obj = objs[i];
970
971 // query marker
972 pt = {x: obj.point.x, y: -obj.point.z, z: obj.point.y}; // obj's coordinate system is y-up
973 app.queryMarker.position.set(pt.x, pt.y, pt.z); // this is z-up
974 app.queryMarker.visible = true;
975 app.queryMarker.updateMatrixWorld();
976
977 // get layerId of clicked object
978 var layerId, object = obj.object;
979 while (object) {
980 layerId = object.userData.layerId;
981 if (layerId !== undefined) break;
982 object = object.parent;
983 }
984
985 app.highlightFeature(object);
986 app.showQueryResult(pt, object);
987
988 app.render();
989
990 if (Q3D.Options.debugMode && object instanceof THREE.Mesh) {
991 var face = obj.face,
992 geom = object.geometry;
993 if (face) {
994 if (geom instanceof THREE.Geometry) {
995 var v = object.geometry.vertices;
996 console.log(v[face.a], v[face.b], v[face.c]);
997 }
998 else {
999 console.log("Qgis2threejs: [DEBUG] THREE.BufferGeometry");
1000 }
1001 }
1002 }
1003
1004 return;
1005 }
1006 app.closePopup();
1007 };
1008
1009 app.saveCanvasImage = function (width, height, fill_background, saveImageFunc) {
1010 if (fill_background === undefined) fill_background = true;
1011
1012 // set canvas size
1013 var old_size;
1014 if (width && height) {
1015 old_size = [app.width, app.height];
1016 app.setCanvasSize(width, height);
1017 }
1018
1019 // functions
1020 var saveBlob = function (blob) {
1021 var filename = "image.png";
1022
1023 // ie
1024 if (window.navigator.msSaveBlob !== undefined) {
1025 window.navigator.msSaveBlob(blob, filename);
1026 app.popup.hide();
1027 }
1028 else {
1029 // create object url
1030 if (app._canvasImageUrl) URL.revokeObjectURL(app._canvasImageUrl);
1031 app._canvasImageUrl = URL.createObjectURL(blob);
1032
1033 // display a link to save the image
1034 var e = document.createElement("a");
1035 e.className = "download-link";
1036 e.href = app._canvasImageUrl;
1037 e.download = filename;
1038 e.innerHTML = "Save";
1039 app.popup.show("Click to save the image to a file." + e.outerHTML, "Image is ready");
1040 }
1041 };
1042
1043 var saveCanvasImage = saveImageFunc || function (canvas) {
1044 if (canvas.toBlob !== undefined) {
1045 canvas.toBlob(saveBlob);
1046 }
1047 else { // !HTMLCanvasElement.prototype.toBlob
1048 // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement.toBlob
1049 var binStr = atob(canvas.toDataURL("image/png").split(',')[1]),
1050 len = binStr.length,
1051 arr = new Uint8Array(len);
1052
1053 for (var i = 0; i < len; i++) {
1054 arr[i] = binStr.charCodeAt(i);
1055 }
1056
1057 saveBlob(new Blob([arr], {type: "image/png"}));
1058 }
1059 };
1060
1061 // TODO: save QWebView page content as image
1062 var renderLabels = function (ctx) {
1063 // context settings
1064 ctx.textAlign = "center";
1065 ctx.textBaseline = "middle";
1066
1067 // get label style from css
1068 var elem = document.createElement("div");
1069 elem.className = "print-label";
1070 document.body.appendChild(elem);
1071 var style = document.defaultView.getComputedStyle(elem, ""),
1072 color = style.color;
1073 ctx.font = style.font;
1074 document.body.removeChild(elem);
1075
1076 var widthHalf = width / 2,
1077 heightHalf = height / 2,
1078 camera = app.camera,
1079 camera_pos = camera.position,
1080 c2t = app.controls.target.clone().sub(camera_pos),
1081 c2l = new THREE.Vector3(),
1082 v = new THREE.Vector3();
1083
1084 // make a list of [label index, distance to camera]
1085 var idx_dist = [];
1086 for (var i = 0, l = app.labels.length; i < l; i++) {
1087 idx_dist.push([i, camera_pos.distanceTo(app.labels[i].pt)]);
1088 }
1089
1090 // sort label indexes in descending order of distances
1091 idx_dist.sort(function (a, b) {
1092 if (a[1] < b[1]) return 1;
1093 if (a[1] > b[1]) return -1;
1094 return 0;
1095 });
1096
1097 var label, text, x, y;
1098 for (var i = 0, l = idx_dist.length; i < l; i++) {
1099 label = app.labels[idx_dist[i][0]];
1100 text = label.e.textContent;
1101 if (c2l.subVectors(label.pt, camera_pos).dot(c2t) > 0) { // label is in front
1102 // calculate label position
1103 v.copy(label.pt).project(camera);
1104 x = (v.x * widthHalf) + widthHalf;
1105 y = -(v.y * heightHalf) + heightHalf;
1106 if (x < 0 || width <= x || y < 0 || height <= y) continue;
1107
1108 // outline effect
1109 ctx.fillStyle = "#FFF";
1110 for (var j = 0; j < 9; j++) {
1111 if (j != 4) ctx.fillText(text, x + Math.floor(j / 3) - 1, y + j % 3 - 1);
1112 }
1113
1114 ctx.fillStyle = color;
1115 ctx.fillText(text, x, y);
1116 }
1117 }
1118 };
1119
1120 var restoreCanvasSize = function () {
1121 // restore canvas size
1122 if (old_size) app.setCanvasSize(old_size[0], old_size[1]);
1123 app.render();
1124 };
1125
1126 // background option
1127 if (!fill_background) app.renderer.setClearColor(0, 0);
1128
1129 // render
1130 app.renderer.preserveDrawingBuffer = true;
1131 app.renderer.render(app.scene, app.camera);
1132
1133 // restore clear color
1134 var bgcolor = Q3D.Options.bgcolor;
1135 app.renderer.setClearColor(bgcolor || 0, (bgcolor === null) ? 0 : 1);
1136
1137 // TODO: save QWebView page content as image
1138 var render_labels = false; // TODO: (app.labelVisibility && app.labels.length > 0);
1139 if ((fill_background && bgcolor === null) || render_labels) {
1140 var canvas = document.createElement("canvas");
1141 canvas.width = width;
1142 canvas.height = height;
1143
1144 var ctx = canvas.getContext("2d");
1145 if (fill_background && bgcolor === null) {
1146 // render "sky-like" background
1147 var grad = ctx.createLinearGradient(0, 0, 0, height);
1148 grad.addColorStop(0, "#98c8f6");
1149 grad.addColorStop(0.4, "#cbebff");
1150 grad.addColorStop(1, "#f0f9ff");
1151 ctx.fillStyle = grad;
1152 ctx.fillRect(0, 0, width, height);
1153 }
1154
1155 var image = new Image();
1156 image.onload = function () {
1157 // draw webgl canvas image
1158 ctx.drawImage(image, 0, 0, width, height);
1159
1160 // render labels
1161 if (render_labels) renderLabels(ctx);
1162
1163 // save canvas image
1164 saveCanvasImage(canvas);
1165 restoreCanvasSize();
1166 };
1167 image.src = app.renderer.domElement.toDataURL("image/png");
1168 }
1169 else {
1170 // save webgl canvas image
1171 saveCanvasImage(app.renderer.domElement);
1172 restoreCanvasSize();
1173 }
1174 };
1175})();
1176
1177
1178/*
1179Q3D.Material
1180*/
1181Q3D.Material = function () {
1182};
1183
1184Q3D.Material.prototype = {
1185
1186 constructor: Q3D.Material,
1187
1188 // callback is called when texture is loaded
1189 loadJSONObject: function (jsonObject, callback) {
1190 this.origProp = jsonObject;
1191
1192 var m = jsonObject, opt = {};
1193
1194 if (m.ds && !Q3D.isIE) opt.side = THREE.DoubleSide;
1195
1196 if (m.flat) opt.shading = THREE.FlatShading;
1197
1198 // texture
1199 if (m.image !== undefined) {
1200 var image = m.image;
1201 if (image.url !== undefined) {
1202 opt.map = THREE.ImageUtils.loadTexture(image.url, undefined, callback);
1203 }
1204 else if (image.object !== undefined) { // WebKit Bridge
1205 opt.map = new THREE.Texture(image.object.toImageData());
1206 opt.map.needsUpdate = true;
1207 callback();
1208 }
1209 else { // base64 TODO: remove
1210 var img = new Image();
1211 img.onload = function () {
1212 opt.map.needsUpdate = true;
1213 callback();
1214 };
1215 img.src = image.base64;
1216 opt.map = new THREE.Texture(img);
1217 }
1218 }
1219
1220 if (m.o !== undefined && m.o < 1) {
1221 opt.opacity = m.o;
1222 opt.transparent = true;
1223 }
1224
1225 if (m.t) opt.transparent = true;
1226
1227 if (m.w) opt.wireframe = true;
1228
1229 if (m.type == Q3D.MaterialType.MeshLambert) {
1230 if (m.c !== undefined) opt.color = m.c;
1231 this.mat = new THREE.MeshLambertMaterial(opt);
1232 }
1233 else if (m.type == Q3D.MaterialType.MeshPhong) {
1234 if (m.c !== undefined) opt.color = m.c;
1235 this.mat = new THREE.MeshPhongMaterial(opt);
1236 }
1237 else if (m.type == Q3D.MaterialType.LineBasic) {
1238 opt.color = m.c;
1239 this.mat = new THREE.LineBasicMaterial(opt);
1240 }
1241 else {
1242 opt.color = 0xffffff;
1243 this.mat = new THREE.SpriteMaterial(opt);
1244 }
1245 },
1246
1247 set: function (material) {
1248 this.mat = material;
1249 this.origProp = {};
1250 },
1251
1252 type: function () {
1253 if (this.mat instanceof THREE.MeshLambertMaterial) return Q3D.MaterialType.MeshLambert;
1254 if (this.mat instanceof THREE.MeshPhongMaterial) return Q3D.MaterialType.MeshPhong;
1255 if (this.mat instanceof THREE.LineBasicMaterial) return Q3D.MaterialType.LineBasic;
1256 if (this.mat instanceof THREE.SpriteMaterial) return Q3D.MaterialType.Sprite;
1257 if (this.mat === undefined) return undefined;
1258 if (this.mat === null) return null;
1259 return Q3D.MaterialType.Unknown;
1260 },
1261
1262 dispose: function () {
1263 if (!this.mat) return;
1264
1265 if (this.mat.map) this.mat.map.dispose(); // dispose of texture
1266 this.mat.dispose();
1267 this.mat = null;
1268 }
1269};
1270
1271/*
1272Q3D.Materials
1273*/
1274Q3D.Materials = function () {
1275 this.materials = [];
1276};
1277
1278Q3D.Materials.prototype = Object.create(THREE.EventDispatcher.prototype);
1279Q3D.Materials.prototype.constructor = Q3D.Materials;
1280
1281// material: instance of Q3D.Material object or THREE.Material-based object
1282Q3D.Materials.prototype.add = function (material) {
1283 if (material instanceof Q3D.Material) this.materials.push(material);
1284 else {
1285 var mat = new Q3D.Material();
1286 mat.set(material);
1287 this.materials.push(mat);
1288 }
1289};
1290
1291Q3D.Materials.prototype.get = function (index) {
1292 return this.materials[index];
1293};
1294
1295Q3D.Materials.prototype.mat = function (index) {
1296 return this.materials[index].mat;
1297};
1298
1299
1300Q3D.Materials.prototype.loadJSONObject = function (jsonObject) {
1301 var _this = this, iterated = false;
1302 var callback = function () {
1303 if (iterated) _this.dispatchEvent({type: "renderRequest"});
1304 };
1305
1306 for (var i = 0, l = jsonObject.length; i < l; i++) {
1307 var mat = new Q3D.Material();
1308 mat.loadJSONObject(jsonObject[i], callback); // callback is called when a texture is loaded
1309 this.add(mat);
1310 }
1311 iterated = true;
1312
1313 // TODO: layer opacity is the average opacity of materials
1314 //this.opacity = sum_opacity / this.materials.length;
1315};
1316
1317Q3D.Materials.prototype.dispose = function () {
1318 for (var i = 0, l = this.materials.length; i < l; i++) {
1319 this.materials[i].dispose();
1320 }
1321 this.materials = [];
1322};
1323
1324// opacity
1325Q3D.Materials.prototype.setOpacity = function (opacity) {
1326 var material;
1327 for (var i = 0, l = this.materials.length; i < l; i++) {
1328 material.mat.transparent = Boolean(material.origProp.t) || (opacity < 1);
1329 material.mat.opacity = opacity;
1330 }
1331};
1332
1333// wireframe: boolean
1334Q3D.Materials.prototype.setWireframeMode = function (wireframe) {
1335 var material;
1336 for (var i = 0, l = this.materials.length; i < l; i++) {
1337 material = this.materials[i];
1338 if (material.origProp.w || material.mat instanceof THREE.LineBasicMaterial) continue;
1339 material.mat.wireframe = wireframe;
1340 }
1341};
1342
1343
1344/*
1345Q3D.DEMBlock
1346*/
1347Q3D.DEMBlock = function () {};
1348
1349Q3D.DEMBlock.prototype = {
1350
1351 constructor: Q3D.DEMBlock,
1352
1353 // obj: json object
1354 loadJSONObject: function (obj, layer, callback) {
1355 var _this = this,
1356 grid = obj.grid;
1357 this.data = obj;
1358
1359 // load material
1360 this.material = new Q3D.Material();
1361 this.material.loadJSONObject(obj.material, function () {
1362 if (callback) callback(_this);
1363 });
1364 layer.materials.add(this.material);
1365
1366 // create geometry and mesh
1367 var PlaneGeometry = (Q3D.Options.exportMode) ? THREE.PlaneGeometry : THREE.PlaneBufferGeometry,
1368 geom = new PlaneGeometry(obj.width, obj.height, grid.width - 1, grid.height - 1),
1369 mesh = new THREE.Mesh(geom, this.material.mat);
1370
1371 if (obj.translate !== undefined) mesh.position.set(obj.translate[0], obj.translate[1], obj.translate[2]);
1372
1373 var buildGeometry = function (grid_values) {
1374 if (Q3D.Options.exportMode) {
1375 for (var i = 0, l = geom.vertices.length; i < l; i++) {
1376 geom.vertices[i].z = grid_values[i];
1377 }
1378 }
1379 else {
1380 var vertices = geom.attributes.position.array;
1381 for (var i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
1382 vertices[j + 2] = grid_values[i];
1383 }
1384 geom.attributes.position.needsUpdate = true;
1385 }
1386
1387 // Calculate normals
1388 if (layer.properties.shading) {
1389 geom.computeFaceNormals();
1390 geom.computeVertexNormals();
1391 }
1392
1393 // build sides, bottom and frame
1394 if (obj.sides) {
1395 _this.buildSides(layer, grid, obj.width, obj.height, mesh, Q3D.Options.side.bottomZ);
1396 layer.sideVisible = true;
1397 }
1398 if (obj.frame) {
1399 _this.buildFrame(layer, grid, obj.width, obj.height, mesh, Q3D.Options.frame.bottomZ);
1400 layer.sideVisible = true;
1401 }
1402
1403 if (callback) callback(_this); // call callback to request rendering
1404 };
1405
1406 if (grid.array !== undefined) {
1407 buildGeometry(grid.array); // WebKit Bridge
1408 }
1409 else if (grid.url !== undefined) {
1410 var xhr = new XMLHttpRequest();
1411 xhr.open("GET", grid.url);
1412 xhr.responseType = "arraybuffer";
1413
1414 xhr.onload = function (event) {
1415 var arrayBuffer = xhr.response;
1416 if (arrayBuffer) {
1417 grid.array = new Float32Array(arrayBuffer)
1418 buildGeometry(grid.array);
1419 }
1420 };
1421 xhr.send(null);
1422 }
1423
1424 this.obj = mesh;
1425 return mesh;
1426 },
1427
1428 buildSides: function (layer, grid, planeWidth, planeHeight, parent, z0) {
1429 var matProp = this.material.origProp,
1430 opacity = (matProp.o !== undefined) ? matProp.o : 1;
1431 var material = new THREE.MeshLambertMaterial({color: Q3D.Options.side.color,
1432 opacity: opacity,
1433 transparent: (opacity < 1)});
1434 layer.materials.add(material);
1435
1436 var PlaneGeometry = (Q3D.Options.exportMode) ? THREE.PlaneGeometry : THREE.PlaneBufferGeometry;
1437 var band_width = -z0 * 2, grid_values = grid.array, w = grid.width, h = grid.height, HALF_PI = Math.PI / 2;
1438 var i, mesh;
1439
1440 // front and back
1441 var geom_fr = new PlaneGeometry(planeWidth, band_width, w - 1, 1),
1442 geom_ba = new PlaneGeometry(planeWidth, band_width, w - 1, 1);
1443
1444 var k = w * (h - 1);
1445 if (Q3D.Options.exportMode) {
1446 for (i = 0; i < w; i++) {
1447 geom_fr.vertices[i].y = grid_values[k + i];
1448 geom_ba.vertices[i].y = grid_values[w - 1 - i];
1449 }
1450 }
1451 else {
1452 var vertices_fr = geom_fr.attributes.position.array,
1453 vertices_ba = geom_ba.attributes.position.array;
1454
1455 for (i = 0; i < w; i++) {
1456 vertices_fr[i * 3 + 1] = grid_values[k + i];
1457 vertices_ba[i * 3 + 1] = grid_values[w - 1 - i];
1458 }
1459 }
1460 mesh = new THREE.Mesh(geom_fr, material);
1461 mesh.rotation.x = HALF_PI;
1462 mesh.position.y = -planeHeight / 2;
1463 mesh.name = "side";
1464 parent.add(mesh);
1465
1466 mesh = new THREE.Mesh(geom_ba, material);
1467 mesh.rotation.x = HALF_PI;
1468 mesh.rotation.y = Math.PI;
1469 mesh.position.y = planeHeight / 2;
1470 mesh.name = "side";
1471 parent.add(mesh);
1472
1473 // left and right
1474 var geom_le = new PlaneGeometry(band_width, planeHeight, 1, h - 1),
1475 geom_ri = new PlaneGeometry(band_width, planeHeight, 1, h - 1);
1476
1477 if (Q3D.Options.exportMode) {
1478 for (i = 0; i < h; i++) {
1479 geom_le.vertices[i * 2 + 1].x = grid_values[w * i];
1480 geom_ri.vertices[i * 2].x = -grid_values[w * (i + 1) - 1];
1481 }
1482 }
1483 else {
1484 var vertices_le = geom_le.attributes.position.array,
1485 vertices_ri = geom_ri.attributes.position.array;
1486
1487 for (i = 0; i < h; i++) {
1488 vertices_le[(i * 2 + 1) * 3] = grid_values[w * i];
1489 vertices_ri[i * 2 * 3] = -grid_values[w * (i + 1) - 1];
1490 }
1491 }
1492 mesh = new THREE.Mesh(geom_le, material);
1493 mesh.rotation.y = -HALF_PI;
1494 mesh.position.x = -planeWidth / 2;
1495 mesh.name = "side";
1496 parent.add(mesh);
1497
1498 mesh = new THREE.Mesh(geom_ri, material);
1499 mesh.rotation.y = HALF_PI;
1500 mesh.position.x = planeWidth / 2;
1501 mesh.name = "side";
1502 parent.add(mesh);
1503
1504 // bottom
1505 if (Q3D.Options.exportMode) {
1506 var geom = new THREE.PlaneGeometry(planeWidth, planeHeight, w - 1, h - 1);
1507 }
1508 else {
1509 var geom = new THREE.PlaneBufferGeometry(planeWidth, planeHeight, 1, 1);
1510 }
1511 mesh = new THREE.Mesh(geom, material);
1512 mesh.rotation.x = Math.PI;
1513 mesh.position.z = z0;
1514 mesh.name = "bottom";
1515 parent.add(mesh);
1516
1517 parent.updateMatrixWorld();
1518 },
1519
1520 buildFrame: function (layer, grid, planeWidth, planeHeight, parent, z0) {
1521 var matProp = data.material.origProp,
1522 opacity = (matProp.o !== undefined) ? matProp.o : 1;
1523 var material = new THREE.LineBasicMaterial({color: Q3D.Options.frame.color,
1524 opacity: opacity,
1525 transparent: (opacity < 1)});
1526 layer.materials.add(material);
1527
1528 // horizontal rectangle at bottom
1529 var hw = planeWidth / 2, hh = planeHeight / 2;
1530 var geom = new THREE.Geometry();
1531 geom.vertices.push(new THREE.Vector3(-hw, -hh, z0),
1532 new THREE.Vector3(hw, -hh, z0),
1533 new THREE.Vector3(hw, hh, z0),
1534 new THREE.Vector3(-hw, hh, z0),
1535 new THREE.Vector3(-hw, -hh, z0));
1536
1537 var obj = new THREE.Line(geom, material);
1538 obj.name = "frame";
1539 parent.add(obj);
1540
1541 // vertical lines at corners
1542 var pts = [[-hw, -hh, grid.array[grid.array.length - grid.width]],
1543 [hw, -hh, grid.array[grid.array.length - 1]],
1544 [hw, hh, grid.array[grid.width - 1]],
1545 [-hw, hh, grid.array[0]]];
1546 pts.forEach(function (pt) {
1547 var geom = new THREE.Geometry();
1548 geom.vertices.push(new THREE.Vector3(pt[0], pt[1], pt[2]),
1549 new THREE.Vector3(pt[0], pt[1], z0));
1550
1551 var obj = new THREE.Line(geom, material);
1552 obj.name = "frame";
1553 parent.add(obj);
1554 });
1555
1556 parent.updateMatrixWorld();
1557 },
1558
1559 getValue: function (x, y) {
1560 var grid = this.data.grid;
1561 if (0 <= x && x < grid.width && 0 <= y && y < grid.height) return grid.array[x + grid.width * y];
1562 return null;
1563 },
1564
1565 // TODO:
1566 contains: function (x, y) {
1567 var xmin = this.plane.offsetX - this.plane.width / 2,
1568 xmax = this.plane.offsetX + this.plane.width / 2,
1569 ymin = this.plane.offsetY - this.plane.height / 2,
1570 ymax = this.plane.offsetY + this.plane.height / 2;
1571 if (xmin <= x && x <= xmax && ymin <= y && y <= ymax) return true;
1572 return false;
1573 }
1574
1575};
1576
1577
1578/*
1579Q3D.ClippedDEMBlock
1580*/
1581Q3D.ClippedDEMBlock = function () {};
1582
1583Q3D.ClippedDEMBlock.prototype = {
1584
1585 constructor: Q3D.ClippedDEMBlock,
1586
1587 loadJSONObject: function (jsonObject, layer, callback) {
1588 for (var k in jsonObject) {
1589 this[k] = jsonObject[k];
1590 }
1591
1592 var geom = Q3D.Utils.createOverlayGeometry(this.clip.triangles, this.clip.split_polygons, layer.getZ.bind(layer));
1593
1594 // set UVs
1595 Q3D.Utils.setGeometryUVs(geom, layer.sceneData.width, layer.sceneData.height);
1596
1597 var mesh = new THREE.Mesh(geom, material);
1598 if (this.plane.offsetX != 0) mesh.position.x = this.plane.offsetX;
1599 if (this.plane.offsetY != 0) mesh.position.y = this.plane.offsetY;
1600 this.obj = mesh;
1601 return mesh;
1602 },
1603
1604 buildSides: function (layer, parent, material, z0) {
1605 var polygons = this.clip.polygons,
1606 zFunc = layer.getZ.bind(layer),
1607 bzFunc = function (x, y) { return z0; };
1608
1609 // make back-side material for bottom
1610 var mat_back = material.clone();
1611 mat_back.side = THREE.BackSide;
1612 layer.materials.add(mat_back);
1613
1614 var geom, mesh, shape, vertices;
1615 for (var i = 0, l = polygons.length; i < l; i++) {
1616 var polygon = polygons[i];
1617
1618 // sides
1619 for (var j = 0, m = polygon.length; j < m; j++) {
1620 vertices = layer.segmentizeLineString(polygon[j], zFunc);
1621 geom = Q3D.Utils.createWallGeometry(vertices, bzFunc);
1622 mesh = new THREE.Mesh(geom, material);
1623 mesh.name = "side";
1624 parent.add(mesh);
1625 }
1626
1627 // bottom
1628 shape = new THREE.Shape(Q3D.Utils.arrayToVec2Array(polygon[0]));
1629 for (var j = 1, m = polygon.length; j < m; j++) {
1630 shape.holes.push(new THREE.Path(Q3D.Utils.arrayToVec2Array(polygon[j])));
1631 }
1632 geom = new THREE.ShapeGeometry(shape);
1633 mesh = new THREE.Mesh(geom, mat_back);
1634 mesh.position.z = z0;
1635 mesh.name = "bottom";
1636 parent.add(mesh);
1637 }
1638 parent.updateMatrixWorld();
1639 },
1640
1641 getValue: function (x, y) {
1642 if (0 <= x && x < this.width && 0 <= y && y < this.height) return this.data[x + this.width * y];
1643 return null;
1644 },
1645
1646 contains: function (x, y) {
1647 var xmin = this.plane.offsetX - this.plane.width / 2,
1648 xmax = this.plane.offsetX + this.plane.width / 2,
1649 ymin = this.plane.offsetY - this.plane.height / 2,
1650 ymax = this.plane.offsetY + this.plane.height / 2;
1651 if (xmin <= x && x <= xmax && ymin <= y && y <= ymax) return true;
1652 return false;
1653 }
1654
1655};
1656
1657/*
1658Q3D.MapLayer
1659*/
1660Q3D.MapLayer = function () {
1661 this.opacity = 1;
1662 this.visible = true;
1663 this.queryable = true;
1664
1665 this.materials = new Q3D.Materials();
1666
1667 var _this = this;
1668 this.materials.addEventListener("renderRequest", function (event) {
1669 console.log("renderRequest from materials");
1670 _this.requestRender();
1671 });
1672
1673 this.objectGroup = new Q3D.Group();
1674 this.queryObjs = [];
1675};
1676
1677Q3D.MapLayer.prototype = Object.create(THREE.EventDispatcher.prototype);
1678Q3D.MapLayer.prototype.constructor = Q3D.MapLayer;
1679
1680Q3D.MapLayer.prototype.addObject = function (object, queryable) {
1681 if (queryable === undefined) queryable = this.queryable;
1682
1683 object.userData.layerId = this.id;
1684 this.objectGroup.add(object);
1685
1686 if (queryable) {
1687 var queryObjs = this.queryObjs;
1688 object.traverse(function (obj) {
1689 queryObjs.push(obj);
1690 });
1691 }
1692};
1693
1694Q3D.MapLayer.prototype.queryableObjects = function () {
1695 return (this.visible) ? this.queryObjs : [];
1696};
1697
1698Q3D.MapLayer.prototype.removeAllObjects = function () {
1699 // dispose of geometries
1700 this.objectGroup.traverse(function (obj) {
1701 if (obj.geometry) obj.geometry.dispose();
1702 });
1703
1704 // dispose of materials
1705 this.materials.dispose();
1706
1707 // remove all child objects from object group
1708 for (var i = this.objectGroup.children.length - 1 ; i >= 0; i--) {
1709 this.objectGroup.remove(this.objectGroup.children[i]);
1710 }
1711 this.queryObjs = [];
1712};
1713
1714Q3D.MapLayer.prototype.loadJSONObject = function (jsonObject, scene) {
1715 // properties
1716 if (jsonObject.properties !== undefined) {
1717 this.properties = jsonObject.properties;
1718 this.setVisible((jsonObject.properties.visible !== false) ? true : false);
1719 }
1720
1721 if (jsonObject.data !== undefined) {
1722 this.removeAllObjects();
1723
1724 // materials
1725 if (jsonObject.data.materials !== undefined) {
1726 this.materials.loadJSONObject(jsonObject.data.materials);
1727 }
1728 }
1729
1730 this.sceneData = scene.userData;
1731};
1732
1733Q3D.MapLayer.prototype.setOpacity = function (opacity) {
1734 this.materials.setOpacity(opacity);
1735 this.opacity = opacity;
1736};
1737
1738Q3D.MapLayer.prototype.setVisible = function (visible) {
1739 // if (this.visible === visible) return;
1740 this.visible = visible;
1741 this.objectGroup.visible = visible;
1742};
1743
1744Q3D.MapLayer.prototype.setWireframeMode = function (wireframe) {
1745 this.materials.setWireframeMode(wireframe);
1746};
1747
1748Q3D.MapLayer.prototype.requestRender = function () {
1749 this.dispatchEvent({type: "renderRequest"});
1750};
1751
1752
1753/*
1754Q3D.DEMLayer --> Q3D.MapLayer
1755*/
1756Q3D.DEMLayer = function () {
1757 Q3D.MapLayer.call(this);
1758 this.type = Q3D.LayerType.DEM;
1759 this.blocks = [];
1760};
1761
1762Q3D.DEMLayer.prototype = Object.create(Q3D.MapLayer.prototype);
1763Q3D.DEMLayer.prototype.constructor = Q3D.DEMLayer;
1764
1765Q3D.DEMLayer.prototype.loadJSONObject = function (jsonObject, scene) {
1766 if (jsonObject.type == "layer") {
1767 Q3D.MapLayer.prototype.loadJSONObject.call(this, jsonObject, scene);
1768 if (jsonObject.data !== undefined) this.build(jsonObject.data);
1769 }
1770 else if (jsonObject.type == "block") {
1771 var index = jsonObject.block;
1772 if (this.blocks[index] === undefined) this.blocks[index] = new Q3D.DEMBlock();
1773
1774 var mesh = this.blocks[index].loadJSONObject(jsonObject, this, this.requestRender.call(this));
1775 this.addObject(mesh);
1776 }
1777};
1778
1779Q3D.DEMLayer.prototype.build = function (blocks) {
1780 var _this = this;
1781 // build blocks
1782 blocks.forEach(function (block) {
1783 var b = new Q3D.DEMBlock(),
1784 mesh = b.loadJSONObject(block, _this, function (blk) {
1785 _this.requestRender();
1786 });
1787 _this.addObject(mesh);
1788 _this.blocks.push(b);
1789 });
1790};
1791
1792// TODO: block.plane, .getValue
1793// calculate elevation at the coordinates (x, y) on triangle face
1794Q3D.DEMLayer.prototype.getZ = function (x, y) {
1795 var xmin = -this.sceneData.width / 2,
1796 ymax = this.sceneData.height / 2;
1797
1798 for (var i = 0, l = this.blocks.length; i < l; i++) {
1799 var block = this.blocks[i];
1800 if (!block.contains(x, y)) continue;
1801
1802 var ix = block.plane.width / (block.width - 1),
1803 iy = block.plane.height / (block.height - 1);
1804
1805 var xmin = block.plane.offsetX - block.plane.width / 2,
1806 ymax = block.plane.offsetY + block.plane.height / 2;
1807
1808 var mx0 = Math.floor((x - xmin) / ix),
1809 my0 = Math.floor((ymax - y) / iy);
1810
1811 var z = [block.getValue(mx0, my0),
1812 block.getValue(mx0 + 1, my0),
1813 block.getValue(mx0, my0 + 1),
1814 block.getValue(mx0 + 1, my0 + 1)];
1815
1816 var px0 = xmin + ix * mx0,
1817 py0 = ymax - iy * my0;
1818
1819 var sdx = (x - px0) / ix,
1820 sdy = (py0 - y) / iy;
1821
1822 // console.log(x, y, mx0, my0, sdx, sdy);
1823
1824 if (sdx <= 1 - sdy) return z[0] + (z[1] - z[0]) * sdx + (z[2] - z[0]) * sdy;
1825 else return z[3] + (z[2] - z[3]) * (1 - sdx) + (z[1] - z[3]) * (1 - sdy);
1826 }
1827 return null;
1828};
1829
1830// TODO: block.width/height
1831Q3D.DEMLayer.prototype.segmentizeLineString = function (lineString, zFunc) {
1832 // does not support multiple blocks
1833 if (zFunc === undefined) zFunc = function () { return 0; };
1834 var width = this.sceneData.width, height = this.sceneData.height;
1835 var xmin = -width / 2, ymax = height / 2;
1836 var block = this.blocks[0];
1837 var x_segments = block.width - 1,
1838 y_segments = block.height - 1;
1839 var ix = width / x_segments,
1840 iy = height / y_segments;
1841
1842 var pts = [];
1843 for (var i = 1, l = lineString.length; i < l; i++) {
1844 var pt1 = lineString[i - 1], pt2 = lineString[i];
1845 var x1 = pt1[0], x2 = pt2[0], y1 = pt1[1], y2 = pt2[1], z1 = pt1[2], z2 = pt2[2];
1846 var nx1 = (x1 - xmin) / ix,
1847 nx2 = (x2 - xmin) / ix;
1848 var ny1 = (ymax - y1) / iy,
1849 ny2 = (ymax - y2) / iy;
1850 var ns1 = Math.abs(ny1 + nx1),
1851 ns2 = Math.abs(ny2 + nx2);
1852
1853 var p = [0], nvp = [[nx1, nx2], [ny1, ny2], [ns1, ns2]];
1854 for (var j = 0; j < 3; j++) {
1855 var v1 = nvp[j][0], v2 = nvp[j][1];
1856 if (v1 == v2) continue;
1857 var k = Math.ceil(Math.min(v1, v2));
1858 var n = Math.floor(Math.max(v1, v2));
1859 for (; k <= n; k++) {
1860 p.push((k - v1) / (v2 - v1));
1861 }
1862 }
1863
1864 p.sort(function (a, b) { return a - b; });
1865
1866 var x, y, z, lp = null;
1867 for (var j = 0, m = p.length; j < m; j++) {
1868 if (lp === p[j]) continue;
1869 if (p[j] == 1) break;
1870
1871 x = x1 + (x2 - x1) * p[j];
1872 y = y1 + (y2 - y1) * p[j];
1873
1874 if (z1 === undefined || z2 === undefined) z = zFunc(x, y);
1875 else z = z1 + (z2 - z1) * p[j];
1876
1877 pts.push(new THREE.Vector3(x, y, z));
1878
1879 // Q3D.Utils.putStick(x, y, zFunc);
1880
1881 lp = p[j];
1882 }
1883 }
1884 // last point (= the first point)
1885 var pt = lineString[lineString.length - 1];
1886 pts.push(new THREE.Vector3(pt[0], pt[1], (pt[2] === undefined) ? zFunc(pt[0], pt[1]) : pt[2]));
1887
1888 /*
1889 for (var i = 0, l = lineString.length - 1; i < l; i++) {
1890 Q3D.Utils.putStick(lineString[i][0], lineString[i][1], zFunc, 0.8);
1891 }
1892 */
1893
1894 return pts;
1895};
1896
1897Q3D.DEMLayer.prototype.setVisible = function (visible) {
1898 Q3D.MapLayer.prototype.setVisible.call(this, visible);
1899 // if (visible && this.sideVisible === false) this.setSideVisibility(false);
1900};
1901
1902// TODO
1903Q3D.DEMLayer.prototype.setSideVisibility = function (visible) {
1904 this.sideVisible = visible;
1905 this.objectGroup.traverse(function (obj) {
1906 if (obj.name == "side" || obj.name == "bottom" || obj.name == "frame") obj.visible = visible;
1907 });
1908};
1909
1910
1911/*
1912Q3D.VectorLayer --> Q3D.MapLayer
1913*/
1914Q3D.VectorLayer = function () {
1915 Q3D.MapLayer.call(this);
1916
1917 // this.labelConnectorGroup = undefined;
1918 // this.labelParentElement = undefined;
1919};
1920
1921Q3D.VectorLayer.prototype = Object.create(Q3D.MapLayer.prototype);
1922Q3D.VectorLayer.prototype.constructor = Q3D.VectorLayer;
1923
1924Q3D.VectorLayer.prototype.build = function (block) {};
1925
1926Q3D.VectorLayer.prototype.clearLabels = function () {
1927 if (this.labelConnectorGroup) this.labelConnectorGroup.clear();
1928
1929 // create parent element for labels
1930 var elem = this.labelParentElement;
1931 if (elem) {
1932 while (elem.lastChild) {
1933 elem.removeChild(elem.lastChild);
1934 }
1935 }
1936};
1937
1938Q3D.VectorLayer.prototype.buildLabels = function (features, getPointsFunc, zFunc) {
1939 if (this.properties.label === undefined || getPointsFunc === undefined) return;
1940
1941 var zShift = this.sceneData.zShift, zScale = this.sceneData.zScale;
1942 var prop = this.properties.label,
1943 pIndex = prop.index,
1944 heightType = prop.heightType,
1945 connHeight = prop.height;
1946
1947 // function to get height for both ends of label connector. returns [bottomZ, topZ]
1948 var labelHeightFunc = function (f, pt) {
1949 var z0 = (zFunc === undefined) ? pt[2] : zFunc(pt[0], pt[1]);
1950
1951 if (heightType == 1) return [z0, connHeight]; // fixed height
1952 if (heightType >= 100) return [z0, (f.prop[heightType - 100] + zShift) * zScale + connHeight]; // data-defined + addend
1953
1954 // 2: height from point (bottom height if extruded polygon, elevation at centroid of polygon if overlay)
1955 // 3: height from top of extruded polygon / from overlay
1956 if (heightType == 3) z0 += f.geom.h;
1957 return [z0, z0 + connHeight];
1958 };
1959
1960 var line_mat = new THREE.LineBasicMaterial({color: Q3D.Options.label.connectorColor});
1961
1962 for (var i = 0, l = features.length; i < l; i++) {
1963 var f = features[i];
1964 var text = f.prop[pIndex];
1965 if (text === null || text === "") continue;
1966
1967 var pts = getPointsFunc(f);
1968 for (var j = 0, m = pts.length; j < m; j++) {
1969 var pt = pts[j];
1970 // create div element for label
1971 var e = document.createElement("div");
1972 e.appendChild(document.createTextNode(text));
1973 e.className = "label";
1974 this.labelParentElement.appendChild(e);
1975
1976 var z = labelHeightFunc(f, pt);
1977 var pt0 = new THREE.Vector3(pt[0], pt[1], z[0]); // bottom
1978 var pt1 = new THREE.Vector3(pt[0], pt[1], z[1]); // top
1979
1980 // create connector
1981 var geom = new THREE.Geometry();
1982 geom.vertices.push(pt1, pt0);
1983 var conn = new THREE.Line(geom, line_mat);
1984 conn.userData.layerId = this.id;
1985 //conn.userData.featureId = i;
1986 conn.userData.elem = e;
1987 this.labelConnectorGroup.add(conn);
1988 }
1989 }
1990};
1991
1992Q3D.VectorLayer.prototype.loadJSONObject = function (jsonObject, scene) {
1993 if (jsonObject.type == "layer") {
1994 Q3D.MapLayer.prototype.loadJSONObject.call(this, jsonObject, scene);
1995 if (jsonObject.data !== undefined) {
1996 this.clearLabels();
1997
1998 // build labels
1999 if (this.properties.label !== undefined) {
2000 // create a label connector group
2001 if (this.labelConnectorGroup === undefined) {
2002 this.labelConnectorGroup = new Q3D.Group();
2003 this.labelConnectorGroup.userData.layerId = this.id;
2004 scene.labelConnectorGroup.add(this.labelConnectorGroup);
2005 }
2006
2007 // create a label parent element
2008 if (this.labelParentElement === undefined) {
2009 this.labelParentElement = document.createElement("div");
2010 scene.labelRootElement.appendChild(this.labelParentElement);
2011 }
2012 }
2013
2014 (jsonObject.data.blocks || []).forEach(function (block) {
2015 if (block.url !== undefined) app.loadJSONFromURL(block.url);
2016 else {
2017 this.build(block.features);
2018 if (this.properties.label !== undefined) this.buildLabels(block.features);
2019 }
2020 }, this);
2021 }
2022 }
2023 else if (jsonObject.type == "block") {
2024 this.build(jsonObject.features);
2025 if (this.properties.label !== undefined) this.buildLabels(jsonObject.features);
2026 }
2027};
2028
2029Q3D.VectorLayer.prototype.setVisible = function (visible) {
2030 Q3D.MapLayer.prototype.setVisible.call(this, visible);
2031
2032 if (this.labelParentElement) this.labelParentElement.style.display = (visible) ? "block" : "none";
2033 if (this.labelConnectorGroup) this.labelConnectorGroup.visible = visible;
2034};
2035
2036
2037/*
2038Q3D.PointLayer --> Q3D.VectorLayer
2039*/
2040Q3D.PointLayer = function () {
2041 Q3D.VectorLayer.call(this);
2042 this.type = Q3D.LayerType.Point;
2043};
2044
2045Q3D.PointLayer.prototype = Object.create(Q3D.VectorLayer.prototype);
2046Q3D.PointLayer.prototype.constructor = Q3D.PointLayer;
2047
2048Q3D.PointLayer.prototype.loadJSONObject = function (jsonObject, scene) {
2049 Q3D.VectorLayer.prototype.loadJSONObject.call(this, jsonObject, scene);
2050};
2051
2052Q3D.PointLayer.prototype.build = function (features) {
2053 var objType = this.properties.objType;
2054 if (objType == "Icon") { this.buildIcons(features); return; }
2055 if (objType == "JSON model" || objType == "COLLADA model") { this.buildModels(features); return; }
2056
2057 var deg2rad = Math.PI / 180;
2058 var createGeometry, scaleZ = 1;
2059
2060 if (objType == "Sphere") {
2061 createGeometry = function (geom) {
2062 if (geom.r == 0) return null;
2063 return new THREE.SphereGeometry(geom.r);
2064 };
2065 }
2066 else if (objType == "Box") createGeometry = function (geom) { return new THREE.BoxGeometry(geom.w, geom.h, geom.d); };
2067 else if (objType == "Disk") {
2068 createGeometry = function (geom) {
2069 var geom = new THREE.CylinderGeometry(geom.r, geom.r, 0, 32), m = new THREE.Matrix4();
2070 if (90 - geom.d) geom.applyMatrix(m.makeRotationX((90 - geom.d) * deg2rad));
2071 if (geom.dd) geom.applyMatrix(m.makeRotationZ(-geom.dd * deg2rad));
2072 return geom;
2073 };
2074 if (this.ns === undefined || this.ns == false) scaleZ = this.sceneData.zExaggeration;
2075 }
2076 else createGeometry = function (geom) { return new THREE.CylinderGeometry(geom.rt, geom.rb, geom.h); }; // Cylinder or Cone
2077
2078 // each feature in this layer
2079 var materials = this.materials;
2080 var f, geom, geometry, z_addend, i, l, mesh, pt;
2081 for (var fidx = 0, flen = features.length; fidx < flen; fidx++) {
2082 f = features[fidx];
2083 geom = f.geom;
2084 z_addend = (geom.h) ? geom.h / 2 : 0;
2085 for (i = 0, l = geom.pts.length; i < l; i++) {
2086 geometry = createGeometry(geom);
2087 if (geometry === null) continue;
2088 mesh = new THREE.Mesh(geometry, materials.mat(f.mat));
2089
2090 pt = geom.pts[i];
2091 mesh.position.set(pt[0], pt[1], pt[2] + z_addend);
2092 if (geom.rotateX) mesh.rotation.x = geom.rotateX * deg2rad;
2093 if (scaleZ != 1) mesh.scale.z = scaleZ;
2094 //mesh.userData.featureId = fid;
2095 mesh.userData.properties = f.prop;
2096
2097 this.addObject(mesh);
2098 }
2099 }
2100};
2101
2102Q3D.PointLayer.prototype.buildIcons = function (features) {
2103 // each feature in this layer
2104 for (var fidx = 0, flen = features.length; fidx < flen; fidx++) {
2105 var f = features[fidx],
2106 geom = f.geom;
2107 var mat = this.materials.mat(f.mat);
2108 var image = scene.images[mat.i]; // TODO:
2109
2110 // base size is 64 x 64
2111 var scale = (geom.scale === undefined) ? 1 : geom.scale;
2112 var sx = image.width / 64 * scale,
2113 sy = image.height / 64 * scale;
2114
2115 for (var i = 0, l = geom.pts.length; i < l; i++) {
2116 var pt = geom.pts[i];
2117 var sprite = new THREE.Sprite(mat);
2118 sprite.position.set(pt[0], pt[1], pt[2]);
2119 sprite.scale.set(sx, sy, scale);
2120 //sprite.userData.featureId = fid;
2121 sprite.mesh.userData.properties = f.prop;
2122
2123 this.addObject(sprite);
2124 }
2125 }
2126};
2127
2128// TODO:
2129Q3D.PointLayer.prototype.buildModels = function (features) {
2130 // each feature in this layer
2131 for (var fid = 0, flen = features.length; fid < flen; fid++) {
2132 var f = features[fid];
2133 Q3D.application.modelBuilders[f.model_index].addFeature(this.userData.id, fid);
2134 }
2135};
2136
2137Q3D.PointLayer.prototype.buildLabels = function (features) {
2138 Q3D.VectorLayer.prototype.buildLabels.call(this, features, function (f) { return f.geom.pts; });
2139};
2140
2141
2142/*
2143Q3D.LineLayer --> Q3D.VectorLayer
2144*/
2145Q3D.LineLayer = function () {
2146 Q3D.VectorLayer.call(this);
2147 this.type = Q3D.LayerType.Line;
2148};
2149
2150Q3D.LineLayer.prototype = Object.create(Q3D.VectorLayer.prototype);
2151Q3D.LineLayer.prototype.constructor = Q3D.LineLayer;
2152
2153Q3D.LineLayer.prototype.loadJSONObject = function (jsonObject, scene) {
2154 Q3D.VectorLayer.prototype.loadJSONObject.call(this, jsonObject, scene);
2155};
2156
2157Q3D.LineLayer.prototype.build = function (features) {
2158 var createObject,
2159 objType = this.properties.objType,
2160 materials = this.materials,
2161 sceneData = this.sceneData;
2162
2163 if (objType == this._lastObjType && this._createObject !== undefined) {
2164 createObject = this._createObject;
2165 }
2166 else if (objType == "Line") {
2167 createObject = function (f, line) {
2168 var geom = new THREE.Geometry(), pt;
2169 for (var i = 0, l = line.length; i < l; i++) {
2170 pt = line[i];
2171 geom.vertices.push(new THREE.Vector3(pt[0], pt[1], pt[2]));
2172 }
2173 return new THREE.Line(geom, materials.mat(f.mat));
2174 };
2175 }
2176 else if (objType == "Pipe" || objType == "Cone") {
2177 var hasJoints = (objType == "Pipe");
2178
2179 createObject = function (f, line) {
2180 var group = new Q3D.Group();
2181
2182 var pt0 = new THREE.Vector3(), pt1 = new THREE.Vector3(), sub = new THREE.Vector3();
2183 var geom, obj, pt;
2184 for (var i = 0, l = line.length; i < l; i++) {
2185 pt = line[i];
2186 pt1.set(pt[0], pt[1], pt[2]);
2187
2188 if (hasJoints) {
2189 geom = new THREE.SphereGeometry(f.geom.rb, 8, 8);
2190 obj = new THREE.Mesh(geom, materials.mat(f.mat));
2191 obj.position.copy(pt1);
2192 group.add(obj);
2193 }
2194
2195 if (i) {
2196 sub.subVectors(pt1, pt0);
2197 geom = new THREE.CylinderGeometry(f.geom.rt, f.geom.rb, pt0.distanceTo(pt1), 8);
2198 obj = new THREE.Mesh(geom, materials.mat(f.mat));
2199 obj.position.set((pt0.x + pt1.x) / 2, (pt0.y + pt1.y) / 2, (pt0.z + pt1.z) / 2);
2200 obj.rotation.set(Math.atan2(sub.z, Math.sqrt(sub.x * sub.x + sub.y * sub.y)), 0, Math.atan2(sub.y, sub.x) - Math.PI / 2, "ZXY");
2201 group.add(obj);
2202 }
2203 pt0.copy(pt1);
2204 }
2205 return group;
2206 };
2207 }
2208 else if (objType == "Box") {
2209 // In this method, box corners are exposed near joint when both azimuth and slope of
2210 // the segments of both sides are different. Also, some unnecessary faces are created.
2211 var debugMode = Q3D.Options.debugMode;
2212 var faces = [], vi;
2213 vi = [[0, 5, 4], [4, 5, 1], // left turn - top, side, bottom
2214 [3, 0, 7], [7, 0, 4],
2215 [6, 3, 2], [2, 3, 7],
2216 [4, 1, 0], [0, 1, 5], // right turn - top, side, bottom
2217 [1, 2, 5], [5, 2, 6],
2218 [2, 7, 6], [6, 7, 3]];
2219
2220 for (var j = 0; j < 12; j++) {
2221 faces.push(new THREE.Face3(vi[j][0], vi[j][1], vi[j][2]));
2222 }
2223
2224 createObject = function (f, line) {
2225 var geometry = new THREE.Geometry(),
2226 group = new Q3D.Group(); // used in debug mode
2227
2228 var geom, obj, dist, quat, rx, rz, wh4, vb4, vf4;
2229 var pt0 = new THREE.Vector3(), pt1 = new THREE.Vector3(), sub = new THREE.Vector3(),
2230 pt = new THREE.Vector3(), ptM = new THREE.Vector3(), scale1 = new THREE.Vector3(1, 1, 1),
2231 matrix = new THREE.Matrix4(), quat = new THREE.Quaternion();
2232
2233 pt0.set(line[0][0], line[0][1], line[0][2]);
2234 for (var i = 1, l = line.length; i < l; i++) {
2235 pt1.set(line[i][0], line[i][1], line[i][2]);
2236 dist = pt0.distanceTo(pt1);
2237 sub.subVectors(pt1, pt0);
2238 rx = Math.atan2(sub.z, Math.sqrt(sub.x * sub.x + sub.y * sub.y));
2239 rz = Math.atan2(sub.y, sub.x) - Math.PI / 2;
2240 ptM.set((pt0.x + pt1.x) / 2, (pt0.y + pt1.y) / 2, (pt0.z + pt1.z) / 2); // midpoint
2241 quat.setFromEuler(new THREE.Euler(rx, 0, rz, "ZXY"));
2242 matrix.compose(ptM, quat, scale1);
2243
2244 // place a box to the segment
2245 geom = new THREE.BoxGeometry(f.geom.w, dist, f.geom.h);
2246 if (debugMode) {
2247 obj = new THREE.Mesh(geom, materials.mat(f.mat));
2248 obj.position.set(ptM.x, ptM.y, ptM.z);
2249 obj.rotation.set(rx, 0, rz, "ZXY");
2250 group.add(obj);
2251 }
2252 else {
2253 geom.applyMatrix(matrix);
2254 geometry.merge(geom);
2255 }
2256
2257 // joint
2258 // 4 vertices of backward side of current segment
2259 wh4 = [[-f.geom.w / 2, f.geom.h / 2],
2260 [f.geom.w / 2, f.geom.h / 2],
2261 [f.geom.w / 2, -f.geom.h / 2],
2262 [-f.geom.w / 2, -f.geom.h / 2]];
2263 vb4 = [];
2264 for (j = 0; j < 4; j++) {
2265 pt.set(wh4[j][0], -dist / 2, wh4[j][1]);
2266 pt.applyMatrix4(matrix);
2267 vb4.push(pt.clone());
2268 }
2269
2270 if (vf4) {
2271 geom = new THREE.Geometry();
2272 geom.vertices = vf4.concat(vb4);
2273 geom.faces = faces;
2274 if (debugMode) {
2275 geom.computeFaceNormals();
2276 group.add(new THREE.Mesh(geom));
2277 }
2278 else {
2279 geometry.merge(geom);
2280 }
2281 }
2282
2283 // 4 vertices of forward side
2284 vf4 = [];
2285 for (j = 0; j < 4; j++) {
2286 pt.set(wh4[j][0], dist / 2, wh4[j][1]);
2287 pt.applyMatrix4(matrix);
2288 vf4.push(new THREE.Vector3(pt.x, pt.y, pt.z));
2289 }
2290
2291 pt0.copy(pt1);
2292 }
2293
2294 if (debugMode) return group;
2295
2296 geometry.mergeVertices();
2297 geometry.computeFaceNormals();
2298 return new THREE.Mesh(geometry, materials.mat(f.mat));
2299 };
2300 }
2301 else if (objType == "Profile") {
2302 var relativeToDEM = (this.am == "relative"), // altitude mode
2303 bRelativeToDEM = (this.bam == "relative"), // altitude mode of bottom height
2304 dem = scene.mapLayers[0], // TODO: referenced DEM layer
2305 z0 = sceneData.zShift * sceneData.zScale;
2306
2307 createObject = function (f, line) {
2308 var bzFunc, vertices;
2309 if (bRelativeToDEM) bzFunc = function (x, y) { return dem.getZ(x, y) + f.geom.bh; };
2310 else bzFunc = function (x, y) { return z0 + f.geom.bh; };
2311
2312 if (relativeToDEM) {
2313 var zFunc = function (x, y) { return dem.getZ(x, y) + f.geom.h; };
2314 vertices = dem.segmentizeLineString(line, zFunc); // line is list of 2D point [x, y]
2315 }
2316 else if (bRelativeToDEM) {
2317 vertices = dem.segmentizeLineString(line); // line is list of 3D point [x, y, z]
2318 }
2319 else { // both altitude modes are absolute
2320 var pt;
2321 vertices = [];
2322 for (var i = 0, l = line.length; i < l; i++) {
2323 pt = line[i];
2324 vertices.push(new THREE.Vector3(pt[0], pt[1], pt[2]));
2325 }
2326 }
2327 var geom = Q3D.Utils.createWallGeometry(vertices, bzFunc); // TODO: flat shading
2328 return new THREE.Mesh(geom, materials.mat(f.mat));
2329 };
2330 }
2331
2332 // each feature in this layer
2333 for (var fidx = 0, flen = features.length; fidx < flen; fidx++) {
2334 var f = features[fidx],
2335 geom = f.geom;
2336 for (var i = 0, l = geom.lines.length; i < l; i++) {
2337 var obj = createObject(f, geom.lines[i]);
2338 //obj.userData.featureId = fid;
2339 obj.userData.properties = f.prop;
2340 this.addObject(obj);
2341 }
2342 }
2343
2344 this._lastObjType = objType;
2345 this._createObject = createObject;
2346};
2347
2348Q3D.LineLayer.prototype.buildLabels = function (features) {
2349 // Line layer doesn't support label
2350 // Q3D.VectorLayer.prototype.buildLabels.call(this, features);
2351};
2352
2353
2354/*
2355Q3D.PolygonLayer --> Q3D.VectorLayer
2356*/
2357Q3D.PolygonLayer = function () {
2358 Q3D.VectorLayer.call(this);
2359 this.type = Q3D.LayerType.Polygon;
2360
2361 // for overlay
2362 this.borderVisible = true;
2363 this.sideVisible = true;
2364};
2365
2366Q3D.PolygonLayer.prototype = Object.create(Q3D.VectorLayer.prototype);
2367Q3D.PolygonLayer.prototype.constructor = Q3D.PolygonLayer;
2368
2369Q3D.PolygonLayer.prototype.loadJSONObject = function (jsonObject, scene) {
2370 Q3D.VectorLayer.prototype.loadJSONObject.call(this, jsonObject, scene);
2371};
2372
2373Q3D.PolygonLayer.prototype.build = function (features) {
2374 var createObject,
2375 materials = this.materials,
2376 sceneData = this.sceneData;
2377
2378 if (this.properties.objType == this._lastObjType && this._createObject !== undefined) {
2379 createObject = this._createObject;
2380 }
2381 else if (this.properties.objType == "Extruded") {
2382 var createSubObject = function (f, polygon, z) {
2383 var i, l, j, m;
2384
2385 var shape = new THREE.Shape(Q3D.Utils.arrayToVec2Array(polygon[0]));
2386 for (i = 1, l = polygon.length; i < l; i++) {
2387 shape.holes.push(new THREE.Path(Q3D.Utils.arrayToVec2Array(polygon[i])));
2388 }
2389
2390 // extruded geometry
2391 var geom = new THREE.ExtrudeGeometry(shape, {bevelEnabled: false, amount: f.geom.h});
2392 var mesh = new THREE.Mesh(geom, materials.mat(f.mat.face));
2393 mesh.position.z = z;
2394
2395 if (f.mat.border !== undefined) {
2396 // border
2397 var border, pt, pts, zFunc = function (x, y) { return 0; };
2398
2399 for (i = 0, l = polygon.length; i < l; i++) {
2400 pts = Q3D.Utils.arrayToVec3Array(polygon[i], zFunc);
2401
2402 geom = new THREE.Geometry();
2403 geom.vertices = pts;
2404
2405 border = new THREE.Line(geom, materials.mat(f.mat.border));
2406 mesh.add(border);
2407
2408 border = new THREE.Line(geom, materials.mat(f.mat.border));
2409 border.position.z = f.geom.h;
2410 mesh.add(border);
2411
2412 // vertical lines
2413 for (j = 0, m = geom.vertices.length - 1; j < m; j++) {
2414 pt = pts[j];
2415
2416 geom = new THREE.Geometry();
2417 geom.vertices.push(pt, new THREE.Vector3(pt.x, pt.y, pt.z + f.geom.h));
2418 border = new THREE.Line(geom, materials.mat(f.mat.border));
2419 mesh.add(border);
2420 }
2421 }
2422 }
2423 return mesh;
2424 };
2425
2426 createObject = function (f) {
2427 if (f.geom.polygons.length == 1) return createSubObject(f, f.geom.polygons[0], f.geom.zs[0]);
2428
2429 var group = new THREE.Group();
2430 for (var i = 0, l = f.geom.polygons.length; i < l; i++) {
2431 group.add(createSubObject(f, f.geom.polygons[i], f.geom.zs[i]));
2432 }
2433 return group;
2434 };
2435 }
2436 else { // this.objType == "Overlay"
2437 var relativeToDEM = (this.am == "relative"), // altitude mode
2438 sbRelativeToDEM = (this.sbm == "relative"), // altitude mode of bottom height of side
2439 dem = scene.mapLayers[0], // TODO: referenced DEM layer
2440 z0 = sceneData.zShift * sceneData.zScale;
2441
2442 createObject = function (f) {
2443 var polygons = (relativeToDEM) ? (f.geom.split_polygons || []) : f.geom.polygons;
2444
2445 var zFunc;
2446 if (relativeToDEM) zFunc = function (x, y) { return dem.getZ(x, y) + f.geom.h; };
2447 else zFunc = function (x, y) { return z0 + f.geom.h; };
2448
2449 var geom = Q3D.Utils.createOverlayGeometry(f.geom.triangles, polygons, zFunc);
2450
2451 // set UVs
2452 if (materials[f.mat].i !== undefined) Q3D.Utils.setGeometryUVs(geom, sceneData.width, sceneData.height);
2453 // TODO: materials.get(f.mat).origProp.i
2454
2455 var mesh = new THREE.Mesh(geom, materials.mat(f.mat));
2456
2457 // TODO: f.mb and f.ms
2458 if (f.mb === undefined && f.ms === undefined) return mesh;
2459
2460 // borders and sides
2461 var bzFunc, geom, vertices;
2462 if (sbRelativeToDEM) bzFunc = function (x, y) { return dem.getZ(x, y) + f.geom.sb; };
2463 else bzFunc = function (x, y) { return z0 + f.geom.sb; };
2464
2465 for (var i = 0, l = f.geom.polygons.length; i < l; i++) {
2466 var polygon = f.geom.polygons[i];
2467 for (var j = 0, m = polygon.length; j < m; j++) {
2468 if (relativeToDEM || sbRelativeToDEM) {
2469 vertices = dem.segmentizeLineString(polygon[j], zFunc);
2470 }
2471 else {
2472 vertices = Q3D.Utils.arrayToVec3Array(polygon[j], zFunc);
2473 }
2474
2475 // TODO
2476 if (f.mb) {
2477 geom = new THREE.Geometry();
2478 geom.vertices = vertices;
2479 mesh.add(new THREE.Line(geom, materials.mat(f.mb)));
2480 }
2481
2482 if (f.ms) {
2483 geom = Q3D.Utils.createWallGeometry(vertices, bzFunc);
2484 mesh.add(new THREE.Mesh(geom, materials.mat(f.ms)));
2485 }
2486 }
2487 }
2488 // TODO: mesh.updateWorldMatrix();
2489 return mesh;
2490 };
2491 }
2492
2493 // each feature in this layer
2494 var f, obj;
2495 for (var i = 0, l = features.length; i < l; i++) {
2496 f = features[i];
2497 obj = createObject(f);
2498 //obj.userData.featureId = i;
2499 obj.userData.properties = f.prop;
2500 this.addObject(obj);
2501 }
2502
2503 this._lastObjType = this.properties.objType;
2504 this._createObject = createObject;
2505};
2506
2507// TODO:
2508Q3D.PolygonLayer.prototype.buildLabels = function (features) {
2509 var zFunc, getPointsFunc = function (f) { return f.geom.centroids; };
2510 var relativeToDEM = (this.am == "relative"); // altitude mode
2511 if (relativeToDEM) {
2512 var dem = this.scene.mapLayers[0]; // TODO: referenced dem
2513 zFunc = dem.getZ.bind(dem);
2514 }
2515
2516 Q3D.VectorLayer.prototype.buildLabels.call(this, features, getPointsFunc, zFunc);
2517};
2518
2519Q3D.PolygonLayer.prototype.setBorderVisibility = function (visible) {
2520 if (this.properties.objType != "Overlay") return;
2521
2522 this.objectGroup.children.forEach(function (parent) {
2523 for (var i = 0, l = parent.children.length; i < l; i++) {
2524 var obj = parent.children[i];
2525 if (obj instanceof THREE.Line) obj.visible = visible;
2526 }
2527 });
2528 this.borderVisible = visible;
2529};
2530
2531Q3D.PolygonLayer.prototype.setSideVisibility = function (visible) {
2532 if (this.properties.objType != "Overlay") return;
2533
2534 this.objectGroup.children.forEach(function (parent) {
2535 for (var i = 0, l = parent.children.length; i < l; i++) {
2536 var obj = parent.children[i];
2537 if (obj instanceof THREE.Mesh) obj.visible = visible;
2538 }
2539 });
2540 this.sideVisible = visible;
2541};
2542
2543
2544// Q3D.ModelBuilder
2545Q3D.ModelBuilder = {};
2546Q3D.ModelBuilder._loaders = {};
2547
2548
2549/*
2550Q3D.ModelBuilder.Base
2551*/
2552Q3D.ModelBuilder.Base = function (scene, obj) {
2553 this.scene = scene;
2554 this.features = [];
2555 this._objects = {};
2556
2557 this.loaded = false;
2558};
2559
2560Q3D.ModelBuilder.Base.prototype = {
2561
2562 constructor: Q3D.ModelBuilder.Base,
2563
2564 addFeature: function (layerId, featureId) {
2565 this.features.push({layerId: layerId, featureId: featureId});
2566 this.buildObjects();
2567 },
2568
2569 buildObjects: function () {
2570 if (!this.loaded) return;
2571
2572 var deg2rad = Math.PI / 180,
2573 m = new THREE.Matrix4();
2574
2575 // TODO: f, geom
2576 this.features.forEach(function (fet) {
2577 var layer = this.scene.mapLayers[fet.layerId],
2578 f = layer.f[fet.featureId];
2579
2580 for (var i = 0, l = f.pts.length; i < l; i++) {
2581 var pt = f.pts[i],
2582 mesh = this.cloneObject(fet.layerId);
2583
2584 // rotation
2585 if (f.rotateX) mesh.applyMatrix(m.makeRotationX(f.rotateX * deg2rad));
2586 if (f.rotateY) mesh.applyMatrix(m.makeRotationY(f.rotateY * deg2rad));
2587 if (f.rotateZ) mesh.applyMatrix(m.makeRotationZ(f.rotateZ * deg2rad));
2588
2589 // scale and position
2590 if (f.scale !== undefined) mesh.scale.set(f.scale, f.scale, f.scale);
2591 mesh.position.set(pt[0], pt[1], pt[2]);
2592
2593 mesh.userData.featureId = fet.featureId;
2594
2595 layer.addObject(mesh);
2596 }
2597 }, this);
2598
2599 this.features = [];
2600 },
2601
2602 cloneObject: function (layerId) {
2603 if (this.object === undefined) return null;
2604
2605 // if there is already the object for the layer, return a clone of the object
2606 if (layerId in this._objects) return this._objects[layerId].clone();
2607
2608 var layer = this.scene.mapLayers[layerId];
2609
2610 // clone the original object
2611 var object = this.object.clone();
2612
2613 if (Object.keys(this._objects).length) {
2614 // if this is not the first layer which uses this model, clone materials
2615 // and append cloned materials to material list of the layer
2616 object.traverse(function (obj) {
2617 if (obj instanceof THREE.Mesh === false) return;
2618 obj.material = obj.material.clone();
2619 layer.materials.add(obj.material);
2620 });
2621 }
2622 else {
2623 // if this is the first, append original materials to material list of the layer
2624 object.traverse(function (obj) {
2625 if (obj instanceof THREE.Mesh === false) return;
2626 layer.materials.add(obj.material);
2627 });
2628 }
2629 this._objects[layerId] = object;
2630
2631 // as properties of the object will be changed, clone the object to keep the original for the layer
2632 return object.clone();
2633 },
2634
2635 onLoad: function (object) {
2636 this.object = object;
2637 this.loaded = true;
2638 this.buildObjects();
2639 }
2640};
2641
2642
2643/*
2644Q3D.ModelBuilder.JSON --> Q3D.ModelBuilder.Base
2645
2646 load JSON data and build JSON models
2647*/
2648Q3D.ModelBuilder.JSON = function (scene, model) {
2649 Q3D.ModelBuilder.Base.call(this, scene, model);
2650
2651 var loaders = Q3D.ModelBuilder._loaders;
2652 if (loaders.jsonLoader === undefined) loaders.jsonLoader = new THREE.JSONLoader(true);
2653 this.loader = loaders.jsonLoader;
2654
2655 if (model.src !== undefined) {
2656 this.loader.load(model.src, this.onLoad.bind(this));
2657 }
2658 else if (model.data) {
2659 var result = this.loader.parse(JSON.parse(model.data));
2660 this.onLoad(result.geometry, result.materials);
2661 }
2662};
2663
2664Q3D.ModelBuilder.JSON.prototype = Object.create(Q3D.ModelBuilder.Base.prototype);
2665Q3D.ModelBuilder.JSON.prototype.constructor = Q3D.ModelBuilder.JSON;
2666
2667Q3D.ModelBuilder.JSON.prototype.onLoad = function (geometry, materials) {
2668 this.geometry = geometry;
2669 this.material = new THREE.MeshFaceMaterial(materials);
2670 Q3D.ModelBuilder.Base.prototype.onLoad.call(this, new THREE.Mesh(this.geometry, this.material));
2671};
2672
2673
2674/*
2675Q3D.ModelBuilder.JSONObject --> Q3D.ModelBuilder.Base
2676*/
2677Q3D.ModelBuilder.JSONObject = function (scene, model) {
2678 Q3D.ModelBuilder.Base.call(this, scene, model);
2679
2680 var loaders = Q3D.ModelBuilder._loaders;
2681 if (loaders.jsonObjectLoader === undefined) loaders.jsonObjectLoader = new THREE.ObjectLoader();
2682 this.loader = loaders.jsonObjectLoader;
2683
2684 if (model.src !== undefined) {
2685 this.loader.load(model.src, this.onLoad.bind(this));
2686 }
2687 else if (model.data) {
2688 this.onLoad(this.loader.parse(JSON.parse(model.data)));
2689 }
2690};
2691
2692Q3D.ModelBuilder.JSONObject.prototype = Object.create(Q3D.ModelBuilder.Base.prototype);
2693Q3D.ModelBuilder.JSONObject.prototype.constructor = Q3D.ModelBuilder.JSONObject;
2694
2695
2696/*
2697Q3D.ModelBuilder.COLLADA --> Q3D.ModelBuilder.Base
2698*/
2699Q3D.ModelBuilder.COLLADA = function (scene, model) {
2700 Q3D.ModelBuilder.Base.call(this, scene, model);
2701
2702 var loaders = Q3D.ModelBuilder._loaders;
2703 if (loaders.colladaLoader === undefined) loaders.colladaLoader = new THREE.ColladaLoader();
2704 this.loader = loaders.colladaLoader;
2705
2706 if (model.src !== undefined) {
2707 this.loader.load(model.src, this.onLoad.bind(this));
2708 }
2709 else if (model.data) {
2710 var xmlParser = new DOMParser(),
2711 responseXML = xmlParser.parseFromString(model.data, "application/xml"),
2712 url = "./";
2713 this.onLoad(this.loader.parse(responseXML, undefined, url));
2714 }
2715};
2716
2717Q3D.ModelBuilder.COLLADA.prototype = Object.create(Q3D.ModelBuilder.Base.prototype);
2718Q3D.ModelBuilder.COLLADA.prototype.constructor = Q3D.ModelBuilder.COLLADA;
2719
2720Q3D.ModelBuilder.COLLADA.prototype.onLoad = function (collada) {
2721 this.collada = collada;
2722 Q3D.ModelBuilder.Base.prototype.onLoad.call(this, collada.scene);
2723};
2724
2725
2726// Q3D.Utils - Utilities
2727Q3D.Utils = {};
2728
2729// [NOT USED]
2730Q3D.Utils.setObjectVisibility = function (object, visible) {
2731 object.traverse(function (obj) {
2732 obj.visible = visible;
2733 });
2734};
2735
2736// TODO: REMOVE
2737Q3D.Utils.loadTexture = function (url, eventDispatcher) {
2738 return THREE.ImageUtils.loadTexture(url, undefined, function (tex) {
2739 if (eventDispatcher) eventDispatcher.dispatchEvent({type: "renderRequest"});
2740 });
2741};
2742
2743// TODO: remove
2744Q3D.Utils.loadTextureBase64 = function (imageData, eventDispatcher) {
2745 var texture, image = new Image();
2746 image.onload = function () {
2747 texture.needsUpdate = true;
2748 if (eventDispatcher && !Q3D.Options.exportMode) eventDispatcher.dispatchEvent({type: "renderRequest"});
2749 };
2750 image.src = imageData;
2751 texture = new THREE.Texture(image);
2752 return texture;
2753};
2754
2755// Put a stick to given position (for debug)
2756Q3D.Utils.putStick = function (x, y, zFunc, h) {
2757 if (Q3D.Utils._stick_mat === undefined) Q3D.Utils._stick_mat = new THREE.LineBasicMaterial({color: 0xff0000});
2758 if (h === undefined) h = 0.2;
2759 if (zFunc === undefined) {
2760 zFunc = function (x, y) { return Q3D.application.scene.mapLayers[0].getZ(x, y); }
2761 }
2762 var z = zFunc(x, y);
2763 var geom = new THREE.Geometry();
2764 geom.vertices.push(new THREE.Vector3(x, y, z + h), new THREE.Vector3(x, y, z));
2765 var stick = new THREE.Line(geom, Q3D.Utils._stick_mat);
2766 Q3D.application.scene.add(stick);
2767};
2768
2769// convert latitude and longitude in degrees to the following format
2770// Ndd°mm′ss.ss″, Eddd°mm′ss.ss″
2771Q3D.Utils.convertToDMS = function (lat, lon) {
2772 function toDMS (degrees) {
2773 var deg = Math.floor(degrees),
2774 m = (degrees - deg) * 60,
2775 min = Math.floor(m),
2776 sec = (m - min) * 60;
2777 return deg + "°" + ("0" + min).slice(-2) + "′" + ((sec < 10) ? "0" : "") + sec.toFixed(2) + "″";
2778 }
2779
2780 return ((lat < 0) ? "S" : "N") + toDMS(Math.abs(lat)) + ", " +
2781 ((lon < 0) ? "W" : "E") + toDMS(Math.abs(lon));
2782};
2783
2784Q3D.Utils.createWallGeometry = function (vertices, bzFunc) {
2785 var geom, pt, v;
2786 if (Q3D.Options.exportMode) {
2787 geom = new THREE.PlaneGeometry(0, 0, vertices.length - 1, 1);
2788 v = geom.vertices;
2789 for (var i = 0, l = vertices.length; i < l; i++) {
2790 pt = vertices[i];
2791 v[i].x = v[i + l].x = pt.x;
2792 v[i].y = v[i + l].y = pt.y;
2793 v[i].z = bzFunc(pt.x, pt.y);
2794 v[i + l].z = pt.z;
2795 }
2796 }
2797 else {
2798 geom = new THREE.PlaneBufferGeometry(0, 0, vertices.length - 1, 1);
2799 v = geom.attributes.position.array;
2800 for (var i = 0, k = 0, l = vertices.length, l3 = l * 3; i < l; i++, k+=3) {
2801 pt = vertices[i];
2802 v[k] = v[k + l3] = pt.x;
2803 v[k + 1] = v[k + l3 + 1] = pt.y;
2804 v[k + 2] = bzFunc(pt.x, pt.y);
2805 v[k + l3 + 2] = pt.z;
2806 }
2807 }
2808 geom.computeFaceNormals();
2809 geom.computeVertexNormals();
2810 return geom;
2811};
2812
2813Q3D.Utils.arrayToVec2Array = function (points) {
2814 var pt, pts = [];
2815 for (var i = 0, l = points.length; i < l; i++) {
2816 pt = points[i];
2817 pts.push(new THREE.Vector2(pt[0], pt[1]));
2818 }
2819 return pts;
2820};
2821
2822Q3D.Utils.arrayToVec3Array = function (points, zFunc) {
2823 if (zFunc === undefined) zFunc = function () { return 0; };
2824 var pt, pts = [];
2825 for (var i = 0, l = points.length; i < l; i++) {
2826 pt = points[i];
2827 pts.push(new THREE.Vector3(pt[0], pt[1], zFunc(pt[0], pt[1])));
2828 }
2829 return pts;
2830};
2831
2832Q3D.Utils.arrayToFace3Array = function (faces) {
2833 var f, fs = [];
2834 for (var i = 0, l = faces.length; i < l; i++) {
2835 f = faces[i];
2836 fs.push(new THREE.Face3(f[0], f[1], f[2]));
2837 }
2838 return fs;
2839};
2840
2841Q3D.Utils.createOverlayGeometry = function (triangles, polygons, zFunc) {
2842 var geom = new THREE.Geometry();
2843
2844 // vertices and faces
2845 if (triangles !== undefined) {
2846 geom.vertices = Q3D.Utils.arrayToVec3Array(triangles.v, zFunc);
2847 geom.faces = Q3D.Utils.arrayToFace3Array(triangles.f);
2848 }
2849
2850 // split-polygons
2851 for (var i = 0, l = polygons.length; i < l; i++) {
2852 var polygon = polygons[i];
2853 var poly_geom = new THREE.Geometry(),
2854 holes = [];
2855
2856 // make Vector3 arrays
2857 poly_geom.vertices = Q3D.Utils.arrayToVec3Array(polygon[0], zFunc);
2858 for (var j = 1, m = polygon.length; j < m; j++) {
2859 holes.push(Q3D.Utils.arrayToVec3Array(polygon[j], zFunc));
2860 }
2861
2862 // triangulate polygon
2863 var faces = THREE.Shape.Utils.triangulateShape(poly_geom.vertices, holes);
2864
2865 // append points of holes to vertices
2866 for (var j = 0, m = holes.length; j < m; j++) {
2867 Array.prototype.push.apply(poly_geom.vertices, holes[j]);
2868 }
2869
2870 // element of faces is [index1, index2, index3]
2871 poly_geom.faces = Q3D.Utils.arrayToFace3Array(faces);
2872
2873 geom.merge(poly_geom);
2874 }
2875 geom.mergeVertices();
2876 geom.computeFaceNormals();
2877 geom.computeVertexNormals();
2878 return geom;
2879};
2880
2881Q3D.Utils.setGeometryUVs = function (geom, base_width, base_height) {
2882 var face, v, uvs = [];
2883 for (var i = 0, l = geom.vertices.length; i < l; i++) {
2884 v = geom.vertices[i];
2885 uvs.push(new THREE.Vector2(v.x / base_width + 0.5, v.y / base_height + 0.5));
2886 }
2887
2888 geom.faceVertexUvs[0] = [];
2889 for (var i = 0, l = geom.faces.length; i < l; i++) {
2890 face = geom.faces[i];
2891 geom.faceVertexUvs[0].push([uvs[face.a], uvs[face.b], uvs[face.c]]);
2892 }
2893};