/* Usage:
<- Canvas will be added here 2D Image
$("element").load3DThumbnail(function(canvas) { //complete! }); The element being called must have a data-3d-url attribute While it's loading the OBJ & MTL files, it creates a CSS loading spinner inside the new div. */ (function () { // constants var fieldOfView = 70; var minRenderDistance = 0.1; var maxRenderDistance = 1000; var loadingWaitBeforeShowingSpinner = 500; // milliseconds - for UX, don't show the spinner immediately var maximumPollsToGet3DThumbnailJson = 10; var retryInterval = 2 * 1000; //WebGLRenderer can be reused by multiple scenes var renderer; //hack because IE8 doesn't like that three.min.js uses getters and setters. //Adding these scripts in the PageScript would break the whole page. function getCachedScript(url) { // Use $.ajax() since it is more flexible than $.getScript // Return the jqXHR object so we can chain callbacks return $.ajax({ dataType: "script", cache: true, url: url }); }; function getScripts(inserts, callback) { var nextInsert = inserts.shift(); if (nextInsert != undefined) { getCachedScript(nextInsert).done(function () { getScripts(inserts, callback); }); } else { if (callback != undefined) callback(); } }; var scriptsLoaded = false; function loadScriptsIfNeeded(callback, scriptsToLoadCsv) { if (scriptsLoaded) { callback(); } else { // Bcause IE8 doesn't like that three.min.js uses getters and setters. // Adding these scripts in the PageScript would break the whole page. var scriptsToLoad = scriptsToLoadCsv.split(","); getScripts(scriptsToLoad, function () { scriptsLoaded = true; callback(); }); } } function addLightsToScene(scene) { //TODO get these values in the thumbnail JSON from obj exporter var ambient = new THREE.AmbientLight(0x878780); scene.add(ambient); var sunLight = new THREE.DirectionalLight(0xACACAC); sunLight.position.set(-0.671597898, 0.671597898, 0.312909544).normalize(); scene.add(sunLight); var backLight = new THREE.DirectionalLight(0x444444); var backLightPos = new THREE.Vector3().copy(sunLight.position).negate().normalize(); //inverse of sun direction backLight.position.set(backLightPos); scene.add(backLight); } function clearContainer(container) { container.find(".thumbnail-spinner").remove(); container.find("canvas").remove(); } $.fn.load3DThumbnail = function (callback, onError) { var cancelled = false; function cancelLoading() { cancelled = true; } function getThumbnailJson(url, callbackAfterJsonRetrieved) { var retries = 0; $.get(url, function (data) { if (data.Final) { $.getJSON(data.Url, function (json) { callbackAfterJsonRetrieved(json); }) .fail(function () { onError("3D Thumbnail failed to load"); }); } else { if (retries < maximumPollsToGet3DThumbnailJson && (cancelled == false)) { setTimeout(function () { getThumbnailJson(url, callbackAfterJsonRetrieved); }, retryInterval); retries += 1; } } }); } function loadObjAndMtl(modelHash, mtlHash, container, json, callbackAfterLoaderIsDone) { var baseUri = "/thumbnail/resolve-hash/"; //server should provide var containerHeight = container.width(); //container.height(); var containerWidth = container.width(); var camera = new THREE.PerspectiveCamera(fieldOfView, containerWidth / containerHeight, minRenderDistance, maxRenderDistance); var scene = new THREE.Scene(); var controls; function render() { renderer.render(scene, camera); } function animate() { if (controls.enabled) { controls.update(); } TWEEN.update(); render(); requestAnimationFrame(animate); } function createCanvas() { renderer.setSize(containerWidth, containerHeight); var canvas = renderer.domElement; container.resize(function () { camera.aspect = container.width() / container.height(); camera.updateProjectionMatrix(); renderer.setSize(container.width(), container.height()); controls.handleResize(); }); window.onbeforeunload = function () { // canvas goes black when navigating to another page $(canvas).hide(); $(container).find("img").show(); } return canvas; } function initializeControls() { // The controller that lets us spin the camera around an object var orbitControls = new THREE.OrbitControls(camera, container.get(0), json); orbitControls.rotateSpeed = 1.5; orbitControls.zoomSpeed = 1.5; orbitControls.dynamicDampingFactor = 0.3; orbitControls.addEventListener("change", render); return orbitControls; } function objAndMtlLoaded(modelObject) { addLightsToScene(scene); scene.add(modelObject); var canvas = createCanvas(); controls = initializeControls(); render(); animate(); callbackAfterLoaderIsDone(canvas); }; // ReSharper disable once InconsistentNaming var loader = new THREE.OBJMTLLoader(); loader.load(baseUri, modelHash, mtlHash, objAndMtlLoaded, undefined, onError); } this.each(function () { try { var container = $(this); // var scriptsCsv = container.data("js-files"); var loadThreeDee = function () { //Renderer can be reused by multiple scenes if (!renderer) { // ReSharper disable once InconsistentNaming renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); } var jsonUrl = container.data("3d-url"); clearContainer(container); var placeholder = $("
"); placeholder.appendTo(container); var loaderVisible = false; function startLoading() { loaderVisible = true; placeholder.height(container.width()); placeholder.show(); placeholder.empty(); setTimeout(function () { if (loaderVisible) { placeholder.addClass("text-center"); // placeholder.html("
Loading
"); placeholder.html("
Loading
"); } }, loadingWaitBeforeShowingSpinner); } function endLoading() { loaderVisible = false; placeholder.hide(); placeholder.empty(); placeholder.removeClass("text-center"); } startLoading(); function updateContainer(json) { loadObjAndMtl(json.obj, json.mtl, container, json, function (canvas) { clearContainer(container); if (cancelled == false) { endLoading(); container.append(canvas); callback(canvas); } }); }; getThumbnailJson(jsonUrl, updateContainer); }; // loadScriptsIfNeeded(loadThreeDee, scriptsCsv); loadThreeDee(); } catch (e) { onError(e); } }); return { cancel: cancelLoading } } })();