This is a pull request for the following functionalities:
Add three.js widget to visualize dataset 3D models without relying on sketchfab. This module is selfcontained, can be reused anywhere on the site and takes a list of external links to 3D models as data input
How to test?
The following steps describe how I test this widget locally (if you know of a better way, please be my guest and I would love to know 🙂):
download the models from the following urls to your computer (or feel free to use any files you want as long as they are LAS, STL, PLY or OBJ)
Manually add the files to the assets folder, e.g: /assets/test-models/_YOURFILES
http://gigadb.gigasciencejournal.com/adminExternalLink/create add the files one by one as external links to any one of the published datasets (e.g. 100006). In the url field you should add something like this: /assets/test-models/filename, in External Link Type select "3d Models"
You should see the "3D models" tab. When you open it, you should see the models you added in the dropdown
The initial state of the viewer should be no loaded modal, and a visible play button
when you click play, a model should be loaded and you should be able to interact with it
the "help" and "fullscreen" buttons should work as intended, either with mouse or keyboard
if you toggle fullscreen and / or resize the window, the viewer should be fully responsive and the model should remain visible
if you select another model in the selector, the model should be immediately loaded
You can test error states if you repeat the above process by adding a file that is not a valid 3d model or not a LAS, STL, PLY or OBJ file (or you can just add a throw new Error('Ooops') in the line before await loadModel(file);)
Example of error message:
it would be critical to test in a production-like environment with files / external links mimicking the production environment
How have functionalities been implemented?
3D models were previously visualized within an iframe taking a sketchfab url as input. This PR gets rid of the sketchfab iframe and replaces it with a JS widget that makes use of the three.js library to load and display an interactive 3D model
The main requirement is to load STL and OBJ file formats, and also (less common) PLY and LAS
Different options to locally load the model were considered (refer to the original ticket for some). I decided to use three.js because:
It is widely popular and well known, essentially being the standard way to render 3D files in JS world
It supports many formats via built-in loaders (LAS being an exception)
It supports / will support WebXR via plugins. Note that WebXR is an experimental API and was not considered in this PR
For a simple use case such as rendering a model with no animations and with basic interactions it is relatively simple to implement
Other options I checked had very low or null adoption and / or did not fully cover the use case. I opted for the widely adopted solution.
The changes can be grouped as follows:
Mainly, everything related to the model viewer is within protected/js/model-viewer
The partial protected/views/shared/_model_viewer.php renders the html for the widget, and loads the necessary scripts;
three.js imports are added to the head as a import map. That way, the three.js library can be imported as a module in the widget scripts
Yii::app()->assetManager->forceCopy = YII_DEBUG; makes sure the assets are rebuilt and not cached in development (NOTE: I am unclear on whether this targets specifically local dev, is this the correct solution? If this is a blocker, this line can be removed, but I found it necessary to add this line during local dev)
widget scripts are added to the body
modelviewer is initialized
The partial is rendered in the dataset page protected/views/dataset/view.php. This page contains a switch statement that renders elements such an iframe depending on the external link types. In the switch statement, the links are handled one by one. In the case of 3D models, I deleted that case from the switch statement, and passed them to the partial as data property. Note that I had to do this because the partial, rather than handling individual links one by one, takes them all to build a select input from which to select the model to load
less/modules/model-viewer.less takes care of all the styles related to the viewer
That covers all the changes outside of the protected/js/model-viewer folder, now related to the widget itself
first, the idea is to abstract implementation details away from the consuming view. I suggest looking at the jsdocs decorating the modelViewer function and dig deeper only if curious about implementation details
the widget is split into two core modules
ui: takes care of updating the state of the UI depending on the state of the viewer (i.e.: loading states, model loaded, no model loaded, error state)
the UI has 4 states:
idle: no model is loaded, a play button to load the currently selected model (by default, the first in the select input) should be visible
loading: a loading spinner should be displayed while loading a model
error: a danger alert should be displayed at the bottom with a relevant message. Use can select another model or click "play" again
success: model is loaded and should be interactive
viewer: it uses the three.js library to render a 3D model, and takes care of all related things (setting up scene, lights, cameras, controls, loading models...)
It is expected that all models are in one of LAS, STL, PLY or OBJ file formats. If another format is needed, the corresponding loader for such file format should be added (protected/js/model-viewer/viewer/components/models/loader.js)
among other things, this module takes care of making the viewer fully responsive
LAS files (data point files) needed a custom implementation, with a visualization looking like a relatively crude set of points. I see sketchfab doesn't do much better, and I understand LAS files are a minority (?) so I decided not to dig more on that field
the "look" of the 3D models is fully customizable. I opted for a metallic effect somewhat similar to sketchfab. The visuals can be fine-tuned / changed if needed as three.js is fully customizable
the entry point modelViewer connects the two modules and initializes them
Any issues with implementation?
I am unable to test on production or in a production-like environment
it assumes the incoming files are defined as ExternalLinks and follow the interface protected/interfaces/DatasetExternalLinksInterface.php, if the requirement is different, for example, a different model such as protected/models/File.php, it should be relatively easy to adapt
it assumes the site can fetch and load the incoming external links without cors issues, in short, cors is not handled in any way
In Yii, what is the right way to access the current environment from within a .js file? For example to determine the isProduction value in protected/js/model-viewer/helpers/logger.js
Pull request for issue: #2054
This is a pull request for the following functionalities:
How to test?
The following steps describe how I test this widget locally (if you know of a better way, please be my guest and I would love to know 🙂):
You can test error states if you repeat the above process by adding a file that is not a valid 3d model or not a LAS, STL, PLY or OBJ file (or you can just add a
throw new Error('Ooops')
in the line beforeawait loadModel(file);
)Example of error message:
How have functionalities been implemented?
3D models were previously visualized within an iframe taking a sketchfab url as input. This PR gets rid of the sketchfab iframe and replaces it with a JS widget that makes use of the three.js library to load and display an interactive 3D model
The main requirement is to load STL and OBJ file formats, and also (less common) PLY and LAS
Different options to locally load the model were considered (refer to the original ticket for some). I decided to use three.js because:
Other options I checked had very low or null adoption and / or did not fully cover the use case. I opted for the widely adopted solution.
The changes can be grouped as follows:
protected/js/model-viewer
protected/views/shared/_model_viewer.php
renders the html for the widget, and loads the necessary scripts;head
as a import map. That way, the three.js library can be imported as a module in the widget scriptsYii::app()->assetManager->forceCopy = YII_DEBUG;
makes sure the assets are rebuilt and not cached in development (NOTE: I am unclear on whether this targets specifically local dev, is this the correct solution? If this is a blocker, this line can be removed, but I found it necessary to add this line during local dev)protected/views/dataset/view.php
. This page contains a switch statement that renders elements such an iframe depending on the external link types. In the switch statement, the links are handled one by one. In the case of 3D models, I deleted that case from the switch statement, and passed them to the partial asdata
property. Note that I had to do this because the partial, rather than handling individual links one by one, takes them all to build a select input from which to select the model to loadless/modules/model-viewer.less
takes care of all the styles related to the viewerThat covers all the changes outside of the
protected/js/model-viewer
folder, now related to the widget itselfmodelViewer
function and dig deeper only if curious about implementation detailsmodelViewer
connects the two modules and initializes themAny issues with implementation?
isProduction
value inprotected/js/model-viewer/helpers/logger.js
Any changes to automated tests?
All tests pass