1 /*
  2  * viewModel.js
  3  *
  4  * Sweet Home 3D, Copyright (c) 2016 Emmanuel PUYBARET / eTeks <info@eteks.com>
  5  *
  6  * This program is free software; you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation; either version 2 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * This program is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with this program; if not, write to the Free Software
 18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19  */
 20 
 21 /**
 22  * Loads the model at given URL and displays it in a 3D canvas.
 23  * @param {string} modelUrl the URL of the model to load
 24  * @param {string} modelRotation the 9 values of a 3x3 matrix 
 25  */
 26 function viewModelInOverlay(modelUrl, modelRotation) {
 27     // Place canvas in the middle of the screen
 28     var windowWidth  = window.innerWidth;
 29     var windowHeight = window.innerHeight;
 30     var pageWidth = document.documentElement.clientWidth;
 31     var pageHeight = document.documentElement.clientHeight;
 32     var bodyElement = document.getElementsByTagName("body").item(0);
 33     if (bodyElement && bodyElement.scrollWidth) {
 34       if (bodyElement.scrollWidth > pageWidth) {
 35         pageWidth = bodyElement.scrollWidth;
 36       }
 37       if (bodyElement.scrollHeight > pageHeight) {
 38         pageHeight = bodyElement.scrollHeight;
 39       }
 40     }
 41     var pageXOffset = window.pageXOffset ? window.pageXOffset : 0;
 42     var pageYOffset = window.pageYOffset ? window.pageYOffset : 0;
 43 
 44     var canvas = document.getElementById("canvas3D");
 45     if (!canvas) {
 46       createModel3DOverlay();
 47       canvas = document.getElementById("canvas3D");
 48     }
 49 
 50     var overlayDiv = document.getElementById("modelViewerOverlay");
 51     overlayDiv.style.height = Math.max(pageHeight, windowHeight) + "px";
 52     overlayDiv.style.width = pageWidth <= windowWidth
 53         ? "100%"
 54         : pageWidth + "px";
 55     overlayDiv.style.display = "block";
 56 
 57     var canvasSize = Math.min(800, windowWidth, windowHeight);
 58     canvasSize *= 0.90;
 59     canvas.width = canvasSize;
 60     canvas.style.width = canvas.width + "px";
 61     canvas.height = canvasSize;
 62     canvas.style.height = canvas.height + "px";
 63     var canvasLeft = pageXOffset + (windowWidth - canvasSize - 10) / 2;
 64     canvas.style.left = canvasLeft + "px";
 65     var canvasTop = pageYOffset + (windowHeight - canvasSize - 10) / 2;
 66     canvas.style.top = canvasTop + "px";
 67         
 68     var closeButtonImage = document.getElementById("modelViewerCloseButton");
 69     closeButtonImage.style.left = (canvasLeft + canvasSize - 5) + "px";
 70     closeButtonImage.style.top = (canvasTop - 10) + "px";
 71     
 72     var progressDiv = document.getElementById("modelViewerProgressDiv");
 73     progressDiv.style.left = (canvasLeft + (canvasSize - 300) / 2) + "px";
 74     progressDiv.style.top = (canvasTop + (canvasSize - 50) / 2) + "px";
 75     progressDiv.style.visibility = "visible";
 76     document.getElementById("modelViewerProgress").value = 0;
 77       
 78     // Show model in canvas
 79     try {
 80       if (canvas.modelPreviewComponent === undefined) {
 81         canvas.modelPreviewComponent = new ModelPreviewComponent("canvas3D", true);
 82       }
 83       var modelPreviewComponent = canvas.modelPreviewComponent;
 84       var modelRotationMatrix = null;
 85       if (modelRotation) {
 86         var values = modelRotation.split(/\s+/);
 87         modelRotationMatrix = [[parseFloat(values [0]), parseFloat(values [1]), parseFloat(values [2])], 
 88                                [parseFloat(values [3]), parseFloat(values [4]), parseFloat(values [5])], 
 89                                [parseFloat(values [6]), parseFloat(values [7]), parseFloat(values [8])]];
 90       }
 91       modelPreviewComponent.setModel(new URLContent(modelUrl), modelRotationMatrix,
 92           function(err) {
 93             console.log(err); 
 94             alert(err); 
 95           },
 96           function(part, info, percentage) {
 97             var progress = document.getElementById("modelViewerProgress");
 98             if (part === ModelLoader.READING_MODEL) {
 99               progress.value = percentage * 100;
100               info = info.substring(info.lastIndexOf('/') + 1);
101             } else if (part === ModelLoader.PARSING_MODEL) {
102               progress.value = 210 + percentage * 100;
103             } else if (part === ModelLoader.BUILDING_MODEL) {
104               progress.value = 310 + percentage * 100;
105             } else if (part === ModelLoader.BINDING_MODEL) {
106               progress.value = 410 + percentage * 50;
107             }
108             
109             var progressLabel = document.getElementById("modelViewerProgressLabel");
110             if (part === ModelLoader.BUILDING_MODEL && percentage === 1) {
111               progressLabel.innerHTML = "Preparing display...";
112             } else {
113               progressLabel.innerHTML = (percentage ? Math.floor(percentage * 100) + "% " : "") 
114                   + part + " " + info;
115             }
116             
117             if (part === ModelLoader.BINDING_MODEL && percentage === 1) {
118               document.getElementById("modelViewerProgressDiv").style.visibility = "hidden";
119               modelPreviewComponent.startRotationAnimation();
120             }
121           });
122     } catch (ex) {
123       hideModel3DOverlay();
124       if (ex == "No WebGL") {
125         alert("Sorry, your browser doesn't support WebGL.");
126       } else {
127         alert("Error: " + ex);
128       }
129     }
130 }
131 
132 /**
133  * Creates an overlay containing a canvas with <code>modelViewerOverlay</code> as id.
134  * @private
135  */
136 function createModel3DOverlay() {
137   var overlayDiv = document.createElement("div");
138   overlayDiv.setAttribute("id", "modelViewerOverlay");
139   overlayDiv.style.display = "none";
140   overlayDiv.style.position = "absolute";
141   overlayDiv.style.left = "0";
142   overlayDiv.style.top = "0";
143   overlayDiv.style.zIndex = "100";
144   overlayDiv.style.background = "rgba(127, 127, 127, .5)";
145     
146   var bodyElement = document.getElementsByTagName("body").item(0);
147   bodyElement.insertBefore(overlayDiv, bodyElement.firstChild);
148 
149   var modelViewDiv = document.createElement("div");
150   modelViewDiv.innerHTML =
151         '<canvas id="canvas3D" style="background-color: #CCCCCC; border: 1px solid gray; position: absolute; touch-action: none"></canvas>'
152       + '<div id="modelViewerProgressDiv" style="position:absolute; width: 300px;">'
153       + '  <progress id="modelViewerProgress" value="0" max="460" style="width: 300px;"></progress>'
154       + '  <label id="modelViewerProgressLabel" style="margin-top: 2px; font-family: Sans-serif; margin-left: 10px; margin-right: 0px; display: block;"></label>'
155       + '</div>';  
156   
157   // Create close button image
158   var closeButtonImage = new Image();
159   closeButtonImage.src = ZIPTools.getScriptFolder() + "close.png";
160   closeButtonImage.id = "modelViewerCloseButton";
161   closeButtonImage.style.position = "absolute";
162 
163   overlayDiv.appendChild(modelViewDiv);  
164   overlayDiv.appendChild(closeButtonImage);
165  
166   var hide = function(ev) {
167       hideModel3DOverlay("canvas3D");
168     }; 
169   document.addEventListener("keydown", 
170       function(ev) {
171         if (ev.keyCode === 27) {
172           hide();
173         }
174       });
175   closeButtonImage.addEventListener("click", hide);
176   var mouseActionsListener = {
177       mousePressed : function(ev) {
178         mouseActionsListener.mousePressedInCanvas = true;
179       },
180       mouseClicked : function(ev) {
181         if (mouseActionsListener.mousePressedInCanvas) {
182           delete mouseActionsListener.mousePressedInCanvas;
183           hide();
184         }
185       }
186     }
187   overlayDiv.addEventListener("mousedown", mouseActionsListener.mousePressed); 
188   overlayDiv.addEventListener("click", mouseActionsListener.mouseClicked); 
189   overlayDiv.addEventListener("touchmove", 
190       function(ev) {
191         ev.preventDefault();
192       });
193 }
194 
195 /**
196  * Hides the overlay and clears resources.
197  * @private
198  */
199 function hideModel3DOverlay() {
200   document.getElementById("modelViewerOverlay").style.display = "none";
201   var modelPreviewComponent = document.getElementById("canvas3D").modelPreviewComponent;
202   if (modelPreviewComponent) {
203     modelPreviewComponent.clear();
204     ModelManager.getInstance().clear();
205     ZIPTools.clear();
206   }
207 }
208 
209 /**
210  * Replaces the href attribute of the element matching the given regular expression
211  * by a call to <code>viewModel3D</code> with the link in parameter.
212  * @param linkRegex
213  * @ignore
214  */
215 function bindAnchorsToModel3DViewer(linkRegex) {
216   var anchors = document.getElementsByTagName("a");
217   for (var i = 0; i < anchors.length; i++) {
218     var anchor = anchors[i];
219     var url = anchor.getAttribute("href");
220     if (url !== null && url.match(linkRegex)) {
221       anchor.onclick = function () {
222           viewModelInOverlay(this.getAttribute("href"), this.getAttribute("data-model-rotation")); 
223           return false;
224         };
225     }
226   }
227 }
228 
229