ExtensionInterface = function (app) { this.app = app; }; ExtensionInterface.prototype.GetButtonsDiv = function () { return this.app.extensionButtons.GetButtonsDiv (); }; ExtensionInterface.prototype.GetModelJson = function () { return this.app.viewer.GetJsonData (); }; ImporterApp = function () { this.canvas = null; this.viewer = null; this.fileNames = null; this.inGenerate = false; this.meshesGroup = null; this.materialMenuItems = null; this.meshMenuItems = null; this.extensions = []; this.importerButtons = null; this.extensionButtons = null; this.introControl = null; this.floatingDialog = null; this.isMobile = null; this.readyForTest = null; }; ImporterApp.prototype.Init = function () { if (!JSM.IsWebGLEnabled () || !JSM.IsFileApiEnabled ()) { while (document.body.lastChild) { document.body.removeChild (document.body.lastChild); } var div = $('
').addClass ('nosupport').appendTo ($('body')); div.html ([ '
', this.GetWelcomeText (), '
You need a browser which supports the following technologies: WebGL, WebGLRenderingContext, File, FileReader, FileList, Blob, URL.
', '
' ].join ('')); return; } var myThis = this; var top = $('#top'); this.importerButtons = new ImporterButtons (top); this.importerButtons.AddLogo ('Online 3D Viewer v 0.6.6'); this.importerButtons.AddButton ('images/openfile.png', 'Open File', function () { myThis.OpenFile (); }); this.importerButtons.AddButton ('images/fitinwindow.png', 'Fit In Window', function () { myThis.FitInWindow (); }); this.importerButtons.AddToggleButton ('images/fixup.png', 'images/fixupgray.png', 'Enable/Disable Fixed Up Vector', function () { myThis.SetFixUp (); }); this.importerButtons.AddButton ('images/top.png', 'Set Up Vector (Z)', function () { myThis.SetNamedView ('z'); }); this.importerButtons.AddButton ('images/bottom.png', 'Set Up Vector (-Z)', function () { myThis.SetNamedView ('-z'); }); this.importerButtons.AddButton ('images/front.png', 'Set Up Vector (Y)', function () { myThis.SetNamedView ('y'); }); this.importerButtons.AddButton ('images/back.png', 'Set Up Vector (-Y)', function () { myThis.SetNamedView ('-y'); }); this.importerButtons.AddButton ('images/left.png', 'Set Up Vector (X)', function () { myThis.SetNamedView ('x'); }); this.importerButtons.AddButton ('images/right.png', 'Set Up Vector (-X)', function () { myThis.SetNamedView ('-x'); }); this.extensionButtons = new ExtensionButtons (top); this.introControl = new FloatingControl (); this.floatingDialog = new FloatingDialog (); var match = window.matchMedia ('(max-device-width : 600px)'); this.isMobile = match.matches; window.addEventListener ('resize', this.Resize.bind (this), false); this.Resize (); var canvasName = 'modelcanvas'; this.canvas = $('#' + canvasName); this.RegisterCanvasClick (); window.addEventListener ('dragover', this.DragOver.bind (this), false); window.addEventListener ('drop', this.Drop.bind (this), false); var fileInput = document.getElementById ('file'); fileInput.addEventListener ('change', this.FileSelected.bind (this), false); window.onhashchange = this.LoadFilesFromHash.bind (this); var hasHashModel = this.LoadFilesFromHash (); if (!hasHashModel && !this.isMobile) { this.ShowIntroControl (); } this.viewer = new ImporterViewer (); this.viewer.Init (canvasName); }; ImporterApp.prototype.ClearReadyForTest = function () { if (this.readyForTest !== null) { this.readyForTest.remove (); this.readyForTest = null; } }; ImporterApp.prototype.SetReadyForTest = function () { this.readyForTest = $('
').attr ('id', 'readyfortest').hide ().appendTo ($('body')); }; ImporterApp.prototype.AddExtension = function (extension) { if (!extension.IsEnabled ()) { return; } var extInterface = new ExtensionInterface (this); extension.Init (extInterface); }; ImporterApp.prototype.ShowIntroControl = function () { var dialogText = [ '
', this.GetWelcomeText (), '
', ].join (''); this.introControl.Open ({ parent : this.canvas, text : dialogText }); this.Resize (); }; ImporterApp.prototype.HideIntroControl = function () { this.introControl.Close (); this.Resize (); }; ImporterApp.prototype.GetWelcomeText = function () { var welcomeText = [ '
Welcome to Online 3D Viewer!
', '
Here you can view your local 3D models online. You have three ways to open a file. Use the open button above to select files, simply drag and drop files to this browser window, or define the url of the files as location hash.
', '
Supported formats: 3ds, obj, stl, off.
', '
Powered by Three.js and JSModeler.
', '
', ].join (''); return welcomeText; }; ImporterApp.prototype.Resize = function () { function SetWidth (elem, value) { elem.width = value; elem.style.width = value + 'px'; } function SetHeight (elem, value) { elem.height = value; elem.style.height = value + 'px'; } var top = document.getElementById ('top'); var left = document.getElementById ('left'); var canvas = document.getElementById ('modelcanvas'); var height = document.body.clientHeight - top.offsetHeight; SetHeight (left, height); SetHeight (canvas, height); SetWidth (canvas, document.body.clientWidth - left.offsetWidth); this.introControl.Resize (); this.floatingDialog.Resize (); }; ImporterApp.prototype.JsonLoaded = function (progressBar) { this.Generate (progressBar); }; ImporterApp.prototype.GenerateMenu = function () { function AddDefaultGroup (menu, name, id) { var group = menu.AddGroup (name, { id : id, openCloseButton : { title : 'Show/Hide ' + name } }); return group; } function AddInformation (infoGroup, jsonData) { var infoTable = new InfoTable (infoGroup.GetContentDiv ()); var materialCount = jsonData.materials.length; var vertexCount = 0; var triangleCount = 0; var i, j, mesh, triangles; for (i = 0; i < jsonData.meshes.length; i++) { mesh = jsonData.meshes[i]; vertexCount += mesh.vertices.length / 3; for (j = 0; j < mesh.triangles.length; j++) { triangles = mesh.triangles[j]; triangleCount += triangles.parameters.length / 9; } } infoTable.AddRow ('Material count', materialCount); infoTable.AddRow ('Vertex count', vertexCount); infoTable.AddRow ('Triangle count', triangleCount); } function AddMaterial (importerApp, importerMenu, materialsGroup, materialIndex, material) { var materialMenuItem = materialsGroup.AddSubItem (material.name, { openCloseButton : { title : 'Show/Hide Information', onOpen : function (contentDiv, material) { contentDiv.empty (); var materialButtons = $('
').addClass ('submenubuttons').appendTo (contentDiv); var highlightButton = $('').addClass ('submenubutton').attr ('src', 'images/highlightmesh.png').attr ('title', 'Highlight Meshes By Material').appendTo (materialButtons); highlightButton.click (function () { importerApp.HighlightMeshesByMaterial (materialIndex); }); var fitInWindowButton = $('').addClass ('submenubutton').attr ('src', 'images/fitinwindowsmall.png').attr ('title', 'Fit Meshes In Window By Material').appendTo (materialButtons); fitInWindowButton.click (function () { importerApp.FitMeshesByMaterialInWindow (materialIndex); }); var table = new InfoTable (contentDiv); table.AddColorRow ('Ambient', material.ambient); table.AddColorRow ('Diffuse', material.diffuse); table.AddColorRow ('Specular', material.specular); table.AddRow ('Shininess', material.shininess.toFixed (2)); table.AddRow ('Opacity', material.opacity.toFixed (2)); }, userData : material } }); return materialMenuItem; } function AddMesh (importerApp, importerMenu, meshesGroup, mesh, meshIndex) { function AddMeshButtons (importerApp, contentDiv, meshName, meshIndex) { function CopyToClipboard (text) { var input = document.createElement ('input'); input.style.position = 'absolute'; input.style.left = '0'; input.style.top = '0'; input.setAttribute ('value', text); document.body.appendChild (input); input.select (); document.execCommand ('copy'); document.body.removeChild(input); } var meshButtons = $('
').addClass ('submenubuttons').appendTo (contentDiv); var fitInWindowButton = $('').addClass ('submenubutton').attr ('src', 'images/fitinwindowsmall.png').attr ('title', 'Fit Mesh In Window').appendTo (meshButtons); fitInWindowButton.click (function () { importerApp.FitMeshInWindow (meshIndex); }); var highlightButton = $('').addClass ('submenubutton').attr ('src', 'images/highlightmesh.png').attr ('title', 'Highlight Mesh').appendTo (meshButtons); highlightButton.click (function () { importerApp.HighlightMesh (meshIndex); }); var copyNameToClipboardButton = $('').addClass ('submenubutton').attr ('src', 'images/copytoclipboard.png').attr ('title', 'Copy Mesh Name To Clipboard').appendTo (meshButtons); copyNameToClipboardButton.click (function () { CopyToClipboard (meshName); }); } var visibleImage = null; var meshMenuItem = meshesGroup.AddSubItem (mesh.name, { id : 'meshmenuitem-' + meshIndex.toString (), openCloseButton : { title : 'Show/Hide Details', onOpen : function (contentDiv, mesh) { contentDiv.empty (); AddMeshButtons (importerApp, contentDiv, mesh.name, meshIndex); var table = new InfoTable (contentDiv); var min = new JSM.Coord (JSM.Inf, JSM.Inf, JSM.Inf); var max = new JSM.Coord (-JSM.Inf, -JSM.Inf, -JSM.Inf); var i, vertex; for (i = 0; i < mesh.vertices.length; i = i + 3) { vertex = new JSM.Coord (mesh.vertices[i], mesh.vertices[i + 1], mesh.vertices[i + 2]); min.x = JSM.Minimum (min.x, vertex.x); min.y = JSM.Minimum (min.y, vertex.y); min.z = JSM.Minimum (min.z, vertex.z); max.x = JSM.Maximum (max.x, vertex.x); max.y = JSM.Maximum (max.y, vertex.y); max.z = JSM.Maximum (max.z, vertex.z); } table.AddRow ('X Size', (max.x - min.x).toFixed (2)); table.AddRow ('Y Size', (max.y - min.y).toFixed (2)); table.AddRow ('Z Size', (max.z - min.z).toFixed (2)); var triangleCount = 0; var triangles; for (i = 0; i < mesh.triangles.length; i++) { triangles = mesh.triangles[i]; triangleCount += triangles.parameters.length / 9; } table.AddRow ('Vertex count', mesh.vertices.length / 3); table.AddRow ('Triangle count', triangleCount); }, userData : mesh }, userButtons : [ { id : 'showhidemesh-' + meshIndex, title : 'Show/Hide Mesh', onCreate : function (image) { image.attr ('src', 'images/visible.png'); visibleImage = image; }, onClick : function (image, meshIndex) { importerApp.ShowHideMesh (meshIndex); }, onCtrlClick : function (image, meshIndex) { importerApp.IsolateMesh (meshIndex); }, userData : meshIndex } ] }); meshMenuItem.isVisible = true; meshMenuItem.visibleImage = visibleImage; return meshMenuItem; } var jsonData = this.viewer.GetJsonData (); var menu = $('#menu'); var importerMenu = new ImporterMenu (menu); var filesGroup = AddDefaultGroup (importerMenu, 'Files', 'filesmenuitem'); filesGroup.AddSubItem (this.fileNames.main); var i; for (i = 0; i < this.fileNames.requested.length; i++) { filesGroup.AddSubItem (this.fileNames.requested[i]); } if (this.fileNames.missing.length > 0) { var missingFilesGroup = AddDefaultGroup (importerMenu, 'Missing Files', 'missingfilesmenuitem'); for (i = 0; i < this.fileNames.missing.length; i++) { missingFilesGroup.AddSubItem (this.fileNames.missing[i]); } } var infoGroup = AddDefaultGroup (importerMenu, 'Information', 'informationmenuitem'); AddInformation (infoGroup, jsonData); this.materialMenuItems = []; var materialsGroup = AddDefaultGroup (importerMenu, 'Materials', 'materialsmenuitem'); var material, materialMenuItem; for (i = 0; i < jsonData.materials.length; i++) { material = jsonData.materials[i]; materialMenuItem = AddMaterial (this, importerMenu, materialsGroup, i, material); this.materialMenuItems.push (materialMenuItem); } this.meshesGroup = AddDefaultGroup (importerMenu, 'Meshes', 'meshesmenuitem'); this.meshMenuItems = []; var mesh, meshMenuItem; for (i = 0; i < jsonData.meshes.length; i++) { mesh = jsonData.meshes[i]; meshMenuItem = AddMesh (this, importerMenu, this.meshesGroup, mesh, i); this.meshMenuItems.push (meshMenuItem); } this.Resize (); }; ImporterApp.prototype.GenerateError = function (errorMessage) { this.viewer.RemoveMeshes (); var menu = $('#menu'); menu.empty (); this.floatingDialog.Open ({ title : 'Error', text : '
' + errorMessage + '
', buttons : [ { text : 'ok', callback : function (dialog) { dialog.Close (); } } ] }); }; ImporterApp.prototype.Generate = function (progressBar) { function ShowMeshes (importerApp, progressBar, merge) { importerApp.inGenerate = true; var environment = { onStart : function (taskCount) { progressBar.Init (taskCount); }, onProgress : function (currentTask) { progressBar.Step (currentTask + 1); }, onFinish : function () { importerApp.GenerateMenu (); importerApp.inGenerate = false; importerApp.SetReadyForTest (); importerApp.viewer.Draw (); } }; if (merge) { var jsonData = importerApp.viewer.GetJsonData (); importerApp.viewer.SetJsonData (JSM.MergeJsonDataMeshes (jsonData)); } importerApp.viewer.ShowAllMeshes (environment); } var jsonData = this.viewer.GetJsonData (); if (jsonData.materials.length === 0 || jsonData.meshes.length === 0) { this.GenerateError ('Failed to open file. Maybe something is wrong with your file.'); this.SetReadyForTest (); return; } var myThis = this; if (jsonData.meshes.length > 250) { this.floatingDialog.Open ({ title : 'Information', text : '
The model contains a large number of meshes. It can cause performance problems. Would you like to merge meshes?
', buttons : [ { text : 'yes', callback : function (dialog) { ShowMeshes (myThis, progressBar, true); dialog.Close (); } }, { text : 'no', callback : function (dialog) { ShowMeshes (myThis, progressBar, false); dialog.Close (); } } ] }); } else { ShowMeshes (myThis, progressBar, false); } }; ImporterApp.prototype.FitInWindow = function () { this.viewer.FitInWindow (); }; ImporterApp.prototype.FitMeshInWindow = function (meshIndex) { this.viewer.FitMeshInWindow (meshIndex); }; ImporterApp.prototype.FitMeshesByMaterialInWindow = function (materialIndex) { var meshIndices = this.viewer.GetMeshesByMaterial (materialIndex); if (meshIndices.length === 0) { return; } this.viewer.FitMeshesInWindow (meshIndices); }; ImporterApp.prototype.SetFixUp = function () { this.viewer.SetFixUp (); }; ImporterApp.prototype.SetNamedView = function (viewName) { this.viewer.SetNamedView (viewName); }; ImporterApp.prototype.SetView = function (viewType) { this.viewer.SetView (viewType); }; ImporterApp.prototype.ShowHideMesh = function (meshIndex) { var meshMenuItem = this.meshMenuItems[meshIndex]; this.ShowHideMeshInternal (meshIndex, !meshMenuItem.isVisible); this.viewer.Draw (); }; ImporterApp.prototype.IsolateMesh = function (meshIndex) { var i, meshMenuItem; var onlyThisVisible = true; if (!this.meshMenuItems[meshIndex].isVisible) { onlyThisVisible = false; } else { for (i = 0; i < this.meshMenuItems.length; i++) { meshMenuItem = this.meshMenuItems[i]; if (meshMenuItem.isVisible && i !== meshIndex) { onlyThisVisible = false; break; } } } var i; for (i = 0; i < this.meshMenuItems.length; i++) { if (onlyThisVisible) { this.ShowHideMeshInternal (i, true); } else { if (i == meshIndex) { this.ShowHideMeshInternal (i, true); } else { this.ShowHideMeshInternal (i, false); } } } this.viewer.Draw (); }; ImporterApp.prototype.ShowHideMeshInternal = function (meshIndex, isVisible) { var meshMenuItem = this.meshMenuItems[meshIndex]; meshMenuItem.isVisible = isVisible; meshMenuItem.visibleImage.attr ('src', meshMenuItem.isVisible ? 'images/visible.png' : 'images/hidden.png'); this.viewer.ShowMesh (meshIndex, meshMenuItem.isVisible); }; ImporterApp.prototype.HighlightMeshInternal = function (meshIndex, highlight) { var meshMenuItem = this.meshMenuItems[meshIndex]; meshMenuItem.Highlight (highlight); this.viewer.HighlightMesh (meshIndex, highlight); }; ImporterApp.prototype.ProcessFiles = function (fileList, isUrl) { this.ClearReadyForTest (); this.HideIntroControl (); this.floatingDialog.Close (); if (this.inGenerate) { return; } var userFiles = fileList; if (userFiles.length === 0) { return; } this.fileNames = null; var myThis = this; var processorFunc = JSM.ConvertFileListToJsonData; if (isUrl) { processorFunc = JSM.ConvertURLListToJsonData; } var menu = $('#menu'); menu.empty (); if (isUrl) { menu.html ('Downloading files...'); } else { menu.html ('Loading files...'); } processorFunc (userFiles, { onError : function () { myThis.GenerateError ('No readable file found. You can open 3ds, obj, stl, and off files.'); myThis.SetReadyForTest (); return; }, onReady : function (fileNames, jsonData) { myThis.fileNames = fileNames; myThis.viewer.SetJsonData (jsonData); menu.empty (); var progressBar = new ImporterProgressBar (menu); myThis.JsonLoaded (progressBar); } }); }; ImporterApp.prototype.RegisterCanvasClick = function () { var myThis = this; var mousePosition = null; this.canvas.mousedown (function () { mousePosition = [event.pageX, event.pageY]; }); this.canvas.mouseup (function (event) { var mouseMoved = (mousePosition == null || event.pageX != mousePosition[0] || event.pageY != mousePosition[1]); if (!mouseMoved) { var x = event.pageX - $(this).offset ().left; var y = event.pageY - $(this).offset ().top; myThis.OnCanvasClick (x, y); } mousePosition = null; }); }; ImporterApp.prototype.ScrollMeshIntoView = function (meshIndex) { if (meshIndex == -1) { return; } var menuItem = this.meshMenuItems[meshIndex]; menuItem.menuItemDiv.get (0).scrollIntoView (); }; ImporterApp.prototype.HighlightMesh = function (meshIndex) { var i, menuItem, highlight; if (meshIndex != -1) { for (i = 0; i < this.meshMenuItems.length; i++) { menuItem = this.meshMenuItems[i]; highlight = false; if (i == meshIndex) { if (!menuItem.IsHighlighted ()) { this.HighlightMeshInternal (i, true); } else { this.HighlightMeshInternal (i, false); } } } } else { for (i = 0; i < this.meshMenuItems.length; i++) { menuItem = this.meshMenuItems[i]; if (menuItem.IsHighlighted ()) { this.HighlightMeshInternal (i, false); } } } this.viewer.Draw (); }; ImporterApp.prototype.HighlightMeshesByMaterial = function (materialIndex) { var meshIndices = this.viewer.GetMeshesByMaterial (materialIndex); if (meshIndices.length === 0) { return; } var i, meshIndex, meshMenuItem; this.HighlightMesh (-1); for (i = 0; i < meshIndices.length; i++) { meshIndex = meshIndices[i]; meshMenuItem = this.meshMenuItems[meshIndex]; this.HighlightMeshInternal (meshIndex, true); } this.meshesGroup.SetOpen (true); this.ScrollMeshIntoView (meshIndices[0]); this.viewer.Draw (); }; ImporterApp.prototype.OnCanvasClick = function (x, y) { if (this.meshMenuItems == null) { return; } var objects = this.viewer.GetMeshesUnderPosition (x, y); var meshIndex = -1; if (objects.length > 0) { meshIndex = objects[0].originalJsonMeshIndex; this.meshesGroup.SetOpen (true); } this.HighlightMesh (meshIndex); this.ScrollMeshIntoView (meshIndex); }; ImporterApp.prototype.DragOver = function (event) { event.stopPropagation (); event.preventDefault (); event.dataTransfer.dropEffect = 'copy'; }; ImporterApp.prototype.Drop = function (event) { event.stopPropagation (); event.preventDefault (); this.ResetHash (); this.ProcessFiles (event.dataTransfer.files, false); }; ImporterApp.prototype.FileSelected = function (event) { event.stopPropagation (); event.preventDefault (); this.ResetHash (); this.ProcessFiles (event.target.files, false); }; ImporterApp.prototype.OpenFile = function () { var fileInput = document.getElementById ('file'); fileInput.click (); }; ImporterApp.prototype.ResetHash = function () { if (window.location.hash.length > 1) { window.location.hash = ''; } }; ImporterApp.prototype.LoadFilesFromHash = function () { if (window.location.hash.length < 2) { return false; } var fileInput = $('#file'); var hash = window.location.hash; if (hash == '#testmode') { fileInput.css ('display', ''); fileInput.css ('position', 'absolute'); fileInput.css ('right', '10px'); fileInput.css ('bottom', '10px'); return false; } fileInput.css ('display', 'none'); var hash = hash.substr (1, hash.length - 1); var fileList = hash.split (','); this.ProcessFiles (fileList, true); return true; }; window.onload = function () { var importerApp = new ImporterApp (); importerApp.Init (); // ExtensionIncludes importerApp.AddExtension (new ExampleExtension ()); // ExtensionIncludesEnd };