diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d373849f..25fd9e4b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,12 +25,14 @@ registration or heartbeat to Clowder that will restrict use of that extractor to - Documentation on how to do easy testing of pull requests - Previewer source URL in the documentation to point to the Clowder GitHub repo. [#395](https://github.com/clowder-framework/clowder/issues/395) - Added a citation.cff file +- Added Google's model viewer within viewer_three.js previewer ### Fixed - Updated lastModifiesDate when updating file or metadata to a dataset, added lastModified to UI [386](https://github.com/clowder-framework/clowder/issues/386) - Disabled button while create dataset ajax call is still going on [#311](https://github.com/clowder-framework/clowder/issues/311) - Changed default to 'Viewer' while inviting users to new spaces [#375](https://github.com/clowder-framework/clowder/issues/375) - Fixed bug where complex JSON metadata objects using arrays were not being indexed properly for search. +- Fixed positioning problems related to how the 3D models appear on the screen ## 1.21.0 - 2022-08-23 diff --git a/public/javascripts/previewers/ifc_previewer/package.json b/public/javascripts/previewers/ifc_previewer/package.json index de59e479f..17e354593 100644 --- a/public/javascripts/previewers/ifc_previewer/package.json +++ b/public/javascripts/previewers/ifc_previewer/package.json @@ -2,5 +2,5 @@ "name": "IFC-Viewer", "main": "ifc_viewer.js", "file": true, - "contentType": ["model/ifc", "model/IFC", "application/octet-stream"] + "contentType": ["model/ifc", "model/IFC"] } \ No newline at end of file diff --git a/public/javascripts/previewers/three_js/package.json b/public/javascripts/previewers/three_js/package.json index a4b9bf6a2..4cb94b49a 100644 --- a/public/javascripts/previewers/three_js/package.json +++ b/public/javascripts/previewers/three_js/package.json @@ -1,6 +1,6 @@ { - "name": "Three-JS", + "name": "3D Viewer", "main": "viewer_three.js", "file": true, - "contentType": [ "model/fbx", "model/FBX","model/gltf","model/GLTF", "model/glb","model/GLB", "application/octet-stream"] + "contentType": [ "model/fbx", "model/FBX","model/gltf","model/GLTF", "model/glb","model/GLB"] } \ No newline at end of file diff --git a/public/javascripts/previewers/three_js/loading_screen.css b/public/javascripts/previewers/three_js/style.css similarity index 97% rename from public/javascripts/previewers/three_js/loading_screen.css rename to public/javascripts/previewers/three_js/style.css index c4a6cfe6c..4d48d161e 100644 --- a/public/javascripts/previewers/three_js/loading_screen.css +++ b/public/javascripts/previewers/three_js/style.css @@ -84,4 +84,9 @@ html, body { -ms-transform: rotate(360deg); transform: rotate(360deg); } +} + +model-viewer { + width: 640px; + height: 480px; } \ No newline at end of file diff --git a/public/javascripts/previewers/three_js/viewer_three.js b/public/javascripts/previewers/three_js/viewer_three.js index 30bfa1488..523e21451 100644 --- a/public/javascripts/previewers/three_js/viewer_three.js +++ b/public/javascripts/previewers/three_js/viewer_three.js @@ -15,6 +15,8 @@ * First functional version * + 11/08/21 (cc): Added loading screen and ability * to load gltf files + * + * + 07/05/22 (cc): Bug fixes ****************************************************************/ @@ -42,247 +44,272 @@ ****************************************************************/ - let fileNameExtension; - let loader; - - (function ($, Configuration) { - let useTab = Configuration.tab; - let referenceUrl = Configuration.url; - let previewer = Configuration.previewer; - - $(useTab).append('
\n' + - '\n' + - '\t
\n' + - '\n' + - '
') - - $(useTab).append(''); - - let fileName = $('#file-name-title').text().trim(); - fileNameExtension = fileName.split('.').pop(); - - let scripts = [ - "three.min.js", - "stats.min.js", - "OrbitControls.js", - "FBXLoader.js", - "GLTFLoader.js", - "DRACOLoader.js", - "fflate.min.js" - ]; - - for (let index = 0; index < scripts.length; index++) { - let s = document.createElement("script"); - s.type = "text/javascript"; - s.src = previewer + "/js/" + scripts[index]; - $(useTab).append(s); - } - - $(document).ready(function () { - init(referenceUrl); - animate(); - }); - - }(jQuery, Configuration)); - - let camera, scene, renderer, stats; - const clock = new THREE.Clock(); - let mixer; - let previewerWidth = 640; - let previewerHeight = 480; - - function init(urlAddress) { - const container = document.getElementById(Configuration.tab.replace("#", "")); - - renderer = new THREE.WebGLRenderer({antialias: false, powerPreference: "high-performance"}); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.setSize(previewerWidth, previewerHeight); - renderer.shadowMap.enabled = true; - - camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 2000000); - camera.position.set(0, 100, 1250); - - scene = new THREE.Scene(); - scene.background = new THREE.Color(0x444444); - //scene.fog = new THREE.Fog( 0xa0a0a0, 200, 1000 ); - - const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444); - hemiLight.position.set(0, 200, 0); - scene.add(hemiLight); - - const dirLight = new THREE.DirectionalLight(0xffffff); - dirLight.position.set(0, 200, 100); - dirLight.castShadow = true; - dirLight.shadow.camera.top = 180; - dirLight.shadow.camera.bottom = -100; - dirLight.shadow.camera.left = -120; - dirLight.shadow.camera.right = 120; - scene.add(dirLight); - - const controls = new THREE.OrbitControls(camera, renderer.domElement); - controls.target.set(0, 100, 0); - controls.update(); - container.appendChild(renderer.domElement); - - // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) ); - - // ground - // const mesh = new THREE.Mesh( - // new THREE.PlaneGeometry( 4000, 4000 ), - // new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) - // ); - // - // mesh.rotation.x = - Math.PI / 2; - // mesh.receiveShadow = true; - // scene.add( mesh ); - // - // const grid = new THREE.GridHelper( 4000, 20, 0x000000, 0x000000 ); - // grid.material.opacity = 0.2; - // grid.material.transparent = true; - // scene.add( grid ); - - const loadingManager = new THREE.LoadingManager(() => { - const loadingScreen = document.getElementById('loading-screen'); - loadingScreen.classList.add('fade-out'); - loadingScreen.addEventListener('transitionend', onTransitionEnd); - }); - - if (fileNameExtension === 'fbx') { - loader = new THREE.FBXLoader(loadingManager); - } - - if (fileNameExtension === 'gltf' || fileNameExtension === 'glb') { - let dracopath = "/assets/javascripts/previewers/three_js/draco/" - - loader = new THREE.GLTFLoader(loadingManager); - - const dracoLoader = new THREE.DRACOLoader(loadingManager); - - dracoLoader.setDecoderPath(dracopath); - - dracoLoader.setDecoderConfig({ type: 'js' }); - - loader.setDRACOLoader(dracoLoader); - } - - loader.setPath(urlAddress); - - //loader.setPath("/assets/javascripts/previewers/three_js/models/chimpanzee/source/"); - - //const texture = new THREE.TextureLoader().setPath('/assets/javascripts/previewers/three_js/models/chimpanzee/source/'); - - loader.load('', function (object) { - - if (fileNameExtension === 'gltf' || fileNameExtension === 'glb') { - object = object.scene; - } - - object.traverse(function (child) { - - if (child.isMesh) { - - child.castShadow = true; - child.receiveShadow = true; - - } - - }); - - if (object.animations[0]) { - mixer = new THREE.AnimationMixer(object); - - const action = mixer.clipAction(object.animations[0]); - action.play(); - } - - const box = new THREE.Box3().setFromObject(object); - const size = box.getSize(new THREE.Vector3()).length(); - const center = box.getCenter(new THREE.Vector3()); - - controls.reset(); - - object.position.x += (object.position.x - center.x); - object.position.y += (object.position.y - center.y); - object.position.z += (object.position.z - center.z); - - controls.maxDistance = size * 10; - camera.near = size / 100; - camera.far = size * 100; - - camera.updateProjectionMatrix(); - scene.add(object); - - // object.traverse( function ( child ) { - // - // if ( child.isMesh ) { - // child.castShadow = true; - // child.receiveShadow = true; - // - // child.material = new THREE.MeshBasicMaterial({map: texture.load('EYESLP_defaultMat_BaseColor.png')}); - // } - // - // if ( child.isMesh ) { - // child.material = new THREE.MeshBasicMaterial({map: texture.load('EYESLP_defaultMat_Normal.png')}); - // } - // - // if ( child.isMesh ) { - // child.material = new THREE.MeshBasicMaterial({map: texture.load('LOW_POLY_defaultMat_AO.png')}); - // } - // // - // if ( child.isMesh ) { - // child.material = new THREE.MeshBasicMaterial({map: texture.load('LOW_POLY_defaultMat_BaseColor.png')}); - // } - // // - // if ( child.isMesh ) { - // child.material = new THREE.MeshBasicMaterial({map: texture.load('LOW_POLY_defaultMat_Metallic.png')}); - // } - // // - // if ( child.isMesh ) { - // child.material = new THREE.MeshBasicMaterial({map: texture.load('LOW_POLY_defaultMat_Roughness.png')}); - // } - // - // if ( child.isMesh ) { - // child.material = new THREE.MeshBasicMaterial({map: texture.load('Pruyebanm.jpg')}); - // } - // - // } ); - // - // scene.add( object ); - - }); - - - window.addEventListener('resize', onWindowResize); - - // stats - stats = new Stats(); - //container.appendChild( stats.dom ); - - } - - function onWindowResize() { - - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - - renderer.setSize(window.innerWidth, window.innerHeight); - - } - - function onTransitionEnd(event) { - - event.target.remove(); - - } - - function animate() { - requestAnimationFrame(animate); - - const delta = clock.getDelta(); - - if (mixer) mixer.update(delta); - - renderer.render(scene, camera); - - stats.update(); - } \ No newline at end of file +/** + * Jquery + */ + +let loader; +let useTab; +let referenceUrl; +let previewer; +let fileName; +let fileNameExtension; + +let scripts = [ + "three.min.js", + "stats.min.js", + "OrbitControls.js", + "FBXLoader.js", + // "GLTFLoader.js", + // "DRACOLoader.js", + "fflate.min.js" +]; + +(function ($, Configuration) { + useTab = Configuration.tab; + referenceUrl = Configuration.url; + previewer = Configuration.previewer; + + fileName = $('#file-name-title').text().trim(); + fileNameExtension = fileName.split('.').pop(); + + $(useTab).append(''); + + /** + * Decipher whether the uploaded file is an + * FBX or a GLTF (GLB) file and load the appropriate model loader + * For the special case of GLTF/GLB model usel Google's + * model viewer + */ + + for (let index = 0; index < scripts.length; index++) { + let s = document.createElement("script"); + s.type = "text/javascript"; + s.src = previewer + "/js/" + scripts[index]; + $(useTab).append(s); + } + + if (fileNameExtension === 'fbx') { + + $(useTab).append('
\n' + + '\n' + + '\t
\n' + + '\n' + + '
') + + $(document).ready(function () { + init(referenceUrl); + animate(); + }); + } + + if (fileNameExtension === 'gltf' || fileNameExtension === 'glb') { + + $(useTab).append('') + + + $(useTab).append("") + } + +}(jQuery, Configuration)); + +/** + * Three.js + */ + +let camera, scene, renderer, stats; +let mixer; +let previewerWidth = 640; +let previewerHeight = 480; +const clock = new THREE.Clock(); + +function init(urlAddress) { + const container = document.getElementById(Configuration.tab.replace("#", "")); + + + /** + * Renderer + */ + + renderer = new THREE.WebGLRenderer( + { + antialias: false + } + ); + + //renderer.physicallyCorrectLights = true; + renderer.outputEncoding = THREE.sRGBEncoding; + renderer.setClearColor(0xcccccc); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(previewerWidth, previewerHeight); + renderer.shadowMap.enabled = true; + + /** + * Camera + */ + + camera = new THREE.PerspectiveCamera(55, previewerWidth / previewerHeight, 0.1, 1000); + //camera.position.set(0, 20, 100); + + /** + * Scene + */ + + scene = new THREE.Scene(); + scene.background = new THREE.Color("White"); + //scene.fog = new THREE.Fog( 0xa0a0a0, 200, 1000 ); + + /** + * Lights + */ + + const hemiLight1 = new THREE.HemisphereLight(0xffffff, 0x444444); + const hemiLight2 = new THREE.HemisphereLight(0xffffff, 1); + //hemiLight.position.set(0, -20, 0); + scene.add(hemiLight1, hemiLight2); + + const ambientLight = new THREE.AmbientLight(0xffffff, 1); + //ambientLight.position.set(0, 200, 0); + scene.add(ambientLight); + + const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); + //dirLight.position.set(0, 4, 4); + + // const helper = new THREE.DirectionalLightHelper(dirLight, 2); + // scene.add(helper); + dirLight.castShadow = true; + // dirLight.shadow.camera.top = 180; + // dirLight.shadow.camera.bottom = -100; + // dirLight.shadow.camera.left = -120; + // dirLight.shadow.camera.right = 120; + scene.add(dirLight); + + /** + * Orbit controls + */ + + const controls = new THREE.OrbitControls(camera, renderer.domElement); + //controls.target.set(0, 100, 0); + controls.update(); + container.appendChild(renderer.domElement); + + // scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) ); + + // ground + // const mesh = new THREE.Mesh( + // new THREE.PlaneGeometry(1000, 1000), + // new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }) + // ); + // mesh.rotation.x = - Math.PI / 2; + // mesh.receiveShadow = true; + // mesh.material.transparent = false; + // scene.add(mesh); + + // const grid = new THREE.GridHelper(1000, 100, 0x000000, 0x000000); + // grid.material.opacity = 0.3; + // grid.material.transparent = true; + // scene.add(grid); + + /** + * Loading manager + */ + + // The loading manager is needed for the loading screen + + const loadingManager = new THREE.LoadingManager(() => { + const loadingScreen = document.getElementById('loading-screen'); + loadingScreen.classList.add('fade-out'); + loadingScreen.addEventListener('transitionend', onTransitionEnd); + }); + + loader = new THREE.FBXLoader(loadingManager); + + // Set loader's path to the url address of the file + + loader.setPath(urlAddress); + + //loader.setPath("/assets/javascripts/previewers/three_js/models/chimpanzee/source/"); + + loader.load('', function (object) { + + if (fileNameExtension === 'gltf' || fileNameExtension === 'glb') { + object = object.scene; + } + + object.traverse(function (child) { + + if (child.isMesh) { + + child.castShadow = true; + child.receiveShadow = true; + + } + + }); + + if (object.animations[0]) { + mixer = new THREE.AnimationMixer(object); + + const action = mixer.clipAction(object.animations[0]); + action.play(); + } + + const box = new THREE.Box3().setFromObject(object); + const size = box.getSize(new THREE.Vector3()).length(); + const center = box.getCenter(new THREE.Vector3()); + + //controls.reset(); + + object.position.x += (object.position.x - center.x); + object.position.y += (object.position.y - center.y); + object.position.z += (object.position.z - center.z); + + controls.maxDistance = size * 10; + controls.minDistance = size; + + camera.near = size / 100; + camera.far = size * 100; + camera.updateProjectionMatrix(); + camera.position.copy(center); + camera.position.x += size / 2.0; + camera.position.y += size / 5.0; + camera.position.z += size / 2.0; + camera.lookAt(center); + controls.update(); + + scene.add(object); + }); + + window.addEventListener('resize', onWindowResize); + + // stats + stats = new Stats(); + //container.appendChild( stats.dom ); +} + +function onWindowResize() { + + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight); + +} + +function onTransitionEnd(event) { + + event.target.remove(); + +} + +function animate() { + requestAnimationFrame(animate); + + const delta = clock.getDelta(); + + if (mixer) mixer.update(delta); + + renderer.render(scene, camera); + + stats.update(); +} \ No newline at end of file