Skip to content

Commit

Permalink
Enhancement webotsJS loading screen and progress bar (cyberbotics#4593)
Browse files Browse the repository at this point in the history
* update css

* testing

* create function to set progress bar content

* replaced all progress messages with new function

* DOM elements not updating

* take screenshot and save or upload to database

* fix atlas wbproj

* fix boomer controller

* new image directory

* started progress class

* progress class working

* testing

* bug when parsing

* background urls fixed

* fixed conflicts

* style and url fix

* testing url

* reset progress to none and css fix

* fixed conflict

* responsive loading page

* get thumbnails from webots cloud server

* progress bar and mobile responsive done

* new progress css, minor fixes, mobile responsive done

* minor fix

* fix file load error messages

* bug fix

* webotsJS done for now

* save screenshot on local export

* share scene and animation with thumbnail

* thumbnails working

* thumbnails working

* Removed overlays and optional rendering for thumbnail

* hide solid specific optional renderings on thumbnail

* ditto

* update thumbnail documentation

* clang format fix

* restore to default paths

* restor icons.svg

* fix cloud upload no screenshot bug

* fixed thumbnail selection still showing

* add default thumbnail

* fix css

* restore paths

* ignore world thumbnail files

* change progress bar design

* ditto

* Fixed screenshot on Windows

* doc cleanup and const

* switch projection to perspective for thumbnail

* new messages, percentages, backwards compatibility for connecting/loading

* update setup_viewer.js

Co-authored-by: Olivier Michel <[email protected]>
Co-authored-by: Benjamin Délèze <[email protected]>
  • Loading branch information
3 people authored Jun 14, 2022
1 parent 57f105e commit bb6691c
Show file tree
Hide file tree
Showing 51 changed files with 723 additions and 889 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ Thumbs.db
/webots.lnk
/webots_debug_output.txt
/util

# world thumbnail files
.*.jpg
13 changes: 7 additions & 6 deletions docs/guide/the-standard-file-hierarchy-of-a-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ The link between the world files and this directory is done through the *physics

> **Note**: Note that the directories can be created by using the wizard [New Project Directory](the-user-interface.md) described in [this chapter](getting-started-with-webots.md).
### The Project Files
### The Project and Thumbnail Files

The project files contain information about the GUI (such as the perspective).
Each world file has a corresponding project file and thumbnail file.
These files are hidden.
Each world file can have one project file.
If the world file is named "myWorldFile.wbt", its project file is named ".myWorldFile.wbproj".
This file is written by Webots when a world is correctly closed.
Removing it allows you to retrieve the default perspective.
The project files contain information about the GUI (such as the perspective).
If the world file is named "myWorldFile.wbt", its project file is named ".myWorldFile.wbproj" and its thumbnail ".myWorldFile.jpg".
These files are written by Webots when a world is correctly saved.
Removing the ".myWorldFile.wbproj" allows you to retrieve the default perspective.
The 768px by 432px ".myWorldFile.jpg" thumbnail is used for loading when viewing a simulation, animation or a scene on the web. If it is not captured or deleted, a default thumbnail is used.

### The "controllers" Directory

Expand Down
3 changes: 2 additions & 1 deletion docs/guide/web-animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The [figure below](#screenshot-of-a-web-animation-page-generated-by-webots) show

### How to Export a Web Animation

Select the `Share...` menu item and choose if you want to upload it to [webots.cloud](https://webots.cloud) or to save it locally, then click the `Record and share animation` button to start the recording.
Select the `Share...` menu item and choose if you want to upload it to [webots.cloud](https://webots.cloud) or to save it locally, then click the `Record and export animation` button to start the recording.
Click the `Stop HTML5 animation` to finish the recording and save the animation.
Webots will ask to playback the resulting file in the default Web browser (from the OS settings).

Expand All @@ -39,6 +39,7 @@ Please refer to [this section](web-scene.md#how-to-embed-a-web-scene-in-your-web
The web animation is played by a web component from the [WebotsView.js] package called `webots-view`.

The following attributes are available:
* `data-thumbnail`: the name of the .jpg file containing the thumbnail. If the `data-thumbnail` attribute is not set, a default thumbnail will be displayed during load.
* `data-scene`: the name of the .x3d file containing the 3d scene.
* `data-animation`: the name of the .json file containing the animation sequence.
* `data-autoplay`: boolean to determine if the animation should be played automatically, `true` by default.
Expand Down
5 changes: 3 additions & 2 deletions docs/guide/web-scene.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The 3D navigation in the player is possible using the mouse or the touch screen,

### How to Export a Web Scene

Select the `Share...` menu item, choose if you want to upload it to [webots.cloud](https://webots.cloud) or to save it locally, then click the `Share scene` button.
Select the `Share...` menu item, choose if you want to upload it to [webots.cloud](https://webots.cloud) or to save it locally, then click the `Export scene` button.
When the export is completed, Webots will ask to playback the resulting file in the default Web browser.

**Note**: The `CSS` file, the `X3D` file and the required textures are exported in the same directory as the target `HTML` file.
Expand All @@ -42,7 +42,8 @@ The resources (`CSS`, `JavaScript`, etc.) on the [Cyberbotics Website](https://w

The web scene is displayed by a web component from the [WebotsView.js] package called `webots-view`.

The following attribute is available:
The following attributes are available:
* `data-thumbnail`: the name of the .jpg file containing the thumbnail. If the `data-thumbnail` attribute is not set, a default thumbnail will be displayed during load.
* `data-scene`: the name of the .x3d file containing the 3d scene. It is evaluated only once: when the page is loaded. If the `data-scene` attribute is set, the `webots-view` web-component will automatically try to load the web scene .

For more complex interaction with the web component, the following functions are available:
Expand Down
3 changes: 2 additions & 1 deletion resources/web/streaming_viewer/setup_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ function init() {
}

function connect() {
const defaultThumbnail = 'https://cyberbotics.com/wwi/R2022b/images/loading/default_thumbnail.png';
const streamingMode = modeSelect.options[modeSelect.selectedIndex].value;
const webotsView = document.getElementsByTagName('webots-view')[0];
webotsView.onready = onConnect;
webotsView.ondisconnect = onDisconnect;
webotsView.connect(ipInput.value, streamingMode, broadcast.checked, mobileDevice);
webotsView.connect(ipInput.value, streamingMode, broadcast.checked, mobileDevice, -1, defaultThumbnail);

ipInput.disabled = true;
modeSelect.disabled = true;
Expand Down
2 changes: 1 addition & 1 deletion resources/web/streaming_viewer/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ body {
width: 100%;
overflow-x: hidden;
background-color: #fafafa;
font-family: "Raleway", sans-serif;
font-family: "Raleway-light";
min-width: 620px;
}

Expand Down
8 changes: 5 additions & 3 deletions resources/web/templates/x3d_playback.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ body {
width: 100%;
overflow-x: hidden;
background-color: #fafafa;
font-family: "Raleway", sans-serif;
font-family: 'Raleway-light';
}

p {
Expand Down Expand Up @@ -51,7 +51,7 @@ p {

webots-view {
width: 100%;
display:block;
display: block;
position: relative;
}

Expand Down Expand Up @@ -83,8 +83,10 @@ webots-view {
font-size: 12px;
padding-top: 5px;
padding-left: 24px;
font-family: 'Arial';
}

.bottom-container a {
color: #00a785;
}
font-family: 'Arial';
}
7 changes: 4 additions & 3 deletions resources/web/templates/x3d_playback.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
<h1>Web %type%: <strong>%title%</strong></h1>
</div>
<div class="webots-view-container">
<webots-view data-scene=%x3dName% data-animation=%jsonName%></webots-view>
<webots-view data-thumbnail=%jpgName% data-scene=%x3dName% data-animation=%jsonName%></webots-view>
</div>
<div class="description-container">
<p><strong>Description:</strong><br><br>%description%</p>
</div>
<div class="bottom-container">
<p>If the animation is not loading correctly, please refer to the <a target='_blank' href='https://cyberbotics.com/doc/guide/web-scene#remarks-on-the-used-technologies-and-their-limitations'>User Guide</a>
it could be that your browser prevents local files CORS requests.</p>
<p>If the animation is not loading correctly, please refer to the <a target='_blank'
href='https://cyberbotics.com/doc/guide/web-scene#remarks-on-the-used-technologies-and-their-limitations'>User
Guide</a> it could be that your browser prevents local files CORS requests.</p>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions resources/web/wwi/FloatingWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default class FloatingWindow {
this.floatingWindow.id = name;
this.floatingWindow.style.visibility = 'hidden';
parentNode.appendChild(this.floatingWindow);

this.floatingWindowHeader = document.createElement('div');
this.floatingWindowHeader.className = 'floating-window-header';
this.floatingWindow.appendChild(this.floatingWindowHeader);
Expand Down
127 changes: 72 additions & 55 deletions resources/web/wwi/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ export default class Parser {
this._prefix = prefix;
this._downloadingImage = new Set();
this._promises = [];
this._promiseCounter = 0;
this._promiseNumber = 0;
WbWorld.init();
}

parse(text, renderer, parent, callback) {
webots.currentView.progress.setProgressBar('Connecting to webots instance...', 'same', 60 + 0.1 * 30, 'Parsing object...');
let xml = null;
if (window.DOMParser) {
const parser = new DOMParser();
Expand All @@ -70,14 +73,21 @@ export default class Parser {
const node = xml.getElementsByTagName('nodes')[0];
if (typeof node === 'undefined')
console.error('Unknown content, nor Scene, nor Node');
else
else {
this._nodeNumber = 0;
this._nodeCounter = 0;
this._countChildElements(node);
this._parseChildren(node, parent);
} else
}
} else {
this._nodeNumber = 0;
this._nodeCounter = 0;
this._countChildElements(scene);
this._parseNode(scene);
}
}

if (document.getElementById('webots-progress-message'))
document.getElementById('webots-progress-message').innerHTML = 'Finalizing...';
webots.currentView.progress.setProgressBar('block', 'Finalizing...', 75, 'Finalizing webotsJS nodes...');

return Promise.all(this._promises).then(() => {
this._promises = [];
Expand All @@ -100,28 +110,36 @@ export default class Parser {
WbBackground.instance.setIrradianceCubeArray(this.irradianceCubeURL);
this.irradianceCubeURL = undefined;
}
WbWorld.instance.sceneTree.forEach(node => {
WbWorld.instance.sceneTree.forEach((node, i) => {
const percentage = 70 + 30 * (i + 1) / WbWorld.instance.sceneTree.length;
const info = 'Finalizing node' + node.id + ': ' + (100 * (i + 1) / WbWorld.instance.sceneTree.length) + '%';
webots.currentView.progress.setProgressBar('block', 'same', 75 + 0.25 * percentage, info);
node.finalize();
});

WbWorld.instance.readyForUpdates = true;

webots.currentView.x3dScene.resize();
renderer.render();
if (document.getElementById('webots-progress'))
document.getElementById('webots-progress').style.display = 'none';
setTimeout(() => { webots.currentView.progress.setProgressBar('none'); }, 300);

if (typeof callback === 'function')
callback();

if (document.getElementById('robot-window-button') !== null)
document.getElementsByTagName('webots-view')[0].toolbar.loadRobotWindows();

console.timeEnd('Loaded in: ');
console.timeEnd('Loaded in');
});
}

_parseNode(node, parentNode, isBoundingObject) {
this._nodeCounter += 1;
const percentage = 30 + 70 * this._nodeCounter / this._nodeNumber;
const infoPercentage = 100 * this._nodeCounter / this._nodeNumber;
const info = 'Parsing node: ' + node.id + ' (' + node.tagName + ') ' + infoPercentage + '%';
webots.currentView.progress.setProgressBar('block', 'same', 60 + 0.1 * percentage, info);

if (typeof WbWorld.instance === 'undefined')
WbWorld.init();

Expand Down Expand Up @@ -246,15 +264,19 @@ export default class Parser {
this._promises.push(loadTextureData(prefix, 'smaa_area_texture.png').then(image => {
this.smaaAreaTexture = image;
this.smaaAreaTexture.isTranslucent = false;
this._updatePromiseCounter('Downloading assets: Texture \'smaa_area_texture.png\'...');
}));
this._promises.push(loadTextureData(prefix, 'smaa_search_texture.png').then(image => {
this.smaaSearchTexture = image;
this.smaaSearchTexture.isTranslucent = false;
this._updatePromiseCounter('Downloading assets: Texture \'smaa_search_texture.png\'...');
}));
this._promises.push(loadTextureData(prefix, 'gtao_noise_texture.png').then(image => {
this.gtaoNoiseTexture = image;
this.gtaoNoiseTexture.isTranslucent = true;
this._updatePromiseCounter('Downloading assets: Texture \'gtao_noise_texture.png\'...');
}));
this._promiseNumber += 3;

WbWorld.instance.scene = new WbScene();
}
Expand Down Expand Up @@ -310,6 +332,10 @@ export default class Parser {
const skyColor = convertStringToVec3(getNodeAttribute(node, 'skyColor', '0 0 0'));
const luminosity = parseFloat(getNodeAttribute(node, 'luminosity', '1'));

const backgroundIdx = (WbWorld.instance.coordinateSystem === 'ENU') ? [0, 1, 2, 3, 4, 5] : [5, 0, 1, 2, 3, 4];
const rotationValues = (WbWorld.instance.coordinateSystem === 'ENU') ? [90, -90, -90, 180, 0, -90] : [0, 0, 0, 0, 0, 0];
const cubeImageIdx = (WbWorld.instance.coordinateSystem === 'ENU') ? [0, 4, 1, 3, 2, 5] : [2, 5, 3, 4, 1, 0];

let backgroundUrl = [];
backgroundUrl[0] = getNodeAttribute(node, 'backUrl');
backgroundUrl[1] = getNodeAttribute(node, 'bottomUrl');
Expand All @@ -330,26 +356,14 @@ export default class Parser {

this.cubeImages = [];
if (areUrlsPresent) {
if (WbWorld.instance.coordinateSystem === 'ENU') {
this._promises.push(loadTextureData(this._prefix, backgroundUrl[0], false, 90)
.then(image => { this.cubeImages[0] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[1], false, -90)
.then(image => { this.cubeImages[4] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[2], false, -90)
.then(image => { this.cubeImages[1] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[3], false, 180)
.then(image => { this.cubeImages[3] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[4]).then(image => { this.cubeImages[2] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[5], false, -90)
.then(image => { this.cubeImages[5] = image; }));
} else {
this._promises.push(loadTextureData(this._prefix, backgroundUrl[5]).then(image => { this.cubeImages[2] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[0]).then(image => { this.cubeImages[5] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[1]).then(image => { this.cubeImages[3] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[2]).then(image => { this.cubeImages[4] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[3]).then(image => { this.cubeImages[1] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundUrl[4]).then(image => { this.cubeImages[0] = image; }));
for (let i = 0; i < 6; i++) {
this._promises.push(loadTextureData(this._prefix, backgroundUrl[backgroundIdx[i]], false, rotationValues[i])
.then(image => {
this.cubeImages[cubeImageIdx[i]] = image;
this._updatePromiseCounter('Downloading assets: Texture \'background ' + i + '\'...');
}));
}
this._promiseNumber += 6;
}

let backgroundIrradianceUrl = [];
Expand All @@ -369,35 +383,17 @@ export default class Parser {
backgroundIrradianceUrl[i] = backgroundIrradianceUrl[i].split('"')
.filter(element => { if (element !== ' ') return element; })[0];
}

this.irradianceCubeURL = [];
if (areIrradianceUrlsPresent) {
if (WbWorld.instance.coordinateSystem === 'ENU') {
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[0], true, 90)
.then(image => { this.irradianceCubeURL[0] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[1], true, -90)
.then(image => { this.irradianceCubeURL[4] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[2], true, -90)
.then(image => { this.irradianceCubeURL[1] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[3], true, 180)
.then(image => { this.irradianceCubeURL[3] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[4], true)
.then(image => { this.irradianceCubeURL[2] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[5], true, -90)
.then(image => { this.irradianceCubeURL[5] = image; }));
} else {
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[5], true)
.then(image => { this.irradianceCubeURL[2] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[0], true)
.then(image => { this.irradianceCubeURL[5] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[1], true)
.then(image => { this.irradianceCubeURL[3] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[2], true)
.then(image => { this.irradianceCubeURL[4] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[3], true)
.then(image => { this.irradianceCubeURL[1] = image; }));
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[4], true)
.then(image => { this.irradianceCubeURL[0] = image; }));
for (let i = 0; i < 6; i++) {
this._promises.push(loadTextureData(this._prefix, backgroundIrradianceUrl[backgroundIdx[i]], true, rotationValues[i]).
then(image => {
this.irradianceCubeURL[cubeImageIdx[i]] = image;
this._updatePromiseCounter('Downloading assets: Texture \'background irradiance ' + i + '\'...');
}));
}
this._promiseNumber += 6;
}

const background = new WbBackground(id, skyColor, luminosity);
Expand All @@ -408,6 +404,23 @@ export default class Parser {
return background;
}

_countChildElements(tree) {
if (tree !== 'undefined') {
tree.childNodes.forEach(child => {
if (child.tagName) {
this._nodeNumber += 1;
this._countChildElements(child);
}
});
}
}

_updatePromiseCounter(info) {
this._promiseCounter += 1;
const percentage = 70 * this._promiseCounter / this._promiseNumber;
webots.currentView.progress.setProgressBar('block', 'same', 75 + 0.25 * percentage, info);
}

_checkUse(node, parentNode) {
let use = getNodeAttribute(node, 'USE');
if (typeof use === 'undefined')
Expand Down Expand Up @@ -609,7 +622,9 @@ export default class Parser {
const node = WbWorld.instance.nodes.get(cadShape.useList[i]);
node.scene = meshContent;
}
this._updatePromiseCounter('Downloading assets: Mesh \'CadShape\'...');
}));
this._promiseNumber += 1;

return cadShape;
}
Expand Down Expand Up @@ -665,8 +680,8 @@ export default class Parser {
const ambientIntensity = parseFloat(getNodeAttribute(node, 'ambientIntensity', '0'));
const castShadows = getNodeAttribute(node, 'castShadows', 'false').toLowerCase() === 'true';

const pointLight = new WbPointLight(id, on, attenuation, color, intensity, location, radius, ambientIntensity, castShadows,
parentNode);
const pointLight = new WbPointLight(id, on, attenuation, color, intensity, location, radius, ambientIntensity,
castShadows, parentNode);

if (typeof parentNode !== 'undefined' && typeof pointLight !== 'undefined')
parentNode.children.push(pointLight);
Expand Down Expand Up @@ -969,7 +984,9 @@ export default class Parser {
const node = WbWorld.instance.nodes.get(mesh.useList[i]);
node.scene = meshContent;
}
this._updatePromiseCounter('Downloading assets: Mesh \'mesh ' + name + '\'...');
}));
this._promiseNumber += 1;

return mesh;
}
Expand Down
Loading

0 comments on commit bb6691c

Please sign in to comment.