Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/develop' into feature-improve-…
Browse files Browse the repository at this point in the history
…node-field-query-api
  • Loading branch information
CoolSpy3 committed Aug 17, 2024
2 parents 6f8e71e + f33793c commit 0024bd6
Show file tree
Hide file tree
Showing 33 changed files with 408 additions and 103 deletions.
4 changes: 4 additions & 0 deletions docs/reference/changelog-r2024.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Released on December **th, 2023.
- New Features
- **Change the name of the web scene format from `X3D` to `W3D` ([#6280](https://github.com/cyberbotics/webots/pull/6280)).**
- Removed support for macOS 11 "Big Sur" and added support for macOS 14 "Sonoma" ([#6580](https://github.com/cyberbotics/webots/pull/6580)).
- Added the `indirectFieldAccess` tag to allow the `fields` variable to be used in proto templates without referencing a specific field ([#6614](https://github.com/cyberbotics/webots/pull/6614)).
- Added a method to include all subtypes of a node type in a PROTO field restriction ([#6574](https://github.com/cyberbotics/webots/pull/6574)).
- Enhancements
- Improved the image range of the rotating [Lidar](lidar.md) ([#6324](https://github.com/cyberbotics/webots/pull/6324)).
Expand All @@ -18,5 +19,8 @@ Released on December **th, 2023.
- Fixed crashes (with some graphics cards) caused by references to unused GLSL uniforms ([#6587](https://github.com/cyberbotics/webots/pull/6587)).
- Fixed `Brake`s added to the second axis of a `Hinge2Joint` being applied to the non-transformed axis ([#6584](https://github.com/cyberbotics/webots/pull/6584)).
- Fixed invalid absolute sound file path resulted in crash ([#6593](https://github.com/cyberbotics/webots/pull/6593))
- Fixed Speaker relative sound file path not working with external controller ([#6605](https://github.com/cyberbotics/webots/pull/6605)).
- Fixed the bug that when the language is Python, getTargets() cannot correctly obtain the multi-target data detected by the radar ([#6606](https://github.com/cyberbotics/webots/pull/6606))
- Fixed incomplete loading while minimized under some windowing systems ([#6617](https://github.com/cyberbotics/webots/pull/6617)).
- Fixed unitialized sliding friction when using asymmetric rolling friction ([#6618](https://github.com/cyberbotics/webots/pull/6618)).
- Fixed handling of remote assets from unofficial sources ([#6585](https://github.com/cyberbotics/webots/pull/6585)).
10 changes: 10 additions & 0 deletions docs/reference/javascript-procedural-proto.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The first represents the effective value of the field (for instance the one defi
- As shown in [this table](#vrml97-type-to-javascript-type-conversion), the conversion of a VRML97 node is an object.
This object contains the following keys: "node\_name" containing the VRML97 node name and "fields" which is in turn an object containing the JavaScript representation of the VRML97 node fields.
This object is equal to `undefined` if the VRML97 node is not defined (`NULL`).
- By default, the parser only detects fields that are accessed directly in the template statements (i.e. `fields.appearance`).
If you would like to access the `fields` object without referencing a specific field, you can add the following line at the beginning of the PROTO file: `# tags: indirectFieldAccess`.
- Objects that are part of [ECMA-262](http://www.ecma-international.org/publications/standards/Ecma-262.htm) are built-in and globally accessible, such as `Math`, `Date` and `String`.
- The `context` object provides contextual information about the PROTO.
Table [this table](#content-of-the-context-object) shows the available information and the corresponding keys.
Expand Down Expand Up @@ -823,6 +825,14 @@ The location of this path can be retrieved from the `context` field object, see

%end

### PROTO Regeneration
When a field used in a template statement is modified, the PROTO node is regenerated.
This means that the template statements are re-evaluated and the PROTO node is reloaded in the world.
For most nodes, this behavior should not affect the simulation.
However, special care should be taken when using PROTOs that have side effects (e.g. writing to a file).
Additionally, Robot nodes will restart their controllers when regenerated.
Using `tags: indirectFieldAccess` in the PROTO file will cause the PROTO to be regenerated whenever any field is modified, even if it is not used in a template statement.

### Optimization

By default, PROTO files are considered to be deterministic.
Expand Down
10 changes: 10 additions & 0 deletions docs/reference/lua-procedural-proto.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ The conversion between the VRML97 types and the Lua types is detailed in [this t
This dictionary contains the following keys: "node\_name" containing the VRML97 node name and "fields" which is a dictionary containing the Lua representation of the VRML97 node fields.
This dictionary is equal to `nil` if the VRML97 node is not defined (`NULL`).
For example, in the SimpleStairs example below, the `fields.appearance.node_name` key contains the `'Appearance'` string.
- By default, the parser only detects fields that are accessed directly in the template statements (i.e. `fields.appearance`).
If you would like to access the `fields` object without referencing a specific field, you can add the following line at the beginning of the PROTO file: `# tags: indirectFieldAccess`.
- The `context` dictionary provides contextual information about the PROTO.
Table [this table](#content-of-the-context-dictionary) shows the available information and its corresponding keys.
- The VRML97 comment ("#") prevails over the Lua statements.
Expand Down Expand Up @@ -88,6 +90,14 @@ The following standard fonts are available to write on the texture:

In addition to these fonts, it is possible to add other TrueType fonts file in your `PROJECT_HOME/fonts` directory.

### PROTO Regeneration
When a field used in a template statement is modified, the PROTO node is regenerated.
This means that the template statements are re-evaluated and the PROTO node is reloaded in the world.
For most nodes, this behavior should not affect the simulation.
However, special care should be taken when using PROTOs that have side effects (e.g. writing to a file).
Additionally, Robot nodes will restart their controllers when regenerated.
Using `tags: indirectFieldAccess` in the PROTO file will cause the PROTO to be regenerated whenever any field is modified, even if it is not used in a template statement.

### Optimization

By default, PROTO files are considered to be deterministic.
Expand Down
8 changes: 5 additions & 3 deletions docs/reference/speaker.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ Finally, the boolean `loop` argument defines if the sound will be played only on

It is possible to change the volume, pitch, balance, and loop parameters of a sound currently playing by calling again the `wb_speaker_play_sound` function with the same speakers and `sound` arguments.

> **Note**: The path to the sound file should be defined either absolutely or relatively.
If defined relatively, it will be searched first relatively to the robot controller folder.
If not found there and if the robot is a PROTO, it will be searched relatively to the PROTO folder of the robot.
> **Note**: The path to the sound file can be defined either absolutely or relatively.
If the file path is accessible by the robot controller either absolutely or relative to its working directory, it is loaded by the controller and its contents are streamed to Webots.
If the file is not found by the controller, the given path is passed to Webots.
Webots will first search for the path relative to the robot controller folder.
If the file is not found relative to the controller folder and the robot is a PROTO, Webots will search for the sound file relative to the folder of the PROTO file.

---

Expand Down
5 changes: 3 additions & 2 deletions include/wren/scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ void wr_scene_apply_pending_updates(WrScene *scene);

/* The 'materialName' parameter is optional (can be set to NULL for default), and if set, force
the renderables to use the named material */
void wr_scene_render(WrScene *scene, const char *material_name, bool culling);
void wr_scene_render_to_viewports(WrScene *scene, int count, WrViewport **viewports, const char *material_name, bool culling);
void wr_scene_render(WrScene *scene, const char *material_name, bool culling, bool offScreen);
void wr_scene_render_to_viewports(WrScene *scene, int count, WrViewport **viewports, const char *material_name, bool culling,
bool offScreen);

void wr_scene_set_ambient_light(const float *ambient_light);

Expand Down
53 changes: 53 additions & 0 deletions src/controller/c/speaker.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ struct Sound {
bool need_update;
bool need_stop;
bool is_playing;
unsigned char *upload_data; // sound data for uploading to webots
int upload_size; // size of sound data
struct Sound *next;
};

Expand Down Expand Up @@ -257,6 +259,18 @@ static void speaker_write_request(WbDevice *d, WbRequest *r) {
else
request_write_char(r, 0);
}

request_write_int32(r, sound->upload_size);
if (sound->upload_size) {
/* Sound data available to stream, send it one time and discard it.
* Webots will cache the data inside the device.
*/
request_write_data(r, sound->upload_data, sound->upload_size);
free(sound->upload_data);
sound->upload_data = NULL;
sound->upload_size = 0;
}

sound->need_update = false;
sound = sound->next;
}
Expand Down Expand Up @@ -298,6 +312,43 @@ static void speaker_toggle_remote(WbDevice *d, WbRequest *r) {
// nothing to do.
}

/* Try to read sound file contents if it is found by given path. */
static void speaker_try_load_sound_local(Sound *sound, const char *sound_file_name) {
FILE *fp;

sound->upload_data = NULL;
sound->upload_size = 0;

if ((fp = fopen(sound_file_name, "rb")) != NULL) {
if (fseek(fp, 0L, SEEK_END) == 0) {
const long file_size = ftell(fp);
if (file_size != -1) {
if (fseek(fp, 0L, SEEK_SET) == 0) {
size_t consumed = 0U;
sound->upload_data = malloc(file_size);
sound->upload_size = (int)file_size;
while (consumed < file_size) {
const size_t read = fread(&sound->upload_data[consumed], sizeof(unsigned char), file_size - consumed, fp);
if (ferror(fp)) {
free(sound->upload_data);
sound->upload_data = NULL;
sound->upload_size = 0;
break;
}
consumed += read;
}
}
}
}

if (sound->upload_data == NULL) {
fprintf(stderr, "Warning: %s() failed to read sound file '%s' :", __FUNCTION__, sound_file_name);
perror(NULL);
}
fclose(fp);
}
}

static void speaker_play_sound(WbDevice *device, const char *sound_name, double volume, double pitch, double balance, bool loop,
int side) {
Sound *sound = speaker_get_sound_if_exist(device, sound_name);
Expand All @@ -309,6 +360,8 @@ static void speaker_play_sound(WbDevice *device, const char *sound_name, double
memcpy(sound->sound_file, sound_name, l);
sound->next = ((Speaker *)device->pdata)->sound_list;
((Speaker *)device->pdata)->sound_list = sound;

speaker_try_load_sound_local(sound, sound_name);
}
sound->volume = volume;
sound->pitch = pitch;
Expand Down
9 changes: 8 additions & 1 deletion src/webots/engine/WbSimulationCluster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,15 @@ void WbSimulationCluster::fillSurfaceParameters(const WbContactProperties *cp, c
// handle rolling friction
if (rho[0] > 0 || rho[1] > 0 || rho[2] > 0) {
contact->surface.mode = contact->surface.mode | dContactRolling;
if (rho[1] > 0 || rho[2] > 0)
if (rho[1] > 0 || rho[2] > 0) {
// ODE will use asymmetric sliding friction if we set dContactAxisDep (because dContactAxisDep
// == dContactMu2), so we need to make sure mu2 is set. If it wasn't already set above, we
// want symmetric sliding friction which means setting mu2 = mu (like ODE would do if
// dContactMu2 was not set.)
if (!(contact->surface.mode & dContactMu2))
contact->surface.mu2 = contact->surface.mu;
contact->surface.mode = contact->surface.mode | dContactAxisDep;
}

contact->surface.rho = rho[0];
contact->surface.rho2 = rho[1];
Expand Down
9 changes: 5 additions & 4 deletions src/webots/gui/WbView3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1111,8 +1111,9 @@ void WbView3D::setWorld(WbSimulationWorld *w) {

WbWrenOpenGlContext::doneWren();

// first rendering without culling to make sure every meshes/textures are actually loaded on the GPU
renderNow(false);
// first rendering is offscreen without culling to make sure every meshes/textures are actually
// loaded on the GPU
renderNow(false, true);
}

void WbView3D::restoreOptionalRendering(const QStringList &enabledCenterOfMassNodeNames,
Expand Down Expand Up @@ -1441,7 +1442,7 @@ void WbView3D::resizeWren(int width, int height) {
emit resized();
}

void WbView3D::renderNow(bool culling) {
void WbView3D::renderNow(bool culling, bool offScreen) {
if (!wr_gl_state_is_initialized())
initialize();

Expand All @@ -1461,7 +1462,7 @@ void WbView3D::renderNow(bool culling) {
WbWrenOpenGlContext::doneWren();
} else
#endif
WbWrenWindow::renderNow(culling);
WbWrenWindow::renderNow(culling, offScreen);
mLastRefreshTimer.start();
emit mainRenderingEnded(mPhysicsRefresh);

Expand Down
2 changes: 1 addition & 1 deletion src/webots/gui/WbView3D.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public slots:

protected slots:
// cppcheck-suppress virtualCallInConstructor
void renderNow(bool culling = true) override;
void renderNow(bool culling = true, bool offScreen = false) override;

protected:
void initialize() override;
Expand Down
9 changes: 5 additions & 4 deletions src/webots/gui/WbWrenWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ void WbWrenWindow::renderLater() {
}
}

void WbWrenWindow::renderNow(bool culling) {
if (!isExposed() || !wr_gl_state_is_initialized())
void WbWrenWindow::renderNow(bool culling, bool offScreen) {
if ((!isExposed() && !offScreen) || !wr_gl_state_is_initialized())
return;

static int first = true;
Expand All @@ -225,9 +225,10 @@ void WbWrenWindow::renderNow(bool culling) {

WbWrenOpenGlContext::makeWrenCurrent();

wr_scene_render(wr_scene_get_instance(), NULL, culling);
wr_scene_render(wr_scene_get_instance(), NULL, culling, offScreen);

WbWrenOpenGlContext::instance()->swapBuffers(this);
if (!offScreen)
WbWrenOpenGlContext::instance()->swapBuffers(this);
WbWrenOpenGlContext::doneWren();

if (mVideoStreamingServer && mVideoStreamingServer->isNewFrameNeeded() && !first)
Expand Down
2 changes: 1 addition & 1 deletion src/webots/gui/WbWrenWindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public slots:

protected:
virtual void initialize();
virtual void renderNow(bool culling = true);
virtual void renderNow(bool culling = true, bool offScreen = false);
virtual void resizeWren(int width, int height);
bool event(QEvent *event) override;

Expand Down
84 changes: 59 additions & 25 deletions src/webots/nodes/WbSpeaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "../../controller/c/messages.h"

#include <QtCore/QBuffer>
#include <QtCore/QDataStream>
#include <QtCore/QDir>

Expand Down Expand Up @@ -61,6 +62,10 @@ WbSpeaker::~WbSpeaker() {
}
mSoundSourcesMap.clear();
mPlayingSoundSourcesMap.clear();

foreach (QByteArray *buffer, mStreamedSoundDataMap)
delete buffer;
mStreamedSoundDataMap.clear();
}

void WbSpeaker::postFinalize() {
Expand All @@ -79,21 +84,8 @@ void WbSpeaker::handleMessage(QDataStream &stream) {
int numberOfSound = 0;
stream >> numberOfSound;
for (int i = 0; i < numberOfSound; ++i) {
short size;
short side;
double volume;
double pitch;
double balance;
unsigned char loop;
stream >> size;
char soundFile[size];
stream.readRawData(soundFile, size);
stream >> volume;
stream >> pitch;
stream >> balance;
stream >> side;
stream >> loop;
playSound(soundFile, volume, pitch, balance, (bool)loop, (int)side);
SoundPlayData playData(stream);
playSound(playData);
}
return;
}
Expand Down Expand Up @@ -194,18 +186,28 @@ void WbSpeaker::playText(const char *text, double volume) {
}
}

void WbSpeaker::playSound(const char *file, double volume, double pitch, double balance, bool loop, int side) {
QString filename = QString(file);
QString key = filename;
if (side == -1)
void WbSpeaker::playSound(SoundPlayData &playData) {
const QString filename(playData.file());
QString key(filename);
if (playData.side() == -1)
key += "_left";
else if (side == 1)
else if (playData.side() == 1)
key += "_right";

// Controller streamed sound data available ?
if (playData.rawLength() && !mStreamedSoundDataMap.contains(filename))
mStreamedSoundDataMap[filename] = new QByteArray(playData.rawData());
QByteArray *cachedSoundData = NULL;
if (mStreamedSoundDataMap.contains(key))
cachedSoundData = mStreamedSoundDataMap[key];

if (!mSoundSourcesMap.contains(key)) { // this sound was never played
QString path;
// check if sound data was streamed from controller
if (cachedSoundData)
path = filename;
// check if the path is absolute
if (QDir::isAbsolutePath(filename) && QFile::exists(filename))
if (path.isEmpty() && QDir::isAbsolutePath(filename) && QFile::exists(filename))
path = filename;
// check if the path is relative to the controller
if (path.isEmpty() && QFile::exists(mControllerDir + filename))
Expand All @@ -221,7 +223,18 @@ void WbSpeaker::playSound(const char *file, double volume, double pitch, double
WbSoundSource *source = WbSoundEngine::createSource();
updateSoundSource(source);
const QString extension = filename.mid(filename.lastIndexOf('.') + 1).toLower();
const WbSoundClip *soundClip = WbSoundEngine::sound(path, extension, NULL, balance, side);

// Use controller streamed sound data if available.
QBuffer *device = NULL;
if (cachedSoundData) {
device = new QBuffer(cachedSoundData);
device->open(QBuffer::ReadOnly);
}

const WbSoundClip *soundClip = WbSoundEngine::sound(path, extension, device, playData.balance(), playData.side());
delete device;
device = NULL;

if (!soundClip) {
this->warn(tr("Impossible to play '%1'. Make sure the file format is supported (8 or 16 bits, mono or stereo wave).\n")
.arg(filename));
Expand All @@ -237,9 +250,9 @@ void WbSpeaker::playSound(const char *file, double volume, double pitch, double
mPlayingSoundSourcesMap[key] = source;
if (!source->isPlaying()) // this sound was already played but is over
source->play();
source->setLooping(loop);
source->setGain(volume);
source->setPitch(pitch);
source->setLooping(playData.loop());
source->setGain(playData.volume());
source->setPitch(playData.pitch());
if (WbSimulationState::instance()->isPaused() || WbSimulationState::instance()->isStep())
source->pause();
}
Expand Down Expand Up @@ -280,3 +293,24 @@ void WbSpeaker::updateSoundSource(WbSoundSource *source) {
source->setVelocity(linearVelocity());
source->setDirection(rotationMatrix() * WbVector3(0, 1, 0));
}

WbSpeaker::SoundPlayData::SoundPlayData(QDataStream &stream) : mFile(), mRawData() {
short size;
unsigned char loopByte;
stream >> size;
char soundFile[size];
stream.readRawData(soundFile, size);
mFile = QString(soundFile);

stream >> mVolume;
stream >> mPitch;
stream >> mBalance;
stream >> mSide;
stream >> loopByte;
mLoop = (bool)loopByte;
stream >> mRawLength;
if (mRawLength) {
mRawData.resize(mRawLength);
stream.readRawData(mRawData.data(), mRawLength);
}
}
Loading

0 comments on commit 0024bd6

Please sign in to comment.