diff --git a/.github/workflows/test_suite_linux.yml b/.github/workflows/test_suite_linux.yml index 4be8c1b6ecf..0cbb501183e 100644 --- a/.github/workflows/test_suite_linux.yml +++ b/.github/workflows/test_suite_linux.yml @@ -177,7 +177,7 @@ jobs: path: artifact - name: Get branch name id: branch-name - uses: tj-actions/branch-names@v5.1 + uses: tj-actions/branch-names@v7.0.7 - name: Extract Webots and Cache run: | tar xjf artifact/webots-*-x86-64*.tar.bz2 -C artifact diff --git a/.github/workflows/test_suite_linux_develop.yml b/.github/workflows/test_suite_linux_develop.yml index b9f11b8be13..e205d96c4e3 100644 --- a/.github/workflows/test_suite_linux_develop.yml +++ b/.github/workflows/test_suite_linux_develop.yml @@ -170,7 +170,7 @@ jobs: path: artifact - name: Get branch name id: branch-name - uses: tj-actions/branch-names@v5.1 + uses: tj-actions/branch-names@v7.0.7 - name: Extract Webots and Cache run: | tar xjf artifact/webots-*-x86-64*.tar.bz2 -C artifact diff --git a/.github/workflows/test_suite_mac.yml b/.github/workflows/test_suite_mac.yml index 15b05f5f45d..66447fc5510 100644 --- a/.github/workflows/test_suite_mac.yml +++ b/.github/workflows/test_suite_mac.yml @@ -43,6 +43,7 @@ jobs: - name: Install Webots Compilation Dependencies run: | # swig wget and cmake are already installed + pip install setuptools npm install -g appdmg - name: Set Commit SHA in Version if: ${{ github.event_name == 'schedule' }} diff --git a/.github/workflows/test_suite_mac_develop.yml b/.github/workflows/test_suite_mac_develop.yml index 89a569ab6a8..a84a8add932 100644 --- a/.github/workflows/test_suite_mac_develop.yml +++ b/.github/workflows/test_suite_mac_develop.yml @@ -39,6 +39,7 @@ jobs: - name: Install Webots Compilation Dependencies run: | # swig wget and cmake are already installed + pip install setuptools npm install -g appdmg - name: Set Commit SHA in Version if: ${{ github.event_name == 'schedule' }} diff --git a/Contents/Info.plist b/Contents/Info.plist index e1d457462d0..4f84a5fc131 100644 --- a/Contents/Info.plist +++ b/Contents/Info.plist @@ -35,7 +35,7 @@ CFBundleExecutable webots CFBundleGetInfoString - Webots R2023b, Copyright 1998-2023 Cyberbotics Ltd. + Webots R2023b revision 1, Copyright 1998-2023 Cyberbotics Ltd. CFBundleIconFile webots_icon CFBundleIdentifier @@ -47,11 +47,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - R2023b + R2023b revision 1 CFBundleSignature wbt CFBundleVersion - R2023b + R2023b revision 1 LSMinimumSystemVersion 10.14 CSResourcesFileMapped diff --git a/docs/discord/update.py b/docs/discord/update.py deleted file mode 100755 index 37c34eae899..00000000000 --- a/docs/discord/update.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Copyright 1996-2023 Cyberbotics Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import discord -import os -import re - -MAX_MESSAGES_PER_PAGE = 1000 - -channels = { - 'news': {'preserveMessageOrder': True}, - 'technical-questions': {'preserveMessageOrder': False}, - 'development': {'preserveMessageOrder': False}, - 'documentation': {'preserveMessageOrder': False}, -} - -contributors = {} - - -class MyClient(discord.Client): - def __init__(self): - discord.Client.__init__(self, intents=discord.Intents.all()) - - async def export_channel(self, channel): - year = None - month = None - path = os.path.dirname(os.path.abspath(__file__)) - years = [] - with open(os.path.join(path, channel.name + '.md'), 'w', encoding='utf-8') as rootFile: - file = rootFile - file.write(u'# %s\n\n' % channel.name.title()) - file.write(u'This is an archive of the `%s` channel of the ' % channel.name + - '[Webots Discord server](https://discordapp.com/invite/nTWbN9m).\n\n') - previousMessageUser = None - messages = [] - async for message in channel.history(limit=None): - messages.append(message) - if not channels[channel.name]['preserveMessageOrder']: - messages.reverse() - - yearlyFiles = [] - - for message in messages: - if message.type == discord.MessageType.default and (message.content or message.attachments): - # ingored massages with a '🚫' reaction - ignoredMessage = False - for reaction in message.reactions: - if reaction.emoji == '🚫': - ignoredMessage = True - break - if ignoredMessage: - continue - # statistics - if message.author.name not in contributors: - contributors[message.author.name] = 0 - else: - contributors[message.author.name] += 1 - # yearly section - if year is None or year != message.created_at.year: - year = message.created_at.year - if len(messages) > MAX_MESSAGES_PER_PAGE: - yearlyfile = open(os.path.join(path, channel.name + '-' + str(year) + '.md'), 'w', encoding='utf-8') - yearlyfile.write(u'# %s %s\n\n' % (channel.name.title(), year)) - yearlyfile.write(u'This is an archive of the `%s` channel of the ' % channel.name + - '[Webots Discord server](https://discordapp.com/invite/nTWbN9m)' + - ' for year %d.\n\n' % year) - yearlyFiles.append(yearlyfile) - years.append(year) - rootFile.write(u' - [%d](%s)\n' % (year, channel.name + '-' + str(year) + '.md')) - file = yearlyfile - month = None - else: - file.write(u'## %d\n\n' % year) - if file != rootFile and message.created_at.month != month: - month = message.created_at.month - file.write(u'## {0:%B}\n\n'.format(message.created_at)) - # author + date header - if previousMessageUser != message.author: - previousMessageUser = message.author - roles = [] - if hasattr(message.author, 'roles'): - for role in message.author.roles: - if role.name != '@everyone': - roles.append(role.name) - file.write(u'##### %s %s%s\n' % - (message.author.name.replace('_', '\\_'), - '[%s] ' % '-'.join(roles) if roles else '', - message.created_at.strftime("%m/%d/%Y %H:%M:%S"))) - else: - file.write('\n') - if message.content: - content = '' - # read message line by line - inCode = False - for line in message.content.splitlines(): - # remove wrongly used multi-line code - for start, code, end in re.findall(r'([^`]*)```([^`]*)```([^`]*)', line): - line = '%s`%s`%s' % (start, code, end) - # multi-line code - if '```' in line: - inCode = not inCode - # make sure it is on a dedicated line - if inCode and not line.startswith('```'): - line = line.replace('```', '\n```') - if not inCode and len(line) > 3: - line = line.replace('```', '\n```') - # not inside a multi-line code - if not inCode: - # remove problematic parts - line = line.replace('', '``') - if line.startswith('#') or line.startswith('> #'): - line = line.replace('#', '\\#') - # protect underscores - undescoreProtected = False - for start, code, end in re.findall(r'([^`]*)`([^`]*)`([^`]*)', line): - line = line.replace(start, start.replace('_', '\\_')) - line = line.replace(end, end.replace('_', '\\_')) - undescoreProtected = True - if not undescoreProtected: - line = line.replace('_', '\\_') - # make url links - regex = r'(?Phttps?://([\w-]+(?:(?:\.[\w-]+)+))([\w.,\\@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?)' - for url in re.findall(regex, line): - urlString = url[0] - line = line.replace(urlString, '[%s](%s)' % (urlString, urlString.replace('\\_', '_'))) - line += '\n' - # add line to the content - content += line + '\n' - # remove last new line - content = content[:-2] - # replace mention by actual name - for mention in message.mentions: - alternativeMention = mention.mention.replace('<@', '<@!') - content = content.replace(alternativeMention, '`@' + mention.name + '`') - content = content.replace(mention.mention, '`@' + mention.name + '`') - file.write(content) - # add attachments - for attachment in message.attachments: - name, extension = os.path.splitext(attachment.filename) - if extension.lower() in ['.bmp', '.gif', '.jpg', '.jpeg', '.png']: - file.write(u'\n%figure\n') - file.write(u'![%s](%s)\n' % (attachment.filename, attachment.url)) - file.write(u'%end') - else: - file.write(u'\n> **Attachment**: [%s](%s)' % (attachment.filename.replace('_', '\\_'), - attachment.url)) - file.write(u'\n\n') - elif message.type == discord.MessageType.pins_add or message.type == discord.MessageType.new_member: - pass - else: - print("\033[33mUnsupported message type:" + str(message.type) + '\033[0m') - print("\033[33m\tContent:" + str(message.content) + '\033[0m') - for yearlyfile in yearlyFiles: - yearlyfile.close() - return years - - async def on_ready(self): - path = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(path, 'index.md'), 'w', encoding='utf-8') as file: - file.write(u'# Webots Discord Archives\n\n') - file.write(u'Release {{ webots.version.full }}\n\n') - file.write(u'%figure\n') - file.write(u'![Discord](images/discord.jpg)\n') - file.write(u'%end\n\n') - file.write(u'Copyright © {{ date.year }} Cyberbotics Ltd.\n\n') - file.write(u'These are archives of the [Webots Discord server](https://discordapp.com/invite/nTWbN9m):\n') - with open(os.path.join(path, 'menu.md'), 'w', encoding='utf-8') as menuFile: - for channel in self.get_all_channels(): - if type(channel) == discord.channel.TextChannel and channel.name in channels: - file.write(u'- [%s](%s)\n' % (channel.name.title(), channel.name + '.md')) - menuFile.write(u'- [%s](%s)\n' % (channel.name.title(), channel.name + '.md')) - years = await self.export_channel(channel) - for year in years: - menuFile.write(u' - [%d](%s)\n' % (year, channel.name + '-' + str(year) + '.md')) - await self.close() - - async def on_message(self, message): - print('Message from {0.author}: {0.content}'.format(message)) - - -parser = argparse.ArgumentParser(description='Update the Webots discord doc.') -parser.add_argument('--token', '-t', dest='token', help='Specifies the Discord token', required=True) -parser.add_argument('--channels', '-c', dest='channels', nargs='+', help='list of channel to export') -args = parser.parse_args() -client = MyClient() -if args.channels is not None: - channels = args.channels -client.run(args.token) - -# display statistics -contributors = sorted(contributors.items(), key=lambda kv: kv[1]) -contributors.reverse() -print('Top 20 contributors:') -for contributor in contributors[0:20]: - print(' - %s (%d)' % contributor) diff --git a/docs/guide/matlab.md b/docs/guide/matlab.md index dca4105bcf0..e6ee7f7089a 100644 --- a/docs/guide/matlab.md +++ b/docs/guide/matlab.md @@ -15,10 +15,6 @@ Here is a simple MATLAB controller example: ```MATLAB function simple_example -% uncomment the next two lines to use the MATLAB desktop -%desktop; -%keyboard; - TIME_STEP = 32; my_led = wb_robot_get_device('my_led'); @@ -40,41 +36,42 @@ while wb_robot_step(TIME_STEP) ~= -1 end ``` -### Using the MATLAB Desktop +### Debugging Using the MATLAB Desktop -In order to avoid cluttering the desktop with too many windows, Webots starts MATLAB with the *-nodesktop* option. -The *-nodesktop* option starts MATLAB without user interface and therefore it keeps the memory usage low which is useful in particular for multi-robot experiments. -If you would like to use the MATLAB desktop to interact with your controller you just need to add these two MATLAB commands somewhere at the beginning of your controller m-file: +For each controller written using MATLAB, Webots will start a new instance of MATLAB to act as an interpreter. +In order to avoid cluttering the desktop with too many windows, Webots starts each instance of MATLAB in non-interactive mode. +This means that MATLAB starts without the user interface which keeps the memory usage low; this is particularly useful in multi-robot experiments. +Any output to stdout (such as `disp` or `fprintf`) will also be redirected to the Webots console. -```MATLAB -desktop; -keyboard; -``` +If you would like to use the MATLAB desktop to interact with your controller, you will need to run it in `` mode with the appropriate additional argument. +You can read more about that [here](running-extern-robot-controllers.md). -The `desktop` command brings up the MATLAB desktop. -The `keyboard` stops the execution of the controller and gives control to the keyboard (`K>>` prompt). -Then MATLAB opens your controller m-file in its editor and indicates that the execution is stopped at the `keyboard` command. -After that, the controller m-file can be debugged interactively, i.e., it is possible to continue the execution step-by-step, set break points, watch variable, etc. -While debugging, the current values of the controller variables are shown in the MATLAB workspace. -It is possible to *continue* the execution of the controller by typing `return` at the `K>>` prompt. -Finally the execution of the controller can be terminated with ctrl-C key combination. +**Note**: This is equivalent to inserting the command `keyboard` in your controller code, but this is strongly discouraged since it will cause an error during non-interactive execution of the code. -Once the controller is terminated, the connection with Webots remains active. +Running an external controller in interactive mode will automatically place a breakpoint at the first line of your controller. +Once MATLAB desktop has initialized, it will halt the execution of the controller and give control to the keyboard (`K>>` prompt). +MATLAB also opens your controller m-file in its editor and indicates that the execution is stopped at the breakpoint. + +At this point, the controller m-file can be debugged interactively, i.e., it is possible to continue the execution step-by-step, set break points, watch variable, etc. +You can use the navigation buttons in the Editor Toolstrip such as Continue/Pause, Step, and Quit Debugging to control the execution. +While running, the controller will run normally until it terminates or reaches another breakpoint. +While paused, the current values of the controller variables are shown in the MATLAB workspace, and the Command Window becomes available. +You can read more about debugging MATLAB code on the [MathWorks homepage](https://www.mathworks.com/help/matlab/matlab_prog/debugging-process-and-features.html). + +While paused (or after the controller has been terminated), the connection with Webots remains active. Therefore it becomes possible to issue Webots commands directly on the MATLAB prompt, for example you can interactively issue commands to query the sensors, etc.: ```MATLAB ->> wb_robot_step(1000); ->> wb_gps_get_values(gps) +K>> wb_robot_step(1000); +K>> wb_gps_get_values(gps) ans = 0.0001 0.0030 -0.6425 ->> | ``` -It is possible to use additional `keyboard` statements in various places in your ".m" controller. -So each time MATLAB will run into a `keyboard` statement, it will return control to the `K>>` prompt where you will be able to debug interactively. - -At this point, it is also possible to restart the controller by calling its m-file from MATLAB prompt. +The execution of the controller can be terminated with Ctrl+C key combination, or by quitting the debugger. +However, since all controllers are functions, their Workspace (local variables) will be lost after termination. +It is possible to re-run the controller by calling `launcher` from MATLAB prompt. Note that this will restart the controller only, not the whole simulation, so the current robot and motor positions will be preserved. -If you want to restart the whole simulation you need to use the `Reload` button as usual. +If you want to restart the whole simulation you need to use the `Reload` button in Webots as usual. diff --git a/docs/guide/running-extern-robot-controllers.md b/docs/guide/running-extern-robot-controllers.md index d46a273437f..4124678fc94 100644 --- a/docs/guide/running-extern-robot-controllers.md +++ b/docs/guide/running-extern-robot-controllers.md @@ -98,6 +98,10 @@ Concrete use cases are discussed in the [Setup](#setup) section. --robot-name= Target a specific robot by specifying its name in case multiple robots wait for an extern controller in the Webots instance. + --interactive + Launch MATLAB in interactive debugging mode. + See https://cyberbotics.com/doc/guide/matlab#using-the-matlab-desktop for more information. + --matlab-path= For MATLAB controllers, this option allows to specify the path to the executable of a specific MATLAB version. By default, the launcher checks in the default MATLAB installation folder. @@ -170,7 +174,11 @@ It is recommended that you do not override this `WEBOTS_TMPDIR` environment vari ### Running a MATLAB Extern Robot Controller Matlab controllers can also be started using the launcher. -By default, the launcher will look for the latest installed version of MATLAB in the following locations, depending on the OS: +By default, the new instance of MATLAB will be running in non-interactive ("batch") mode. +However, by providing the `--interactive` option, this can be overridden, which will cause the full desktop user interface to run. +See [this page](matlab.md) for more details on how to debug webots controllers using the MATLAB desktop. + +Regardless of mode, the launcher will look for the latest installed version of MATLAB in the following locations, depending on the OS: - **Windows**: C:\Program Files\MATLAB\R20XXx\bin\win64\MATLAB.exe - **Linux**: /usr/local/MATLAB/R20XXx/bin/matlab diff --git a/docs/js/showdown-extensions.js b/docs/js/showdown-extensions.js index 48e64065842..9b413517bcf 100644 --- a/docs/js/showdown-extensions.js +++ b/docs/js/showdown-extensions.js @@ -34,8 +34,8 @@ showdown.extension('wbVariables', function() { major: 'R2023b', // full is equal to major for the first major version // and contains the revision number for subsequent versions - full: 'R2023b', - package: 'R2023b' + full: 'R2023b revision 1', + package: 'R2023b-rev1' } }, date: { diff --git a/docs/reference/changelog-r2023.md b/docs/reference/changelog-r2023.md index d390ce097be..669cc558f76 100644 --- a/docs/reference/changelog-r2023.md +++ b/docs/reference/changelog-r2023.md @@ -1,5 +1,22 @@ # Webots R2023 Change Log +## Webots R2023b Revision 1 +Released on XXX XXth, 2023. + - New Devices and Objects + - Added a model of a silo and a field ditch ([#6289](https://github.com/cyberbotics/webots/pull/6289)). + - Enhancements + - Enabled the launching of MATLAB desktop from the extern launcher ([#6366](https://github.com/cyberbotics/webots/pull/6366)). + - Improved overlays visible in Overlays menu by adding all the robots in the menu list ([#6297](https://github.com/cyberbotics/webots/pull/6297)). + - Bug fixes + - Fixed errors loading template PROTO if the system user name, the project path, or the temporary directory path contains the `\` character ([#6288](https://github.com/cyberbotics/webots/pull/6288)). + - Fixed Webots and libController version comparison not to take revisions into account ([#6315](https://github.com/cyberbotics/webots/pull/6315)). + - Fixed translation, rotation and scale displayed in the Position tab of the Node viewer in the scene tree ([#6309](https://github.com/cyberbotics/webots/pull/6309)). + - Replaced the [Mesh](mesh.md) bounding object of the ROSbot XL by [Boxes](box.md) ([#6326](https://github.com/cyberbotics/webots/pull/6326)). + - Fixed a crash when [IndexedLineSet](indexedlineset.md) has `coord` but no `coordIndex` ([#6359](https://github.com/cyberbotics/webots/pull/6359)). + - Fixed values returned by the [Receiver.getEmitterDirection](https://cyberbotics.com/doc/reference/receiver?tab-language=python#wb_receiver_get_emitter_direction) Python method ([#6394](https://github.com/cyberbotics/webots/pull/6394)). + - Fixed recognition of omnidirectional cameras with fov > pi/2 in [WbObjectDetection] ([#6396](https://github.com/cyberbotics/webots/pull/6396)). + - Fixed [ElevationGrid](elevationgrid.md) collisions not matching the displayed when the x and y dimensions are different ([#6412](https://github.com/cyberbotics/webots/pull/6412)) + ## Webots R2023b Released on June 28th, 2023. - New Features @@ -104,8 +121,6 @@ Released on June 28th, 2023. - Fixed duplicate [Lidar](lidar.md) rotatingHead when exporting to URDF ([#6233](https://github.com/cyberbotics/webots/pull/6233)). - Dependency Updates - Upgraded to Qt6.4.3 on Ubuntu ([#6065](https://github.com/cyberbotics/webots/pull/6065)) and macOS ([#6157](https://github.com/cyberbotics/webots/pull/6157)). - - Cleanup - - Deprecated the C and MATLAB API functions `wb_supervisor_node_enable/disable_contact_point_tracking` in favor of `wb_supervisor_node_enable/disable_contact_points_tracking` to be more consistent with other APIs ([#5633](https://github.com/cyberbotics/webots/pull/5633)). ## Webots R2023a Released on November 29th, 2022. diff --git a/lib/controller/matlab/launcher.m b/lib/controller/matlab/launcher.m index 18860d6876d..455b7694948 100644 --- a/lib/controller/matlab/launcher.m +++ b/lib/controller/matlab/launcher.m @@ -181,9 +181,11 @@ if ~isvarname(WEBOTS_CONTROLLER_NAME) newname = matlab.lang.makeValidName(WEBOTS_CONTROLLER_NAME); copyfile(append(WEBOTS_CONTROLLER_NAME, '.m'), append(newname, '.m'), 'f'); + if desktop('-inuse'), dbstop('in', newname); end eval([newname, args]); delete(append(newname, '.m')); % delete temporary file else + if desktop('-inuse'), dbstop('in', WEBOTS_CONTROLLER_NAME); end eval([WEBOTS_CONTROLLER_NAME, args]); end diff --git a/lib/controller/python/controller/receiver.py b/lib/controller/python/controller/receiver.py index 11b60805f4b..24f3c964e19 100644 --- a/lib/controller/python/controller/receiver.py +++ b/lib/controller/python/controller/receiver.py @@ -91,8 +91,8 @@ def signal_strength(self) -> float: return wb.wb_receiver_get_signal_strength(self._tag) @property - def emitter_direction(self): - return wb.wb_receiver_get_emitter_direction(self._tag) + def emitter_direction(self) -> List[float]: + return wb.wb_receiver_get_emitter_direction(self._tag)[:3] @property def channel(self) -> int: diff --git a/projects/objects/buildings/protos/Silo.proto b/projects/objects/buildings/protos/Silo.proto new file mode 100644 index 00000000000..82d6b9a40a9 --- /dev/null +++ b/projects/objects/buildings/protos/Silo.proto @@ -0,0 +1,182 @@ +#VRML_SIM R2023b utf8 +# license: Copyright Cyberbotics Ltd. Licensed for use only with Webots. +# license url: https://cyberbotics.com/webots_assets_license +# documentation url: https://webots.cloud/run?url=https://github.com/cyberbotics/webots/blob/released/projects/objects/buildings/protos/Silo.proto +# keywords: building/farm +# A steel grain silo. +# template language: javascript + +EXTERNPROTO "webots://projects/appearances/protos/CorrugatedMetal.proto" + +PROTO Silo [ + field SFVec3f translation 0 0 0 # Is `Pose.translation`. + field SFRotation rotation 0 0 1 0 # Is `Pose.rotation`. + field SFFloat radius 3 # Defines the radius of the silo. + field SFFloat height 17 # Defines the height of the silo. + field SFFloat roofHeight 2 # Defines the height of the silo's roof. + field SFString name "silo" # Is `Solid.name`. + field SFBool enableBoundingObject TRUE # Defines whether the silo should have a bounding object. + field SFBool locked FALSE # Is `Solid.locked`. +] +{ + %< + let roofHeight = fields.roofHeight.value; + let height = fields.height.value; + let radius = fields.radius.value; + + if (radius <= 0) { + radius = fields.radius.defaultValue; + console.error('\'radius\' must be strictly positive. Value reset to ' + radius + '.'); + } + + if (height <= 2) { + height = fields.height.defaultValue; + console.error('\'height\' must be greater than 2. Value reset to ' + height + '.'); + } + + if (roofHeight <= 0 || roofHeight >= height) { + roofHeight = fields.roofHeight.defaultValue; + console.error('\'roofHeight\' must be strictly positive and lower than `height`. Value reset to ' + roofHeight + '.'); + } + + const cylinderHeight = height - roofHeight; + const ladderWidth = 0.7; + const ladderHeight = cylinderHeight * 2 / 3; + const ladderStepSize = 0.5; + const ladderSteps = Math.trunc(ladderHeight / ladderStepSize); + const barHeight = ladderHeight + ladderStepSize; + >% + Solid { + translation IS translation + rotation IS rotation + children [ + DEF BODY Pose { + translation 0 0 %<= cylinderHeight * 0.5 >% + children [ + Shape { + appearance CorrugatedMetal { + colorOverride 1 0.9696 0.82 + textureTransform TextureTransform { + rotation 1.5708 + scale %<= cylinderHeight>% %<= 3 * radius >% + } + } + geometry Cylinder { + height %<= cylinderHeight >% + radius %<= radius >% + } + } + ] + } + DEF ROOF Pose { + translation 0 0 %<= cylinderHeight + roofHeight * 0.5 >% + children [ + Shape { + appearance CorrugatedMetal { + colorOverride 1 0.9696 0.82 + textureTransform TextureTransform { + rotation 1.5708 + scale %<= radius >% %<= 3 * roofHeight>% + } + } + geometry Cone { + bottomRadius %<= radius >% + height %<= roofHeight >% + subdivision 40 + } + } + ] + } + DEF LADDER Pose { + translation %<= radius + 0.04 >% 0 0 + children [ + DEF LEFT_POLE Pose { + translation 0 %<= -ladderWidth * 0.5 - 0.025 >% %<= cylinderHeight + 0.1 + ladderStepSize - barHeight * 0.5 >% + children [ + DEF SIDE_POLE Shape { + appearance DEF LADDER_APPEARANCE PBRAppearance { + baseColor 0.603922 0.6 0.588235 + roughness 0.8 + } + geometry Box { + size 0.05 0.05 %<= barHeight >% + } + } + ] + } + DEF RIGHT_POLE Pose { + translation 0 %<= ladderWidth * 0.5 + 0.025 >% %<= cylinderHeight + 0.1 + ladderStepSize - barHeight * 0.5 >% + children [ + USE SIDE_POLE + ] + } + DEF LEFT_TOP_POLE Pose { + translation %<= -radius * 0.5 - 0.025 >% %<= -ladderWidth * 0.5 - 0.025 >% %<= cylinderHeight + 0.3 >% + children [ + DEF TOP_POLE Shape { + appearance DEF LADDER_APPEARANCE PBRAppearance { + baseColor 0.603922 0.6 0.588235 + roughness 0.8 + } + geometry Box { + size %<= radius >% 0.05 0.1 + } + } + ] + } + DEF RIGHT_TOP_POLE Pose { + translation %<= -radius * 0.5 - 0.025 >% %<= ladderWidth * 0.5 + 0.025 >% %<= cylinderHeight + 0.3 >% + children [ + USE TOP_POLE + ] + } + Pose { + translation 0 0 %<= cylinderHeight + 0.1 >% + children [ + DEF FOOL Shape { + appearance USE LADDER_APPEARANCE + geometry Box { + size 0.05 %<= ladderWidth >% 0.05 + } + } + ] + } + %< + let h = cylinderHeight + 0.1; + for (let n = 1; n < ladderSteps; n++) { + h -= ladderStepSize; + >% + Pose { + translation 0 0 %<= h >% + children [ + USE FOOL + ] + } + %< + } + >% + ] + } + ] + name IS name + %< + if (fields.enableBoundingObject.value) { + >% + boundingObject Pose { + translation 0 0 %<= height * 0.5>% + children [ + Cylinder { + height %<= height >% + radius %<= radius >% + } + ] + } + %< + } + >% + locked IS locked + recognitionColors [ + 0.89 0.89 0.89 + ] + } +} diff --git a/projects/objects/buildings/protos/icons/Silo.png b/projects/objects/buildings/protos/icons/Silo.png new file mode 100644 index 00000000000..2208a660828 Binary files /dev/null and b/projects/objects/buildings/protos/icons/Silo.png differ diff --git a/projects/objects/factory/manhole/protos/SquareManhole.proto b/projects/objects/factory/manhole/protos/SquareManhole.proto index 75725b9d677..d0045d23749 100644 --- a/projects/objects/factory/manhole/protos/SquareManhole.proto +++ b/projects/objects/factory/manhole/protos/SquareManhole.proto @@ -21,7 +21,6 @@ PROTO SquareManhole [ model "manhole" children [ Pose { - rotation 0 0 1 1.57 children [ Shape { appearance PBRAppearance { diff --git a/projects/objects/factory/pipes/protos/LJoint.proto b/projects/objects/factory/pipes/protos/LJoint.proto index 51f02565dd4..2a61e1f775e 100644 --- a/projects/objects/factory/pipes/protos/LJoint.proto +++ b/projects/objects/factory/pipes/protos/LJoint.proto @@ -4,6 +4,7 @@ # documentation url: https://webots.cloud/run?url=https://github.com/cyberbotics/webots/blob/released/projects/objects/factory/pipes/protos/LJoint.proto # keywords: industrial/plumbing # 90-degree L-joint for connecting pipes. +# template language: javascript EXTERNPROTO "webots://projects/appearances/protos/OldSteel.proto" @@ -15,6 +16,11 @@ PROTO LJoint [ field SFNode appearance OldSteel { textureTransform TextureTransform { rotation 0.78 scale 2 2 } } # Defines the appearance of the pipe. ] { +%< + const scaleX = fields.scale.value.x; + const scaleY = fields.scale.value.y; + const scaleZ = fields.scale.value.z; +>% Solid { translation IS translation rotation IS rotation @@ -325,33 +331,33 @@ PROTO LJoint [ boundingObject Group { children [ Pose { - translation 0.031098346 0 -0.036 + translation %<= scaleX * 0.031098346 >% 0 %<= scaleZ * -0.036 >% rotation 0 1 0 1.8325997 children [ Capsule { - height 0.04 - radius 0.035 + height %<= scaleX * 0.04 >% + radius %<= scaleX * 0.035 >% } ] } Pose { - translation -0.036079734 0 0.029794677 + translation %<= scaleX * -0.036079734 >% 0 %<= scaleZ * 0.029794677 >% rotation 0 -1 0 0.2 children [ Capsule { - height 0.04 - radius 0.035 + height %<= scaleX * 0.04 >% + radius %<= scaleX * 0.035 >% } ] translationStep 0.001 } Pose { - translation -0.019555909 0 -0.0041774259 + translation %<= scaleX * -0.019555909 >% 0 %<= scaleZ * -0.0041774259 >% rotation 0 1 0 -0.59269969 children [ Capsule { - height 0.04 - radius 0.035 + height %<= scaleX * 0.04 >% + radius %<= scaleX * 0.035 >% } ] rotationStep 0.01 diff --git a/projects/objects/floors/protos/Ditch.proto b/projects/objects/floors/protos/Ditch.proto new file mode 100644 index 00000000000..514c9a9d53e --- /dev/null +++ b/projects/objects/floors/protos/Ditch.proto @@ -0,0 +1,170 @@ +#VRML_SIM R2023b utf8 +# license: Copyright Cyberbotics Ltd. Licensed for use only with Webots. +# license url: https://cyberbotics.com/webots_assets_license +# documentation url: https://webots.cloud/run?url=https://github.com/cyberbotics/webots/blob/released/projects/objects/floors/protos/Ditch.proto +# tags: nonDeterministic +# keywords: primitive/ground +# Randomly generated ditch with optional water. +# template language: javascript + +EXTERNPROTO "webots://projects/appearances/protos/SandyGround.proto" + +PROTO Ditch [ + field SFVec3f translation 0 0 0 # Is `Pose.translation`. + field SFRotation rotation 0 0 1 0 # Is `Solid.rotation`. + field SFString name "ditch" # Is `Solid.name`. + field SFVec3f size 20 5 2 # Defines the size of the ditch. + field SFFloat waterHeight 0.3 # Defines the height of the water in the ditch. + field SFNode appearance SandyGround { textureTransform TextureTransform { scale 5 5 } } # Defines the appearance of the terrain. + field SFInt32 randomSeed 1 # Defines whether the bounds of the terrain should be flat. +] +{ + %< + // reference: https://stackoverflow.com/a/42543313/2210777 + import * as wbrandom from 'wbrandom.js'; + + if (fields.randomSeed.value <= 0) + wbrandom.seed(Date.now()); + else + wbrandom.seed(fields.randomSeed.value); + + function gaussian2d(x, y, amp, cx, cy, sx, sy) { + return amp * Math.exp( - ( ( Math.pow(x - cx, 2) / (2.0 * Math.pow(sx, 2)) ) + (Math.pow(y - cy, 2) / (2.0 * Math.pow(sy, 2)) ) )); + } + + // create 256 shuffled numbers repeated twice. + let perm = []; + for (let i = 1; i <= 256; ++i) + perm.splice(wbrandom.integer(i) -1, 0, i); + + for (let i = 0; i < 256; ++i) + perm.push(perm[i]); + + // generate 256 directions + let dirs = []; + for (let a = 0; a < 256; ++a) + dirs.push([Math.cos(a * 2.0 * Math.PI / 256), Math.sin(a * 2.0 * Math.PI / 256)]); + + function noise (x, y, per, dirs, perm) { + function surflet (grid_x, grid_y, dirs, perm) { + const dist_x = Math.abs(x - grid_x); + const dist_y = Math.abs(y - grid_y); + const poly_x = 1 - 6 * Math.pow(dist_x, 5) + 15 * Math.pow(dist_x, 4) - 10 * Math.pow(dist_x, 3); + const poly_y = 1 - 6 * Math.pow(dist_y, 5) + 15 * Math.pow(dist_y, 4) - 10 * Math.pow(dist_y, 3); + const hashed = perm[(perm[(Math.floor(grid_x) % per)] + Math.floor(grid_y) % per)]; + const grad = (x - grid_x) * dirs[hashed-1][0] + (y - grid_y) * dirs[hashed-1][1]; + + return poly_x * poly_y * grad; + } + + let int_x = Math.floor(x); + let int_y = Math.floor(y); + + return surflet(int_x + 0, int_y + 0, dirs, perm) + surflet(int_x + 1, int_y + 0, dirs, perm) + surflet(int_x + 0, int_y + 1, dirs, perm) + surflet(int_x + 1, int_y + 1, dirs, perm); + } + + function fBm (x, y, per, octs, dirs, perm) { // Fractional Brownian motion + let val = 0; + for (let o = 0; o <= octs - 1; ++o) + val = val + (Math.pow(0.5, o) * noise(x * Math.pow(2, o), y * Math.pow(2, o), per * Math.pow(2, o), dirs, perm)); + + return val; + } + + const perlinSize = 128; + const nOctave = 3; + + let size = fields.size.value; + if (size.x <= 0 || size.y <= 0 || size.z <= 0) { + size = fields.size.defaultValue; + console.error('\'size\' must be strictly positive. Value reset to ' + size + '.'); + } + + let yDimension = 10; + const hValues = [0, 0, -size.z * 0.5, -size.z, -size.z, -size.z, -size.z, -size.z * 0.5, 0, 0]; + + let xDimension = Math.floor(size.x / 0.5); + if (xDimension < 8) + xDimension = 2; + + const ySpacing = size.y / (yDimension - 1); + + let waterHeight = fields.waterHeight.value; + if (waterHeight >= size.z) { + waterHeight = fields.waterHeight.defaultValue; + console.error('\'waterHeight\' should be lower than `size.z`. Value reset to ' + waterHeight + '.'); + } + + let heights = [] + let minHeight = 0; + for (let i = 0; i < 9; ++i) { + const x = i / xDimension; + for (let j = 0; j < xDimension; ++j) { + let height = hValues[i]; + if (i > 0 && i < 7 && nOctave > 0) { + const y = j / yDimension; + height += 0.2 * fBm(x, y, perlinSize, nOctave, dirs, perm); + } + if (height < minHeight) + minHeight = height; + heights.push(height); + } + } + >% + %< if (waterHeight >= 0) { >% + Fluid { + %< } else { >% + Solid { + %< } >% + translation IS translation + rotation IS rotation + children [ + %< if (waterHeight >= 0) { >% + DEF WATER_BOX Pose { + translation 0 0 %<= minHeight + waterHeight * 0.5>% + children [ + Shape { + appearance PBRAppearance { + baseColor 0.30985 0.464424 0.636667 + transparency 0.3 + roughness 0.05 + metalness 0.9 + } + geometry Box { + size %<= size.x >% %<= size.y - 2 * ySpacing >% %<= waterHeight >% + } + } + ] + } + Solid { + children [ + %< } >% + DEF DITCH_ELEVATION_GRID Pose { + translation %<= - size.x * 0.5 >% %<= -size.y * 0.5 >% 0 + children [ + Shape { + appearance IS appearance + geometry ElevationGrid { + xDimension %<= xDimension >% + xSpacing %<= size.x / (xDimension - 1) >% + yDimension %<= yDimension >% + ySpacing %<= ySpacing >% + height [ + %<= heights.join(', ') >% + ] + } + } + ] + } + ] + name IS name + model "ditch" + boundingObject USE DITCH_ELEVATION_GRID + } + %< if (waterHeight >= 0) { >% + ] + viscosity 0.01 + boundingObject USE WATER_BOX + } + %< } >% +} diff --git a/projects/objects/floors/protos/icons/Ditch.png b/projects/objects/floors/protos/icons/Ditch.png new file mode 100644 index 00000000000..f08c4de66a4 Binary files /dev/null and b/projects/objects/floors/protos/icons/Ditch.png differ diff --git a/projects/robots/husarion/rosbot_xl/protos/RosbotXl.proto b/projects/robots/husarion/rosbot_xl/protos/RosbotXl.proto index cd73b8c5821..fce087c6545 100644 --- a/projects/robots/husarion/rosbot_xl/protos/RosbotXl.proto +++ b/projects/robots/husarion/rosbot_xl/protos/RosbotXl.proto @@ -40,13 +40,43 @@ PROTO RosbotXl [ -2.000608e-04 -9.754257e-04 -2.603326e-04 ] } - boundingObject DEF body_collision Group { + boundingObject DEF BODY_COLLISION Group { children [ - DEF MAIN_BODY_BOX Pose { - translation 0 0 0.048 + DEF LEFT_BODY_COLLISION Pose { + translation -0.005 0.115 0.117 children [ - Mesh { - url "meshes/body_collision.stl" + DEF SIDE_BODY_BOX Box { + size 0.27 0.02 0.03 + } + ] + } + DEF RIGHT_BODY_COLLISION Pose { + translation -0.005 -0.115 0.117 + children [ + USE SIDE_BODY_BOX + ] + } + DEF CENTER_BODY_COLLISION Pose { + translation -0.005 0 0.082 + children [ + Box { + size 0.27 0.21 0.099 + } + ] + } + DEF FRONT_BODY_COLLISION Pose { + translation 0.146 0 0.074 + children [ + Box { + size 0.032 0.21 0.069 + } + ] + } + DEF BACK_BODY_COLLISION Pose { + translation -0.1536 0 0.082 + children [ + Box { + size 0.027 0.27 0.099 } ] } diff --git a/projects/robots/pal_robotics/tiago_extensions/protos/TiagoRobotiqGripper.proto b/projects/robots/pal_robotics/tiago_extensions/protos/TiagoRobotiqGripper.proto index c94ffe5039f..7f8bff87766 100644 --- a/projects/robots/pal_robotics/tiago_extensions/protos/TiagoRobotiqGripper.proto +++ b/projects/robots/pal_robotics/tiago_extensions/protos/TiagoRobotiqGripper.proto @@ -12,7 +12,7 @@ EXTERNPROTO "webots://projects/devices/robotiq/protos/Robotiq2f140Gripper.proto" EXTERNPROTO "webots://projects/devices/robotiq/protos/RobotiqEPickGripper.proto" PROTO TiagoRobotiqGripper [ - field SFVec3f translation 0 0.013 0 + field SFVec3f translation 0 0.032 0 field SFRotation rotation 0 0.7071 0.7071 3.14159 field SFString{"2F-85", "2F-140", "EPick"} model "2F-85" field SFString{"front", "left", "right"} side "front" @@ -42,12 +42,11 @@ PROTO TiagoRobotiqGripper [ rotation IS rotation children [ DEF COUPLING Pose { - translation 0 0 -0.0041 + translation 0 -0.0003 -0.0117 children [ Shape { - geometry Cylinder { - height 0.0082 - radius 0.023 + geometry Mesh { + url "meshes/pal_robotiq_coupling.stl" } appearance DEF BLACK_METAL PBRAppearance { baseColor 0 0 0 diff --git a/projects/robots/pal_robotics/tiago_extensions/protos/meshes/pal_robotiq_coupling.stl b/projects/robots/pal_robotics/tiago_extensions/protos/meshes/pal_robotiq_coupling.stl new file mode 100644 index 00000000000..a6f5ea13fb0 Binary files /dev/null and b/projects/robots/pal_robotics/tiago_extensions/protos/meshes/pal_robotiq_coupling.stl differ diff --git a/projects/robots/robotis/darwin-op/controllers/symmetry_matlab/symmetry_matlab.m b/projects/robots/robotis/darwin-op/controllers/symmetry_matlab/symmetry_matlab.m index 1bf6742da11..304f93973d1 100644 --- a/projects/robots/robotis/darwin-op/controllers/symmetry_matlab/symmetry_matlab.m +++ b/projects/robots/robotis/darwin-op/controllers/symmetry_matlab/symmetry_matlab.m @@ -1,11 +1,6 @@ % Description: MATLAB controller example for Webots function symmetry_matlab -% uncomment the next two lines if you want to use -% MATLAB's desktop and interact with the controller -%desktop; -%keyboard; - TIME_STEP = wb_robot_get_basic_time_step(); right_shoulder_motor = wb_robot_get_device('ShoulderR'); diff --git a/projects/robots/softbank/nao/controllers/supervisor_matlab/supervisor_matlab.m b/projects/robots/softbank/nao/controllers/supervisor_matlab/supervisor_matlab.m index c172f97111b..317ca0b6560 100644 --- a/projects/robots/softbank/nao/controllers/supervisor_matlab/supervisor_matlab.m +++ b/projects/robots/softbank/nao/controllers/supervisor_matlab/supervisor_matlab.m @@ -5,11 +5,6 @@ % -MATLAB must be installed and the "matlab" command must be in the PATH environment variable function supervisor_matlab -% uncomment the next two lines if you want to use -% MATLAB's desktop to interact with this controller: -%desktop; -%keyboard; - % controller time step TIME_STEP = 40; diff --git a/projects/samples/howto/force_control/controllers/force_control_matlab/force_control_matlab.m b/projects/samples/howto/force_control/controllers/force_control_matlab/force_control_matlab.m index 28bb978d0af..270608dedd9 100644 --- a/projects/samples/howto/force_control/controllers/force_control_matlab/force_control_matlab.m +++ b/projects/samples/howto/force_control/controllers/force_control_matlab/force_control_matlab.m @@ -1,14 +1,8 @@ -% % Example of spring and dampers simulation implemented with force control % To try this example you need to change the Robot.controller field % to "force_control_matlab" in the force_control.wbt example -% -function force_control_matlab -% uncomment the next two lines if you want to use -% MATLAB's desktop to interact with the controller: -%desktop; -%keyboard; +function force_control_matlab CONTROL_STEP = 4; SPRING_CONSTANT = 40; diff --git a/projects/samples/howto/url/controllers/url/url.c b/projects/samples/howto/url/controllers/url/url.c index fa62d137ca3..f17b6b76b80 100644 --- a/projects/samples/howto/url/controllers/url/url.c +++ b/projects/samples/howto/url/controllers/url/url.c @@ -53,7 +53,7 @@ int main() { printf("The texture URL of the robot body was changed from the supervisor to a non-existing file.\n"); } else if (counter == 160) { wb_supervisor_field_set_mf_string(url, 0, - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/appearances/" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/" "protos/textures/brushed_steel/brushed_steel_base_color.jpg"); printf("The texture URL of the robot body was changed from the supervisor to an existing URL.\n"); } diff --git a/projects/samples/howto/url/worlds/url.wbt b/projects/samples/howto/url/worlds/url.wbt index 77e1a6191d1..5700bb37ad9 100644 --- a/projects/samples/howto/url/worlds/url.wbt +++ b/projects/samples/howto/url/worlds/url.wbt @@ -11,9 +11,9 @@ WorldInfo { title "Assets Download" contactProperties [ ContactProperties { - bumpSound "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/sounds/bump.wav" - rollSound "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/sounds/roll.wav" - slideSound "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/sounds/slide.wav" + bumpSound "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/sounds/bump.wav" + rollSound "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/sounds/roll.wav" + slideSound "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/sounds/slide.wav" } ] } @@ -23,40 +23,40 @@ Viewpoint { } Background { backUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_back.png" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_back.jpg" ] bottomUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2022b/projects/default/worlds/textures/cubic/mountains_bottom.jpg" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_bottom.jpg" ] frontUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_front.png" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_front.jpg" ] leftUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_left.png" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_left.jpg" ] rightUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_right.png" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_right.jpg" ] topUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_top.png" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_top.jpg" ] backIrradianceUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_back.hdr" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_back.hdr" ] bottomIrradianceUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_bottom.hdr" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_bottom.hdr" ] frontIrradianceUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_front.hdr" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_front.hdr" ] leftIrradianceUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_left.hdr" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_left.hdr" ] rightIrradianceUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_right.hdr" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_right.hdr" ] topIrradianceUrl [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/textures/cubic/mountains_top.hdr" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/cubic/mountains_top.hdr" ] } TexturedBackgroundLight { @@ -172,7 +172,7 @@ DEF MESH Transform { } geometry Mesh { url [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/meshes/suzanne.obj" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/meshes/suzanne.obj" ] } } @@ -190,27 +190,27 @@ Robot { baseColor 0.8039 0.6745 0.5764 baseColorMap DEF IMAGE_TEXTURE ImageTexture { url [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/appearances/protos/textures/varnished_pine/varnished_pine_base_color.jpg" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/textures/varnished_pine/varnished_pine_base_color.jpg" ] filtering 5 } roughnessMap ImageTexture { url [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/appearances/protos/textures/varnished_pine/varnished_pine_roughness.jpg" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/textures/varnished_pine/varnished_pine_roughness.jpg" ] filtering 5 } metalness 0 normalMap ImageTexture { url [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/appearances/protos/textures/varnished_pine/varnished_pine_normal.jpg" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/textures/varnished_pine/varnished_pine_normal.jpg" ] filtering 5 } normalMapFactor 0.5 occlusionMap ImageTexture { url [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/appearances/protos/textures/varnished_pine/varnished_pine_occlusion.jpg" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/textures/varnished_pine/varnished_pine_occlusion.jpg" ] filtering 5 } @@ -231,7 +231,7 @@ Robot { RotationalMotor { name "left wheel motor" consumptionFactor 70 - sound "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/sounds/rotational_motor.wav" + sound "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/sounds/rotational_motor.wav" } PositionSensor { name "left wheel sensor" @@ -275,7 +275,7 @@ Robot { RotationalMotor { name "right wheel motor" consumptionFactor 70 - sound "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/default/worlds/sounds/rotational_motor.wav" + sound "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/sounds/rotational_motor.wav" } PositionSensor { name "right wheel sensor" @@ -380,7 +380,7 @@ Robot { width 400 height 300 antiAliasing TRUE - noiseMaskUrl "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/projects/samples/devices/worlds/textures/noise_mask.png" + noiseMaskUrl "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/samples/devices/worlds/textures/noise_mask.png" } ] name "MyBot" diff --git a/projects/vehicles/worlds/boomer.wbt b/projects/vehicles/worlds/boomer.wbt index 682dc768790..f4ced93e941 100644 --- a/projects/vehicles/worlds/boomer.wbt +++ b/projects/vehicles/worlds/boomer.wbt @@ -17,6 +17,10 @@ EXTERNPROTO "webots://projects/objects/animals/protos/Cat.proto" EXTERNPROTO "webots://projects/objects/animals/protos/Horse.proto" EXTERNPROTO "webots://projects/objects/animals/protos/Cow.proto" EXTERNPROTO "webots://projects/objects/animals/protos/Sheep.proto" +EXTERNPROTO "webots://projects/objects/buildings/protos/Silo.proto" +EXTERNPROTO "webots://projects/objects/floors/protos/Ditch.proto" +EXTERNPROTO "webots://projects/appearances/protos/SandyGround.proto" +EXTERNPROTO "webots://projects/appearances/protos/FormedConcrete.proto" WorldInfo { info [ @@ -43,6 +47,7 @@ TexturedBackground { TexturedBackgroundLight { } DEF FLOOR Solid { + translation 0 -259.88 0 children [ DEF PLANE_SHAPE Shape { appearance PBRAppearance { @@ -58,13 +63,162 @@ DEF FLOOR Solid { } } geometry Plane { - size 1000 1000 + size 1000 600 } castShadows FALSE } ] + name "floor" + boundingObject Pose { + translation 0 0 -0.025 + children [ + Box { + size 1000 600 0.05 + } + ] + } +} +DEF FLOOR Solid { + translation 0 245 0 + children [ + DEF PLANE_SHAPE Shape { + appearance PBRAppearance { + baseColorMap ImageTexture { + url [ + "webots://projects/default/worlds/textures/dry_grass.jpg" + ] + } + roughness 1 + metalness 0 + textureTransform TextureTransform { + scale 200 200 + } + } + geometry Plane { + size 1000 400 + } + castShadows FALSE + } + ] + name "floor(1)" + boundingObject Pose { + translation 0 0 -0.025 + children [ + Box { + size 1000 400 0.05 + } + ] + } +} +DEF FLOOR Solid { + translation -350 42.59 -1 + children [ + DEF FLOOR_SHAPE Shape { + appearance PBRAppearance { + baseColorMap ImageTexture { + url [ + "webots://projects/default/worlds/textures/dry_grass.jpg" + ] + } + roughness 1 + metalness 0 + textureTransform TextureTransform { + scale 100 2 + } + } + geometry Box { + size 300 5 2 + } + castShadows FALSE + } + ] + name "floor(3)" + boundingObject USE PLANE_SHAPE +} +DEF FLOOR Solid { + translation 350 42.59 -1 + children [ + DEF FLOOR_SHAPE Shape { + appearance PBRAppearance { + baseColorMap ImageTexture { + url [ + "webots://projects/default/worlds/textures/dry_grass.jpg" + ] + } + roughness 1 + metalness 0 + textureTransform TextureTransform { + scale 100 2 + } + } + geometry Box { + size 300 5 2 + } + castShadows FALSE + } + ] + name "floor(4)" boundingObject USE PLANE_SHAPE } +Ditch { + translation 0 42.59 0 + size 400 5 1 + waterHeight 0.5 + appearance SandyGround { + textureTransform TextureTransform { + scale 84 5 + } + } +} +Solid { + translation 36.45 42.83 0 + rotation 0 0 1 1.5708 + children [ + DEF RAMP Group { + children [ + Pose { + translation 0 0 0.05 + children [ + Shape { + appearance DEF BRIDGE_APPEARANCE FormedConcrete { + } + geometry Box { + size 6 7 0.1 + } + } + ] + } + Pose { + translation 3.06 0 0 + rotation 0 1 0 0.5236 + children [ + Shape { + appearance USE BRIDGE_APPEARANCE + geometry Box { + size 0.2 7 0.1 + } + } + ] + } + Pose { + translation -3.06 0 0 + rotation 0 1 0 -0.5236 + children [ + Shape { + appearance USE BRIDGE_APPEARANCE + geometry Box { + size 0.2 7 0.1 + } + } + ] + } + ] + } + ] + name "bridge" + model "bridge" + boundingObject USE RAMP +} Sheep { translation 6.24 -4.94 0 rotation 0 0 1 -0.785395307179586 @@ -204,6 +358,9 @@ Barn { translation 0 -6.47 0 rotation 0 0 1 4.692820414042842e-06 } +Silo { + translation -2.75 6.28 0 +} DEF BOOMER_T3050 Tractor { translation 2.79493 -17.8 0.609281 controller "boomer" diff --git a/resources/projects/libraries/qt_utils/motion_editor/MotionWidget.cpp b/resources/projects/libraries/qt_utils/motion_editor/MotionWidget.cpp index 15578a0dd87..2d3af7f0070 100644 --- a/resources/projects/libraries/qt_utils/motion_editor/MotionWidget.cpp +++ b/resources/projects/libraries/qt_utils/motion_editor/MotionWidget.cpp @@ -134,7 +134,7 @@ void MotionWidget::setItemAppearance(QListWidgetItem *item, Pose::Status status) QColor color("black"); if (status == Pose::INVALID) - color.setNamedColor("red"); + color.fromString("red"); item->setForeground(QBrush(color)); } diff --git a/resources/projects/libraries/qt_utils/motion_editor/PoseWidget.cpp b/resources/projects/libraries/qt_utils/motion_editor/PoseWidget.cpp index a962b3d0a1d..0df421942e7 100644 --- a/resources/projects/libraries/qt_utils/motion_editor/PoseWidget.cpp +++ b/resources/projects/libraries/qt_utils/motion_editor/PoseWidget.cpp @@ -166,10 +166,10 @@ void PoseWidget::setItemAppearance(QListWidgetItem *item, MotorTargetState::Stat QColor color("black"); switch (status) { case MotorTargetState::DISABLED: - color.setNamedColor("dimgray"); + color.fromString("dimgray"); break; case MotorTargetState::INVALID: - color.setNamedColor("red"); + color.fromString("red"); break; default: break; diff --git a/resources/translations/wb_de.ts b/resources/translations/wb_de.ts index 6685d1ba65c..65b4d582943 100644 --- a/resources/translations/wb_de.ts +++ b/resources/translations/wb_de.ts @@ -5653,7 +5653,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/translations/wb_es.ts b/resources/translations/wb_es.ts index e34fa081ee7..a32fe66fe3a 100644 --- a/resources/translations/wb_es.ts +++ b/resources/translations/wb_es.ts @@ -5650,7 +5650,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/translations/wb_fr.ts b/resources/translations/wb_fr.ts index 8c934603f29..4d5d96b49d5 100644 --- a/resources/translations/wb_fr.ts +++ b/resources/translations/wb_fr.ts @@ -5655,7 +5655,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/translations/wb_generic.ts b/resources/translations/wb_generic.ts index 12222216870..0e0c04aaf3e 100644 --- a/resources/translations/wb_generic.ts +++ b/resources/translations/wb_generic.ts @@ -5650,7 +5650,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/translations/wb_it.ts b/resources/translations/wb_it.ts index c5e9068cbce..b9c8e98fd9e 100644 --- a/resources/translations/wb_it.ts +++ b/resources/translations/wb_it.ts @@ -5670,7 +5670,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/translations/wb_ja_JP.ts b/resources/translations/wb_ja_JP.ts index 29348300811..8a1f3479360 100644 --- a/resources/translations/wb_ja_JP.ts +++ b/resources/translations/wb_ja_JP.ts @@ -5650,7 +5650,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/translations/wb_zh_CN.ts b/resources/translations/wb_zh_CN.ts index 13816cf43d0..dd620283750 100644 --- a/resources/translations/wb_zh_CN.ts +++ b/resources/translations/wb_zh_CN.ts @@ -5650,7 +5650,7 @@ screenshot of the world in .jpg format when the it is saved, shared or exported. - Please adapt your project to R2023a following these instructions: https://cyberbotics.com/doc/guide/from-2022a-to-2022b + Please adapt your project to R2023b following these instructions: https://cyberbotics.com/doc/guide/from-2023a-to-2023b diff --git a/resources/version.txt b/resources/version.txt index d0235d228a2..0b9133bdfb4 100644 --- a/resources/version.txt +++ b/resources/version.txt @@ -1 +1 @@ -R2023b +R2023b revision 1 diff --git a/resources/web/wwi/FloatingProtoParameterWindow.js b/resources/web/wwi/FloatingProtoParameterWindow.js index 7348078058f..ddf3889d446 100644 --- a/resources/web/wwi/FloatingProtoParameterWindow.js +++ b/resources/web/wwi/FloatingProtoParameterWindow.js @@ -1,43 +1,14 @@ import FloatingWindow from './FloatingWindow.js'; -import {VRML} from './protoVisualizer/vrml_type.js'; -import WbAccelerometer from './nodes/WbAccelerometer.js'; -import WbAltimeter from './nodes/WbAltimeter.js'; -import WbBallJoint from './nodes/WbBallJoint.js'; -import WbCamera from './nodes/WbCamera.js'; -import WbCharger from './nodes/WbCharger.js'; -import WbCompass from './nodes/WbCompass.js'; -import WbConnector from './nodes/WbConnector.js'; +import { VRML } from './protoVisualizer/vrml_type.js'; import WbDevice from './nodes/WbDevice.js'; -import WbDisplay from './nodes/WbDisplay.js'; -import WbDistanceSensor from './nodes/WbDistanceSensor.js'; -import WbEmitter from './nodes/WbEmitter.js'; -import WbGps from './nodes/WbGps.js'; -import WbGyro from './nodes/WbGyro.js'; -import WbHingeJoint from './nodes/WbHingeJoint.js'; -import WbHinge2Joint from './nodes/WbHinge2Joint.js'; -import WbInertialUnit from './nodes/WbInertialUnit.js'; import WbJoint from './nodes/WbJoint.js'; -import WbLed from './nodes/WbLed.js'; -import WbLidar from './nodes/WbLidar.js'; -import WbLinearMotor from './nodes/WbLinearMotor.js'; -import WbLightSensor from './nodes/WbLightSensor.js'; import WbMotor from './nodes/WbMotor.js'; -import WbPen from './nodes/WbPen.js'; -import WbRadar from './nodes/WbRadar.js'; -import WbReceiver from './nodes/WbReceiver.js'; -import WbRotationalMotor from './nodes/WbRotationalMotor.js'; -import WbSpeaker from './nodes/WbSpeaker.js'; import WbWorld from './nodes/WbWorld.js'; -import WbRangeFinder from './nodes/WbRangeFinder.js'; -import WbTouchSensor from './nodes/WbTouchSensor.js'; -import WbVacuumGripper from './nodes/WbVacuumGripper.js'; -import WbBrake from './nodes/WbBrake.js'; -import WbPositionSensor from './nodes/WbPositionSensor.js'; import NodeSelectorWindow from './NodeSelectorWindow.js'; -import {SFNode, MFNode, vrmlFactory} from './protoVisualizer/Vrml.js'; +import { SFNode, MFNode, vrmlFactory } from './protoVisualizer/Vrml.js'; import Node from './protoVisualizer/Node.js'; -import WbPropeller from './nodes/WbPropeller.js'; import WbVector4 from './nodes/utils/WbVector4.js'; +import { WbNodeType } from './nodes/wb_node_type.js'; export default class FloatingProtoParameterWindow extends FloatingWindow { #mfId; @@ -1445,49 +1416,50 @@ export default class FloatingProtoParameterWindow extends FloatingWindow { } #sortDevice(device, div) { - if (device instanceof WbAccelerometer) + const nodeType = device.nodeType; + if (nodeType === WbNodeType.WB_NODE_ACCELEROMETER) this.devicesList.get('Accelerometer').push(div); - else if (device instanceof WbAltimeter) + else if (nodeType === WbNodeType.WB_NODE_ALTIMETER) this.devicesList.get('Altimeter').push(div); - else if (device instanceof WbCamera) + else if (nodeType === WbNodeType.WB_NODE_CAMERA) this.devicesList.get('Camera').push(div); - else if (device instanceof WbCharger) + else if (nodeType === WbNodeType.WB_NODE_CHARGER) this.devicesList.get('Charger').push(div); - else if (device instanceof WbCompass) + else if (nodeType === WbNodeType.WB_NODE_COMPASS) this.devicesList.get('Compass').push(div); - else if (device instanceof WbConnector) + else if (nodeType === WbNodeType.WB_NODE_CONNECTOR) this.devicesList.get('Connector').push(div); - else if (device instanceof WbDisplay) + else if (nodeType === WbNodeType.WB_NODE_DISPLAY) this.devicesList.get('Display').push(div); - else if (device instanceof WbDistanceSensor) + else if (nodeType === WbNodeType.WB_NODE_DISTANCE_SENSOR) this.devicesList.get('DistanceSensor').push(div); - else if (device instanceof WbEmitter) + else if (nodeType === WbNodeType.WB_NODE_EMITTER) this.devicesList.get('Emitter').push(div); - else if (device instanceof WbGps) + else if (nodeType === WbNodeType.WB_NODE_GPS) this.devicesList.get('Gps').push(div); - else if (device instanceof WbGyro) + else if (nodeType === WbNodeType.WB_NODE_GYRO) this.devicesList.get('Gyro').push(div); - else if (device instanceof WbInertialUnit) + else if (nodeType === WbNodeType.WB_NODE_INERTIAL_UNIT) this.devicesList.get('InertialUnit').push(div); - else if (device instanceof WbLed) + else if (nodeType === WbNodeType.WB_NODE_LED) this.devicesList.get('Led').push(div); - else if (device instanceof WbLidar) + else if (nodeType === WbNodeType.WB_NODE_LIDAR) this.devicesList.get('Lidar').push(div); - else if (device instanceof WbLightSensor) + else if (nodeType === WbNodeType.WB_NODE_LIGHT_SENSOR) this.devicesList.get('LightSensor').push(div); - else if (device instanceof WbPen) + else if (nodeType === WbNodeType.WB_NODE_PEN) this.devicesList.get('Pen').push(div); - else if (device instanceof WbRadar) + else if (nodeType === WbNodeType.WB_NODE_RADAR) this.devicesList.get('Radar').push(div); - else if (device instanceof WbRangeFinder) + else if (nodeType === WbNodeType.WB_NODE_RANGE_FINDER) this.devicesList.get('Rangefinder').push(div); - else if (device instanceof WbReceiver) + else if (nodeType === WbNodeType.WB_NODE_RECEIVER) this.devicesList.get('Receiver').push(div); - else if (device instanceof WbSpeaker) + else if (nodeType === WbNodeType.WB_NODE_SPEAKER) this.devicesList.get('Speaker').push(div); - else if (device instanceof WbTouchSensor) + else if (nodeType === WbNodeType.WB_NODE_TOUCH_SENSOR) this.devicesList.get('TouchSensor').push(div); - else if (device instanceof WbVacuumGripper) + else if (nodeType === WbNodeType.WB_NODE_VACUUM_GRIPPER) this.devicesList.get('VacuumGripper').push(div); } @@ -1565,11 +1537,11 @@ export default class FloatingProtoParameterWindow extends FloatingWindow { const endPointName = joint.solidEndPoint() ? joint.solidEndPoint().name : numberOfJoint; let jointType; - if (joint instanceof WbBallJoint) + if (joint.nodeType === WbNodeType.WB_NODE_BALL_JOINT) jointType = 'BallJoint 1: '; - else if (joint instanceof WbHinge2Joint) + else if (joint.nodeType === WbNodeType.WB_NODE_HINGE_2_JOINT) jointType = 'Hinge2joint 1: '; - else if (joint instanceof WbHingeJoint) + else if (joint.nodeType === WbNodeType.WB_NODE_HINGE_JOINT) jointType = 'Hingejoint: '; else jointType = 'Sliderjoint: '; @@ -1586,7 +1558,7 @@ export default class FloatingProtoParameterWindow extends FloatingWindow { this.joints.appendChild(div); - if (joint instanceof WbBallJoint) { + if (joint.nodeType === WbNodeType.WB_NODE_BALL_JOINT) { div = document.createElement('div'); div.className = 'proto-joint'; @@ -1611,7 +1583,7 @@ export default class FloatingProtoParameterWindow extends FloatingWindow { this.#view.x3dScene.render(); }); this.joints.appendChild(div); - } else if (joint instanceof WbHinge2Joint) { + } else if (joint.nodeType === WbNodeType.WB_NODE_HINGE_2_JOINT) { div = document.createElement('div'); div.className = 'proto-joint'; @@ -1626,7 +1598,7 @@ export default class FloatingProtoParameterWindow extends FloatingWindow { }); this.joints.appendChild(div); } - } else if (joint instanceof WbPropeller) { + } else if (joint.nodeType === WbNodeType.WB_NODE_PROPELLER) { numberOfJoint++; let div = document.createElement('div'); @@ -1695,13 +1667,13 @@ export default class FloatingProtoParameterWindow extends FloatingWindow { this.#createGridFiller(grid, row); const deviceType = document.createElement('div'); - if (devices[i] instanceof WbRotationalMotor) + if (devices[i].nodeType === WbNodeType.WB_NODE_ROTATIONAL_MOTOR) deviceType.innerHTML = 'RotationalMotor: '; - else if (devices[i] instanceof WbLinearMotor) + else if (devices[i].nodeType === WbNodeType.WB_NODE_LINEAR_MOTOR) deviceType.innerHTML = 'LinearMotor: '; - else if (devices[i] instanceof WbBrake) + else if (devices[i].nodeType === WbNodeType.WB_NODE_BRAKE) deviceType.innerHTML = 'Brake: '; - else if (devices[i] instanceof WbPositionSensor) + else if (devices[i].nodeType === WbNodeType.WB_NODE_POSITION_SENSOR) deviceType.innerHTML = 'PositionSensor:'; deviceType.style.gridRow = row + ' / ' + row; diff --git a/resources/web/wwi/Parser.js b/resources/web/wwi/Parser.js index f43012ccd9f..f7d4b75532d 100644 --- a/resources/web/wwi/Parser.js +++ b/resources/web/wwi/Parser.js @@ -80,6 +80,7 @@ import WbWorld from './nodes/WbWorld.js'; import WbWrenPostProcessingEffects from './wren/WbWrenPostProcessingEffects.js'; import { getAnId } from './nodes/utils/id_provider.js'; +import { WbNodeType } from './nodes/wb_node_type.js'; import DefaultUrl from './DefaultUrl.js'; import { webots } from './webots.js'; @@ -264,7 +265,7 @@ export default class Parser { break; case 'Fog': if (!WbWorld.instance.hasFog) - result = this.#parseFog(node); + result = this.#parseFog(node, parentNode); else console.error('This world already has a fog.'); break; @@ -321,10 +322,10 @@ export default class Parser { // We are forced to check if the result correspond to the class we expect because of the case of a USE if (typeof result !== 'undefined' && result instanceof WbGeometry) { if (typeof parentNode !== 'undefined') { - if (parentNode instanceof WbShape) { + if (parentNode.nodeType === WbNodeType.WB_NODE_SHAPE) { parentNode.geometry?.delete(); parentNode.geometry = result; - } else if (parentNode instanceof WbSolid || parentNode instanceof WbPose || parentNode instanceof WbGroup) { + } else if (parentNode instanceof WbGroup) { // Bounding object parentNode.boundingObject?.delete(); if (parentNode instanceof WbSolid) @@ -334,13 +335,13 @@ export default class Parser { } } } else if (node.tagName === 'PBRAppearance') { - if (typeof parentNode !== 'undefined' && parentNode instanceof WbShape) { + if (typeof parentNode !== 'undefined' && parentNode.nodeType === WbNodeType.WB_NODE_SHAPE) { parentNode.appearance?.delete(); result = this.#parsePbrAppearance(node, id); parentNode.appearance = result; } } else if (node.tagName === 'Appearance') { - if (typeof parentNode !== 'undefined' && parentNode instanceof WbShape) { + if (typeof parentNode !== 'undefined' && parentNode.nodeType === WbNodeType.WB_NODE_SHAPE) { parentNode.appearance?.delete(); result = this.#parseAppearance(node, id); parentNode.appearance = result; @@ -348,7 +349,7 @@ export default class Parser { } else if (node.tagName === 'Material') { result = this.#parseMaterial(node, id); if (typeof result !== 'undefined') { - if (typeof parentNode !== 'undefined' && parentNode instanceof WbAppearance) { + if (typeof parentNode !== 'undefined' && parentNode.nodeType === WbNodeType.WB_NODE_APPEARANCE) { parentNode.material?.delete(); parentNode.material = result; } @@ -356,7 +357,7 @@ export default class Parser { } else if (node.tagName === 'ImageTexture') { result = this.#parseImageTexture(node, id); if (typeof result !== 'undefined') { - if (typeof parentNode !== 'undefined' && parentNode instanceof WbAppearance) { + if (typeof parentNode !== 'undefined' && parentNode.nodeType === WbNodeType.WB_NODE_APPEARANCE) { parentNode.texture?.delete(); parentNode.texture = result; } else { @@ -616,10 +617,11 @@ export default class Parser { if (typeof parentNode !== 'undefined') { useNode.parent = parentNode.id; const isBoundingObject = getNodeAttribute(node, 'role', undefined) === 'boundingObject'; - if (isBoundingObject && (result instanceof WbShape || result instanceof WbGroup || result instanceof WbGeometry)) + if (isBoundingObject && (result.nodeType === WbNodeType.WB_NODE_SHAPE || result instanceof WbGroup || + result instanceof WbGeometry)) parentNode.boundingObject = useNode; - else if (result instanceof WbShape || result instanceof WbGroup || result instanceof WbLight || - result instanceof WbCadShape) + else if (result.nodeType === WbNodeType.WB_NODE_SHAPE || result instanceof WbGroup || result instanceof WbLight || + result.nodeType === WbNodeType.WB_NODE_CAD_SHAPE) parentNode.children.push(useNode); } @@ -766,7 +768,7 @@ export default class Parser { parentNode.geometryField = newNode; else if (isBoundingObject && parentNode instanceof WbSolid) parentNode.boundingObject = newNode; - else if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + else if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = newNode; else parentNode.children.push(newNode); @@ -797,7 +799,7 @@ export default class Parser { if (typeof parentNode !== 'undefined') { newNode.parent = parentNode.id; - if (parentNode instanceof WbSlot) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT) parentNode.endPoint = newNode; else parentNode.children.push(newNode); @@ -823,7 +825,7 @@ export default class Parser { group.parent = parentNode.id; if (isBoundingObject && parentNode instanceof WbSolid) parentNode.boundingObject = group; - else if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + else if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = group; else parentNode.children.push(group); @@ -844,7 +846,7 @@ export default class Parser { this.#parseChildren(node, propeller); propeller.parent = parentNode.id; - if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = propeller; else parentNode.children.push(propeller); @@ -866,7 +868,7 @@ export default class Parser { if (typeof parentNode !== 'undefined') { slot.parent = parentNode.id; - if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = slot; else parentNode.children.push(slot); @@ -894,7 +896,7 @@ export default class Parser { if (typeof parentNode !== 'undefined') { joint.parent = parentNode.id; - if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = joint; else parentNode.children.push(joint); @@ -1024,7 +1026,7 @@ export default class Parser { if (typeof parentNode !== 'undefined') { if (isBoundingObject && parentNode instanceof WbSolid) parentNode.boundingObject = shape; - else if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + else if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = shape; else parentNode.children.push(shape); @@ -1061,7 +1063,7 @@ export default class Parser { if (typeof parentNode !== 'undefined') { cadShape.parent = parentNode.id; - if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = cadShape; else parentNode.children.push(cadShape); @@ -1090,6 +1092,9 @@ export default class Parser { WbWorld.instance.nodes.set(billboard.id, billboard); this.#parseChildren(node, billboard); + if (typeof parentNode !== 'undefined') + parentNode.children.push(billboard); + return billboard; } @@ -1111,7 +1116,7 @@ export default class Parser { if (typeof parentNode !== 'undefined' && typeof dirLight !== 'undefined') { dirLight.parent = parentNode.id; - if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = dirLight; else parentNode.children.push(dirLight); @@ -1142,7 +1147,7 @@ export default class Parser { castShadows, parentNode); if (typeof parentNode !== 'undefined' && typeof pointLight !== 'undefined') { - if (parentNode instanceof WbSlot || parentNode instanceof WbJoint) + if (parentNode.nodeType === WbNodeType.WB_NODE_SLOT || parentNode instanceof WbJoint) parentNode.endPoint = pointLight; else parentNode.children.push(pointLight); @@ -1181,7 +1186,7 @@ export default class Parser { return spotLight; } - #parseFog(node) { + #parseFog(node, parentNode) { this.#updateParserProgress(node); const id = this.#parseId(node); const color = convertStringToVec3(getNodeAttribute(node, 'color', '1 1 1')); @@ -1195,6 +1200,9 @@ export default class Parser { if (typeof fog !== 'undefined') WbWorld.instance.hasFog = true; + if (typeof parentNode !== 'undefined') + parentNode.children.push(fog); + return fog; } diff --git a/resources/web/wwi/ProtoManager.js b/resources/web/wwi/ProtoManager.js index 61a660c702f..12803821255 100644 --- a/resources/web/wwi/ProtoManager.js +++ b/resources/web/wwi/ProtoManager.js @@ -150,7 +150,7 @@ export default class ProtoManager { appearance.setAttribute('baseColor', '0.8 0.8 0.8'); const imageTexture = xml.createElement('ImageTexture'); imageTexture.setAttribute('id', getAnId()); - imageTexture.setAttribute('url', 'https://raw.githubusercontent.com/cyberbotics/webots/R2022b/projects/default/worlds/textures/grid.png'); + imageTexture.setAttribute('url', 'https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/default/worlds/textures/grid.png'); imageTexture.setAttribute('role', 'baseColorMap'); appearance.appendChild(imageTexture); const textureTransform = xml.createElement('TextureTransform'); diff --git a/resources/web/wwi/WebotsView.js b/resources/web/wwi/WebotsView.js index 182148acb2c..820dfcd6e82 100644 --- a/resources/web/wwi/WebotsView.js +++ b/resources/web/wwi/WebotsView.js @@ -5,9 +5,8 @@ import {webots} from './webots.js'; import {changeGtaoLevel} from './nodes/wb_preferences.js'; import WbWorld from './nodes/WbWorld.js'; import WbDevice from './nodes/WbDevice.js'; -import WbShape from './nodes/WbShape.js'; -import WbSlot from './nodes/WbSlot.js'; import WbVector3 from './nodes/utils/WbVector3.js'; +import {WbNodeType} from './nodes/wb_node_type.js'; /* The following member variables can be set by the application: @@ -402,8 +401,8 @@ export default class WebotsView extends HTMLElement { this.resize(); this.toolbar.protoParameterWindowInitializeSizeAndPosition(); const topProtoNode = WbWorld.instance.root.children[WbWorld.instance.root.children.length - 1]; - if (topProtoNode instanceof WbDevice || topProtoNode instanceof WbSlot || topProtoNode instanceof WbShape || - moveFloor === '1') + if (topProtoNode instanceof WbDevice || topProtoNode.nodeType === WbNodeType.WB_NODE_SLOT || + topProtoNode.nodeType === WbNodeType.WB_NODE_SHAPE || moveFloor === '1') this.#repositionFloor(topProtoNode, WbWorld.instance.root.children[WbWorld.instance.root.children.length - 2]); WbWorld.instance.viewpoint.moveViewpointToObject(topProtoNode); diff --git a/resources/web/wwi/X3dScene.js b/resources/web/wwi/X3dScene.js index 03278d891b7..e70bd80c5f5 100644 --- a/resources/web/wwi/X3dScene.js +++ b/resources/web/wwi/X3dScene.js @@ -6,45 +6,15 @@ import { webots } from './webots.js'; import WrenRenderer from './WrenRenderer.js'; import WbAbstractCamera from './nodes/WbAbstractCamera.js'; -import WbBox from './nodes/WbBox.js'; -import WbCadShape from './nodes/WbCadShape.js'; -import WbCamera from './nodes/WbCamera.js'; -import WbCapsule from './nodes/WbCapsule.js'; -import WbColor from './nodes/WbColor.js'; -import WbCone from './nodes/WbCone.js'; -import WbCoordinate from './nodes/WbCoordinate.js'; -import WbCylinder from './nodes/WbCylinder.js'; -import WbDistanceSensor from './nodes/WbDistanceSensor.js'; -import WbElevationGrid from './nodes/WbElevationGrid.js'; -import WbFog from './nodes/WbFog.js'; -import WbImageTexture from './nodes/WbImageTexture.js'; -import WbIndexedFaceSet from './nodes/WbIndexedFaceSet.js'; -import WbIndexedLineSet from './nodes/WbIndexedLineSet.js'; -import WbLidar from './nodes/WbLidar.js'; import WbLight from './nodes/WbLight.js'; -import WbMaterial from './nodes/WbMaterial.js'; -import WbMesh from './nodes/WbMesh.js'; -import WbPen from './nodes/WbPen.js'; -import WbPbrAppearance from './nodes/WbPbrAppearance.js'; -import WbPlane from './nodes/WbPlane.js'; -import WbPointLight from './nodes/WbPointLight.js'; import WbPose from './nodes/WbPose.js'; -import WbRadar from './nodes/WbRadar.js'; -import WbSphere from './nodes/WbSphere.js'; -import WbTextureCoordinate from './nodes/WbTextureCoordinate.js'; -import WbTextureTransform from './nodes/WbTextureTransform.js'; -import WbTrackWheel from './nodes/WbTrackWheel.js'; import WbWorld from './nodes/WbWorld.js'; import WbVector2 from './nodes/utils/WbVector2.js'; import WbVector3 from './nodes/utils/WbVector3.js'; -import WbNormal from './nodes/WbNormal.js'; -import WbSpotLight from './nodes/WbSpotLight.js'; import WbDirectionalLight from './nodes/WbDirectionalLight.js'; -import WbRangeFinder from './nodes/WbRangeFinder.js'; -import WbConnector from './nodes/WbConnector.js'; -import WbPropeller from './nodes/WbPropeller.js'; import { findUpperShape } from './nodes/utils/node_utilities.js'; +import { WbNodeType } from './nodes/wb_node_type.js'; export default class X3dScene { #nextRenderingTime; @@ -161,7 +131,7 @@ export default class X3dScene { const xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', url, true); xmlhttp.overrideMimeType('plain/text'); - xmlhttp.onreadystatechange = async () => { + xmlhttp.onreadystatechange = async() => { // Some browsers return HTTP Status 0 when using non-http protocol (for file://) if (xmlhttp.readyState === 4 && (xmlhttp.status === 200 || xmlhttp.status === 0)) { const parser = new Parser(prefix); @@ -218,7 +188,7 @@ export default class X3dScene { if (typeof use === 'undefined') { // remove a USE node from the list if it has been deleted const index = object.useList.indexOf(length); - this.useList.splice(index, 1); + object.useList.splice(index, 1); } else this.#applyUpdateToObject(update, use); @@ -231,17 +201,19 @@ export default class X3dScene { if (key === 'id') continue; + const nodeType = object.nodeType; + if (key === 'translation') { if (object instanceof WbPose) object.translation = convertStringToVec3(update[key]); - else if (object instanceof WbTextureTransform) + else if (nodeType === WbNodeType.WB_NODE_TEXTURE_TRANSFORM) object.translation = convertStringToVec2(update[key]); } else if (key === 'rotation') { - if (object instanceof WbTextureTransform) + if (nodeType === WbNodeType.WB_NODE_TEXTURE_TRANSFORM) object.rotation = parseFloat(update[key]); else { const quaternion = convertStringToQuaternion(update[key]); - if (object instanceof WbTrackWheel) + if (nodeType === WbNodeType.WB_NODE_TRACK_WHEEL) object.updateRotation(quaternion); else object.rotation = quaternion; @@ -249,70 +221,76 @@ export default class X3dScene { } else if (key === 'scale') { if (object instanceof WbPose) object.scale = convertStringToVec3(update[key]); - else if (object instanceof WbTextureTransform) + else if (nodeType === WbNodeType.WB_NODE_TEXTURE_TRANSFORM) object.scale = convertStringToVec2(update[key]); } else if (key === 'center') { - if (object instanceof WbTextureTransform) + if (nodeType === WbNodeType.WB_NODE_TEXTURE_TRANSFORM) object.center = convertStringToVec2(update[key]); } else if (key === 'castShadows') { - if (object instanceof WbLight || object instanceof WbCadShape) + if (object instanceof WbLight || nodeType === WbNodeType.WB_NODE_CAD_SHAPE || nodeType === WbNodeType.WB_NODE_SHAPE) object.castShadows = update[key].toLowerCase() === 'true'; } else if (key === 'isPickable') { - if (object instanceof WbCadShape) + if (nodeType === WbNodeType.WB_NODE_CAD_SHAPE || nodeType === WbNodeType.WB_NODE_SHAPE) object.isPickable = update[key].toLowerCase() === 'true'; } else if (key === 'size') { - if (object instanceof WbBox || object instanceof WbPlane) + if (nodeType === WbNodeType.WB_NODE_BOX || nodeType === WbNodeType.WB_NODE_PLANE) object.size = convertStringToVec3(update[key]); } else if (key === 'radius') { - if (object instanceof WbCapsule || object instanceof WbSphere || object instanceof WbCylinder || - object instanceof WbSpotLight || object instanceof WbPointLight) + if (nodeType === WbNodeType.WB_NODE_CAPSULE || nodeType === WbNodeType.WB_NODE_SPHERE || + nodeType === WbNodeType.WB_NODE_CYLINDER || nodeType === WbNodeType.WB_NODE_SPOT_LIGHT || + nodeType === WbNodeType.WB_NODE_POINT_LIGHT) object.radius = parseFloat(update[key]); } else if (key === 'subdivision') { - if (object instanceof WbSphere || object instanceof WbCapsule || object instanceof WbCone || - object instanceof WbCylinder) + if (nodeType === WbNodeType.WB_NODE_SPHERE || nodeType === WbNodeType.WB_NODE_CAPSULE || + nodeType === WbNodeType.WB_NODE_CONE || nodeType === WbNodeType.WB_NODE_CYLINDER) object.subdivision = parseInt(update[key]); } else if (key === 'ico') { - if (object instanceof WbSphere) + if (nodeType === WbNodeType.WB_NODE_SPHERE) object.ico = update[key].toLowerCase() === 'true'; } else if (key === 'height') { - if (object instanceof WbCapsule || object instanceof WbCone || object instanceof WbCylinder) + if (nodeType === WbNodeType.WB_NODE_CAPSULE || nodeType === WbNodeType.WB_NODE_CONE || + nodeType === WbNodeType.WB_NODE_CYLINDER) object.height = parseFloat(update[key]); - else if (object instanceof WbElevationGrid) + else if (nodeType === WbNodeType.WB_NODE_ELEVATION_GRID) // Filter is used to remove Nan elements object.height = convertStringToFloatArray(update[key]).filter(e => e); else if (object instanceof WbAbstractCamera) object.height = parseInt(update[key]); } else if (key === 'bottom') { - if (object instanceof WbCapsule || object instanceof WbCone || object instanceof WbCylinder) + if (nodeType === WbNodeType.WB_NODE_CAPSULE || nodeType === WbNodeType.WB_NODE_CONE || + nodeType === WbNodeType.WB_NODE_CYLINDER) object.bottom = update[key].toLowerCase() === 'true'; } else if (key === 'top') { - if (object instanceof WbCapsule || object instanceof WbCylinder) + if (nodeType === WbNodeType.WB_NODE_CAPSULE || nodeType === WbNodeType.WB_NODE_CYLINDER) object.top = update[key].toLowerCase() === 'true'; } else if (key === 'side') { - if (object instanceof WbCapsule || object instanceof WbCone || object instanceof WbCylinder) + if (nodeType === WbNodeType.WB_NODE_CAPSULE || nodeType === WbNodeType.WB_NODE_CONE || + nodeType === WbNodeType.WB_NODE_CYLINDER) object.side = update[key].toLowerCase() === 'true'; } else if (key === 'bottomRadius') { - if (object instanceof WbCone) + if (nodeType === WbNodeType.WB_NODE_CONE) object.bottomRadius = parseFloat(update[key]); } else if (key === 'ccw') { - if (object instanceof WbMesh || object instanceof WbIndexedFaceSet || object instanceof WbCadShape) + if (nodeType === WbNodeType.WB_NODE_MESH || nodeType === WbNodeType.WB_NODE_INDEXED_FACE_SET || + nodeType === WbNodeType.WB_NODE_CAD_SHAPE) object.ccw = update[key].toLowerCase() === 'true'; } else if (key === 'direction') { - if (object instanceof WbSpotLight || WbDirectionalLight) + if (nodeType === WbNodeType.WB_NODE_SPOT_LIGHT || WbDirectionalLight) object.direction = convertStringToVec3(update[key]); } else if (key === 'color') { - if (object instanceof WbLight || object instanceof WbFog) + if (object instanceof WbLight || nodeType === WbNodeType.WB_NODE_FOG) object.color = convertStringToVec3(update[key]); - else if (object instanceof WbColor) + else if (nodeType === WbNodeType.WB_NODE_COLOR) object.color = convertStringToVec3Array(update[key]); } else if (key === 'name') { - if (object instanceof WbMesh) + if (nodeType === WbNodeType.WB_NODE_MESH) object.name = update[key]; } else if (key === 'materialIndex') { - if (object instanceof WbMesh) + if (nodeType === WbNodeType.WB_NODE_MESH) object.materialIndex = parseInt(update[key]); } else if (key === 'url') { - if (object instanceof WbMesh || object instanceof WbCadShape || object instanceof WbImageTexture) { + if (nodeType === WbNodeType.WB_NODE_MESH || nodeType === WbNodeType.WB_NODE_CAD_SHAPE || + nodeType === WbNodeType.WB_NODE_IMAGE_TEXTURE) { let urlString = update[key]; if (urlString === '[]') { object.url = undefined; @@ -323,33 +301,33 @@ export default class X3dScene { object.url = urlString.split('"').filter(element => { if (element !== ' ') return element; })[0]; } } else if (key === 'point') { - if (object instanceof WbCoordinate) + if (nodeType === WbNodeType.WB_NODE_COORDINATE) object.point = convertStringToVec3Array(update[key]); - if (object instanceof WbTextureCoordinate) + if (nodeType === WbNodeType.WB_NODE_TEXTURE_COORDINATE) object.point = convertStringToVec2Array(update[key]); } else if (key === 'vector') { - if (object instanceof WbNormal) + if (nodeType === WbNodeType.WB_NODE_NORMAL) object.vector = convertStringToVec3Array(update[key]); } else if (key === 'coordIndex') { - if (object instanceof WbIndexedLineSet || object instanceof WbIndexedFaceSet) + if (nodeType === WbNodeType.WB_NODE_INDEXED_LINE_SET || nodeType === WbNodeType.WB_NODE_INDEXED_FACE_SET) object.coordIndex = update[key]; } else if (key === 'attenuation') { - if (object instanceof WbSpotLight || object instanceof WbPointLight) + if (nodeType === WbNodeType.WB_NODE_SPOT_LIGHT || nodeType === WbNodeType.WB_NODE_POINT_LIGHT) object.attenuation = convertStringToVec3(update[key]); } else if (key === 'location') { - if (object instanceof WbSpotLight || object instanceof WbPointLight) + if (nodeType === WbNodeType.WB_NODE_SPOT_LIGHT || nodeType === WbNodeType.WB_NODE_POINT_LIGHT) object.location = convertStringToVec3(update[key]); - } else if (object instanceof WbFog) { + } else if (nodeType === WbNodeType.WB_NODE_FOG) { if (key === 'visibilityRange') object.visibilityRange = parseFloat(update[key]); else if (key === 'fogType') object.fogType = update[key]; - } else if (object instanceof WbSpotLight) { + } else if (nodeType === WbNodeType.WB_NODE_SPOT_LIGHT) { if (key === 'beamWidth') object.beamWidth = parseFloat(update[key]); else if (key === 'cutOffAngle') object.cutOffAngle = parseFloat(update[key]); - } else if (object instanceof WbIndexedFaceSet) { + } else if (nodeType === WbNodeType.WB_NODE_INDEXED_FACE_SET) { if (key === 'normalPerVertex') object.normalPerVertex = update[key].toLowerCase() === 'true'; else if (key === 'normalIndex') @@ -358,7 +336,7 @@ export default class X3dScene { object.texCoordIndex = update[key]; else if (key === 'creaseAngle') object.creaseAngle = parseFloat(update[key]); - } else if (object instanceof WbElevationGrid) { + } else if (nodeType === WbNodeType.WB_NODE_ELEVATION_GRID) { if (key === 'xDimension') object.xDimension = update[key]; else if (key === 'xSpacing') @@ -369,7 +347,7 @@ export default class X3dScene { object.ySpacing = update[key]; else if (key === 'thickness') object.thickness = update[key]; - } else if (object instanceof WbPbrAppearance || object instanceof WbMaterial) { + } else if (nodeType === WbNodeType.WB_NODE_PBR_APPEARANCE || nodeType === WbNodeType.WB_NODE_MATERIAL) { if (key === 'baseColor') object.baseColor = convertStringToVec3(update[key]); else if (key === 'diffuseColor') @@ -396,24 +374,24 @@ export default class X3dScene { object.shininess = parseFloat(update[key]); else if (key === 'specularColor') object.specularColor = convertStringToVec3(update[key]); - } else if (object instanceof WbImageTexture) { + } else if (nodeType === WbNodeType.WB_NODE_IMAGE_TEXTURE) { if (key === 'repeatS') object.repeatS = update[key].toLowerCase() === 'true'; else if (key === 'repeatT') object.repeatT = update[key].toLowerCase() === 'true'; else if (key === 'filtering') object.filtering = parseInt(update[key]); - } else if (object instanceof WbCamera) { + } else if (nodeType === WbNodeType.WB_NODE_CAMERA) { if (key === 'far') object.far = parseFloat(update[key]); else if (key === 'near') object.near = parseFloat(update[key]); - } else if (object instanceof WbRangeFinder) { + } else if (nodeType === WbNodeType.WB_NODE_RANGE_FINDER) { if (key === 'maxRange') object.maxRange = parseFloat(update[key]); else if (key === 'minRange') object.minRange = parseFloat(update[key]); - } else if (object instanceof WbLidar) { + } else if (nodeType === WbNodeType.WB_NODE_LIDAR) { if (key === 'maxRange') object.maxRange = parseFloat(update[key]); else if (key === 'minRange') @@ -426,7 +404,7 @@ export default class X3dScene { object.tiltAngle = parseFloat(update[key]); else if (key === 'verticalFieldOfView') object.verticalFieldOfView = parseFloat(update[key]); - } else if (object instanceof WbRadar) { + } else if (nodeType === WbNodeType.WB_NODE_RADAR) { if (key === 'maxRange') object.maxRange = parseFloat(update[key]); else if (key === 'minRange') @@ -435,10 +413,10 @@ export default class X3dScene { object.verticalFieldOfView = parseFloat(update[key]); else if (key === 'horizontalFieldOfView') object.horizontalFieldOfView = parseFloat(update[key]); - } else if (object instanceof WbPen) { + } else if (nodeType === WbNodeType.WB_NODE_PEN) { if (key === 'write') object.write = update[key].toLowerCase() === 'true'; - } else if (object instanceof WbDistanceSensor) { + } else if (nodeType === WbNodeType.WB_NODE_DISTANCE_SENSOR) { if (key === 'numberOfRays') object.numberOfRays = parseInt(update[key]); else if (key === 'aperture') @@ -457,7 +435,7 @@ export default class X3dScene { } object.lookupTable = lookupTable; } - } else if (object instanceof WbConnector) { + } else if (nodeType === WbNodeType.WB_NODE_CONNECTOR) { if (key === 'numberOfRotations') object.numberOfRotations = parseInt(update[key]); } @@ -479,8 +457,8 @@ export default class X3dScene { if (typeof object.parent !== 'undefined') { const parent = WbWorld.instance.nodes.get(object.parent); - if (typeof parent !== 'undefined' && parent instanceof WbPropeller && parent.currentHelix !== object.id && - WbWorld.instance.readyForUpdates) + if (typeof parent !== 'undefined' && parent.nodeType === WbNodeType.WB_NODE_PROPELLER && + parent.currentHelix !== object.id && WbWorld.instance.readyForUpdates) parent.switchHelix(object.id); } } diff --git a/resources/web/wwi/nodes/WbBackground.js b/resources/web/wwi/nodes/WbBackground.js index c69f5d67d9e..7ab9c9c58ae 100644 --- a/resources/web/wwi/nodes/WbBackground.js +++ b/resources/web/wwi/nodes/WbBackground.js @@ -1,6 +1,5 @@ import {arrayXPointer, arrayXPointerFloat} from './utils/utils.js'; import WbBaseNode from './WbBaseNode.js'; -import WbPbrAppearance from './WbPbrAppearance.js'; import WbVector3 from './utils/WbVector3.js'; import WbWorld from './WbWorld.js'; import WbWrenShaders from '../wren/WbWrenShaders.js'; @@ -223,7 +222,7 @@ export default class WbBackground extends WbBaseNode { #updatePBRs() { WbWorld.instance.nodes.forEach((value, key, map) => { - if (value instanceof WbPbrAppearance && typeof value.parent !== 'undefined') { + if (value.nodeType === WbNodeType.WB_NODE_PBR_APPEARANCE && typeof value.parent !== 'undefined') { const parent = WbWorld.instance.nodes.get(value.parent); parent?.applyMaterialToGeometry(); } diff --git a/resources/web/wwi/nodes/WbBillboard.js b/resources/web/wwi/nodes/WbBillboard.js index 9c125f4d9b1..12b193c55a6 100644 --- a/resources/web/wwi/nodes/WbBillboard.js +++ b/resources/web/wwi/nodes/WbBillboard.js @@ -54,7 +54,7 @@ export default class WbBillboard extends WbGroup { static isDescendantOfBillboard(node) { while (typeof node !== 'undefined') { - if (node instanceof WbBillboard) + if (node.nodeType === WbNodeType.WB_NODE_BILLBOARD) return true; node = WbWorld.instance.nodes.get(node.parent); diff --git a/resources/web/wwi/nodes/WbCylinder.js b/resources/web/wwi/nodes/WbCylinder.js index dbc70466c51..f8519a8f2da 100644 --- a/resources/web/wwi/nodes/WbCylinder.js +++ b/resources/web/wwi/nodes/WbCylinder.js @@ -161,7 +161,8 @@ export default class WbCylinder extends WbGeometry { this._computeWrenRenderable(); const createOutlineMesh = this.isInBoundingObject(); - this._wrenMesh = _wr_static_mesh_unit_cylinder_new(this.#subdivision, this.#side, this.#top, this.#bottom, createOutlineMesh); + this._wrenMesh = _wr_static_mesh_unit_cylinder_new(this.#subdivision, this.#side, this.#top, this.#bottom, + createOutlineMesh); if (createOutlineMesh) this.#updateLineScale(); @@ -217,6 +218,6 @@ export default class WbCylinder extends WbGeometry { const offset = _wr_config_get_line_scale() / WbGeometry.LINE_SCALE_FACTOR; _wr_transform_set_scale(this.wrenNode, _wrjs_array3(this.#radius * (1.0 + offset), this.#radius * (1.0 + offset), - this.#height * (1.0 + offset))); + this.#height * (1.0 + offset))); } } diff --git a/resources/web/wwi/nodes/WbDevice.js b/resources/web/wwi/nodes/WbDevice.js index 6f99ed603a6..36b6f1a7e1f 100644 --- a/resources/web/wwi/nodes/WbDevice.js +++ b/resources/web/wwi/nodes/WbDevice.js @@ -74,7 +74,8 @@ export default class WbDevice extends WbSolid { } // Axes (X & Z only) - const axesCoordinates = [[0, 0, 0, 0.1 * radiusScale, 0, 0], [0, 0, 0, 0, 0.1 * radiusScale, 0], [0, 0, 0, 0, 0, 0.1 * radiusScale]]; + const axesCoordinates = [[0, 0, 0, 0.1 * radiusScale, 0, 0], [0, 0, 0, 0, 0.1 * radiusScale, 0], + [0, 0, 0, 0, 0, 0.1 * radiusScale]]; for (let i = 0; i < 3; ++i) { const axesCoordinatesPointer = arrayXPointerFloat(axesCoordinates[i]); diff --git a/resources/web/wwi/nodes/WbGroup.js b/resources/web/wwi/nodes/WbGroup.js index b09660f4a01..9861f202013 100644 --- a/resources/web/wwi/nodes/WbGroup.js +++ b/resources/web/wwi/nodes/WbGroup.js @@ -1,5 +1,4 @@ import WbBaseNode from './WbBaseNode.js'; -import WbCadShape from './WbCadShape.js'; import WbLight from './WbLight.js'; import WbSolid from './WbSolid.js'; import WbWorld from './WbWorld.js'; @@ -140,7 +139,7 @@ export default class WbGroup extends WbBaseNode { updateBoundingObjectVisibility() { this.children.forEach(child => { - if (!(child instanceof WbLight || child instanceof WbCadShape)) + if (!(child instanceof WbLight || child.nodeType === WbNodeType.WB_NODE_CAD_SHAPE)) child.updateBoundingObjectVisibility(); }); } diff --git a/resources/web/wwi/nodes/WbImageTexture.js b/resources/web/wwi/nodes/WbImageTexture.js index 5ce6a04817e..f9300ec3b70 100644 --- a/resources/web/wwi/nodes/WbImageTexture.js +++ b/resources/web/wwi/nodes/WbImageTexture.js @@ -1,7 +1,6 @@ import {textureFiltering} from './wb_preferences.js'; import {resetIfNotInRangeWithIncludedBounds} from './utils/WbFieldChecker.js'; import ImageLoader from '../ImageLoader.js'; -import WbAppearance from './WbAppearance.js'; import WbBaseNode from './WbBaseNode.js'; import WbWorld from './WbWorld.js'; import {WbNodeType} from './wb_node_type.js'; @@ -92,7 +91,7 @@ export default class WbImageTexture extends WbBaseNode { if (typeof this.parent !== 'undefined') { const parent = WbWorld.instance.nodes.get(this.parent); if (typeof parent !== 'undefined') { - if (parent instanceof WbAppearance) + if (parent.nodeType === WbNodeType.WB_NODE_APPEARANCE) parent.texture = undefined; else { switch (this.role) { diff --git a/resources/web/wwi/nodes/WbJoint.js b/resources/web/wwi/nodes/WbJoint.js index 37a29abddf9..619e2b1561e 100644 --- a/resources/web/wwi/nodes/WbJoint.js +++ b/resources/web/wwi/nodes/WbJoint.js @@ -1,7 +1,7 @@ import WbBaseNode from './WbBaseNode.js'; -import WbSlot from './WbSlot.js'; import WbSolid from './WbSolid.js'; import WbWorld from './WbWorld.js'; +import {WbNodeType} from './wb_node_type.js'; export default class WbJoint extends WbBaseNode { #endPoint; @@ -91,7 +91,7 @@ export default class WbJoint extends WbBaseNode { if (typeof this.endPoint === 'undefined') return; - if (this.endPoint instanceof WbSlot) { + if (this.endPoint.nodeType === WbNodeType.WB_NODE_SLOT) { const childrenSlot = this.endPoint.slotEndPoint(); return childrenSlot?.solidEndPoint(); } else if (this.endPoint instanceof WbSolid) diff --git a/resources/web/wwi/nodes/WbLed.js b/resources/web/wwi/nodes/WbLed.js index a8e4b843484..de16c00fe09 100644 --- a/resources/web/wwi/nodes/WbLed.js +++ b/resources/web/wwi/nodes/WbLed.js @@ -1,8 +1,6 @@ -import WbAppearance from './WbAppearance.js'; import WbDevice from './WbDevice.js'; import WbGroup from './WbGroup.js'; import WbLight from './WbLight.js'; -import WbShape from './WbShape.js'; import WbVector3 from './utils/WbVector3.js'; import {clampValuesIfNeeded} from './utils/WbFieldChecker.js'; import {WbNodeType} from './wb_node_type.js'; @@ -81,9 +79,9 @@ export default class WbLed extends WbDevice { } for (const child of group.children) { - if (child instanceof WbShape && typeof child.appearance !== 'undefined') { + if (child.nodeType === WbNodeType.WB_NODE_SHAPE && typeof child.appearance !== 'undefined') { const appearance = child.appearance; - if (appearance instanceof WbAppearance) { + if (appearance.nodeType === WbNodeType.WB_NODE_APPEARANCE) { const material = appearance.material; if (typeof material !== 'undefined') this.#materials.push(material); diff --git a/resources/web/wwi/nodes/WbLookupTable.js b/resources/web/wwi/nodes/WbLookupTable.js index 2b8f5548287..a7355a08f14 100644 --- a/resources/web/wwi/nodes/WbLookupTable.js +++ b/resources/web/wwi/nodes/WbLookupTable.js @@ -12,9 +12,8 @@ export default class WbLookupTable { const v = lookupTable[i]; this.#inputs[i] = v.x; } - } else { + } else this.#inputs = undefined; - } } minMetricsRange() { diff --git a/resources/web/wwi/nodes/WbPropeller.js b/resources/web/wwi/nodes/WbPropeller.js index 1ea9674b733..da412799a4f 100644 --- a/resources/web/wwi/nodes/WbPropeller.js +++ b/resources/web/wwi/nodes/WbPropeller.js @@ -1,4 +1,5 @@ import WbGroup from './WbGroup.js'; +import { WbNodeType } from './wb_node_type.js'; export default class WbPropeller extends WbGroup { #device; @@ -16,6 +17,10 @@ export default class WbPropeller extends WbGroup { this.#device = device; } + get nodeType() { + return WbNodeType.WB_NODE_PROPELLER; + } + postFinalize() { super.postFinalize(); diff --git a/resources/web/wwi/nodes/WbShape.js b/resources/web/wwi/nodes/WbShape.js index 080c9f4e9f7..c7879af12ae 100644 --- a/resources/web/wwi/nodes/WbShape.js +++ b/resources/web/wwi/nodes/WbShape.js @@ -1,7 +1,5 @@ import WbAppearance from './WbAppearance.js'; import WbBaseNode from './WbBaseNode.js'; -import WbPbrAppearance from './WbPbrAppearance.js'; -import WbPointSet from './WbPointSet.js'; import WbWorld from './WbWorld.js'; import WbWrenShaders from '../wren/WbWrenShaders.js'; import {getAnId} from './utils/id_provider.js'; @@ -9,13 +7,15 @@ import {nodeIsInBoundingObject} from './utils/node_utilities.js'; import {WbNodeType} from './wb_node_type.js'; export default class WbShape extends WbBaseNode { + #appearance; #boundingObjectFirstTimeSearch; + #castShadows; #isInBoundingObject; - #appearance; - constructor(id, castShadow, isPickable, geometry, appearance) { + #isPickable; + constructor(id, castShadows, isPickable, geometry, appearance) { super(id); - this.castShadow = castShadow; - this.isPickable = isPickable; + this.#castShadows = castShadows; + this.#isPickable = isPickable; this.#boundingObjectFirstTimeSearch = true; this.#isInBoundingObject = false; @@ -52,25 +52,48 @@ export default class WbShape extends WbBaseNode { this.notifyLed(); } + get castShadows() { + return this.#castShadows; + } + + set castShadows(newCastShadows) { + this.#castShadows = newCastShadows; + + this.updateCastShadows(); + } + + get isPickable() { + return this.#isPickable; + } + + set isPickable(newIsPickable) { + this.#isPickable = newIsPickable; + + this.updateIsPickable(); + } + applyMaterialToGeometry() { if (!this.wrenMaterial) this.#createWrenMaterial(Enum.WR_MATERIAL_PHONG); if (this.geometry) { - if (this.appearance instanceof WbAppearance) { - if (this.appearance.wrenObjectsCreatedCalled) - this.wrenMaterial = this.appearance.modifyWrenMaterial(this.wrenMaterial); - else - this.wrenMaterial = WbAppearance.fillWrenDefaultMaterial(this.wrenMaterial); - } else if ((this.appearance instanceof WbPbrAppearance) && !(this.geometry instanceof WbPointSet)) { - this.#createWrenMaterial(); - if (this.appearance.wrenObjectsCreatedCalled) - this.wrenMaterial = this.appearance.modifyWrenMaterial(this.wrenMaterial); + if (typeof this.appearance !== 'undefined') { + if (this.appearance.nodeType === WbNodeType.WB_NODE_APPEARANCE) { + if (this.appearance.wrenObjectsCreatedCalled) + this.wrenMaterial = this.appearance.modifyWrenMaterial(this.wrenMaterial); + else + this.wrenMaterial = WbAppearance.fillWrenDefaultMaterial(this.wrenMaterial); + } else if ((this.appearance.nodeType === WbNodeType.WB_NODE_PBR_APPEARANCE) && + !(this.geometry.nodeType === WbNodeType.WB_NODE_POINT_SET)) { + this.#createWrenMaterial(); + if (this.appearance.wrenObjectsCreatedCalled) + this.wrenMaterial = this.appearance.modifyWrenMaterial(this.wrenMaterial); + } } else this.wrenMaterial = WbAppearance.fillWrenDefaultMaterial(this.wrenMaterial); if (!this.geometry.isInBoundingObject()) - this.geometry.setWrenMaterial(this.wrenMaterial, this.castShadow); + this.geometry.setWrenMaterial(this.wrenMaterial, this.#castShadows); } } @@ -93,7 +116,7 @@ export default class WbShape extends WbBaseNode { } this.useList.push(customID); - return new WbShape(customID, this.castShadow, this.isPickable, geometry, appearance); + return new WbShape(customID, this.#castShadows, this.#isPickable, geometry, appearance); } createWrenObjects() { @@ -165,14 +188,14 @@ export default class WbShape extends WbBaseNode { if (this.isInBoundingObject()) return; - this.geometry?.computeCastShadows(this.castShadow); + this.geometry?.computeCastShadows(this.#castShadows); } updateIsPickable() { if (this.isInBoundingObject()) return; - this.geometry?.setPickable(this.isPickable); + this.geometry?.setPickable(this.#isPickable); } updateGeometryMaterial() { diff --git a/resources/web/wwi/nodes/WbSlot.js b/resources/web/wwi/nodes/WbSlot.js index bbb95c0dcbb..7ae6ffd463f 100644 --- a/resources/web/wwi/nodes/WbSlot.js +++ b/resources/web/wwi/nodes/WbSlot.js @@ -71,7 +71,7 @@ export default class WbSlot extends WbBaseNode { } slotEndPoint() { - if (typeof this.endPoint !== 'undefined' && this.endPoint instanceof WbSlot) + if (typeof this.endPoint !== 'undefined' && this.endPoint.nodeType === WbNodeType.WB_NODE_SLOT) return this.endPoint; } diff --git a/resources/web/wwi/nodes/utils/utils.js b/resources/web/wwi/nodes/utils/utils.js index 86de69172ef..f6c3be5cd92 100644 --- a/resources/web/wwi/nodes/utils/utils.js +++ b/resources/web/wwi/nodes/utils/utils.js @@ -1,6 +1,5 @@ import WbVector3 from './WbVector3.js'; import WbVector4 from './WbVector4.js'; -import WbWorld from '../WbWorld.js'; function array3Pointer(x, y, z) { const data = new Float32Array([x, y, z]); diff --git a/resources/wren/shaders/merge_spherical.frag b/resources/wren/shaders/merge_spherical.frag index 1fff4b5b25e..0c3558caddd 100644 --- a/resources/wren/shaders/merge_spherical.frag +++ b/resources/wren/shaders/merge_spherical.frag @@ -14,7 +14,7 @@ precision highp float; const float FLT_MAX = intBitsToFloat(0x7F800000); -const vec3 orientations[6] = vec3[6](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), +const vec3 orientations[6] = vec3[6](vec3(1.0, 0.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)); in vec2 texUv; @@ -34,7 +34,121 @@ uniform float fovYCorrectionCoefficient; uniform sampler2D inputTextures[6]; +// Same as texture but pick the side to used for the interpolation to avoid discontinuities +vec4 textureSmooth(sampler2D tex, vec2 p, int bias) { + ivec2 texSize = textureSize(tex, 0); + ivec2 pixelCoord = ivec2(round((2 * texSize.x * p.x - 1) / 2), round((2 * texSize.y * p.y - 1) / 2)); + + float dx = ((2 * texSize.x * p.x - 1) / 2 - pixelCoord.x); + float dy = ((2 * texSize.y * p.y - 1) / 2 - pixelCoord.y); + + // Get depth for each pixel in a 3 by 3 grid + float dCenter = texelFetch(tex, pixelCoord, bias).x; + float dUp = texelFetch(tex, pixelCoord + ivec2(0, 1), bias).x; + float dDown = texelFetch(tex, pixelCoord + ivec2(0, -1), bias).x; + float dLeft = texelFetch(tex, pixelCoord + ivec2(-1, 0), bias).x; + float dRight = texelFetch(tex, pixelCoord + ivec2(1, 0), bias).x; + float dUpLeft = texelFetch(tex, pixelCoord + ivec2(-1, 1), bias).x; + float dDownRight = texelFetch(tex, pixelCoord + ivec2(1, -1), bias).x; + float dDownLeft = texelFetch(tex, pixelCoord + ivec2(-1, -1), bias).x; + float dUpRight = texelFetch(tex, pixelCoord + ivec2(1, 1), bias).x; + + int xSide = 1, ySide = 1; + if (texSize.x != 1 && texSize.y != 1) { + // Compute variance for each corner + float costUpRight = + (dUp - dCenter) * (dUp - dCenter) + (dUpRight - dCenter) * (dUpRight - dCenter) + (dRight - dCenter) * (dRight - dCenter); + float costDownRight = (dDown - dCenter) * (dDown - dCenter) + (dDownRight - dCenter) * (dDownRight - dCenter) + + (dRight - dCenter) * (dRight - dCenter); + float costUpLeft = + (dUp - dCenter) * (dUp - dCenter) + (dUpLeft - dCenter) * (dUpLeft - dCenter) + (dLeft - dCenter) * (dLeft - dCenter); + float costDownLeft = (dDown - dCenter) * (dDown - dCenter) + (dDownLeft - dCenter) * (dDownLeft - dCenter) + + (dLeft - dCenter) * (dLeft - dCenter); + + // Deal with image borders + if (pixelCoord.x == 0) { + costUpLeft = FLT_MAX; + costDownLeft = FLT_MAX; + } + if (pixelCoord.x + 1 > texSize.x - 1) { + costUpRight = FLT_MAX; + costDownRight = FLT_MAX; + } + if (pixelCoord.y == 0) { + costDownLeft = FLT_MAX; + costDownRight = FLT_MAX; + } + if (pixelCoord.y + 1 > texSize.y - 1) { + costUpLeft = FLT_MAX; + costUpRight = FLT_MAX; + } + // Find the corner with lowest variance + float costMin = costUpRight; + if (costDownRight < costMin) { + xSide = 1; + ySide = -1; + costMin = costDownRight; + } + if (costDownLeft < costMin) { + xSide = -1; + ySide = -1; + costMin = costDownLeft; + } + if (costUpLeft < costMin) { + xSide = -1; + ySide = 1; + } + + vec4 c00 = texelFetch(tex, pixelCoord, bias); + vec4 c01 = texelFetch(tex, pixelCoord + ivec2(xSide, 0), bias); + vec4 c10 = texelFetch(tex, pixelCoord + ivec2(0, ySide), bias); + vec4 c11 = texelFetch(tex, pixelCoord + ivec2(xSide, ySide), bias); + + if (isinf(c00.x) || isinf(c01.x) || isinf(c10.x) || isinf(c11.x)) + return c00; + else { + vec4 c0 = c00 + (c01 - c00) * dx * xSide; + vec4 c1 = c10 + (c11 - c10) * dx * xSide; + + vec4 c = c0 + (c1 - c0) * dy * ySide; + + return c; + } + } else if (texSize.x == 1 && texSize.y == 1) { // No interpolation + return texelFetch(tex, ivec2(pixelCoord.x, pixelCoord.y), bias); + } else if (texSize.y == 1) { // Linear interpolation + // Pick side with smallest variance (accounting for edges) + if ((pixelCoord.x == texSize.x - 1) || ((pixelCoord.x != 0) && (abs(dRight - dCenter) > abs(dLeft - dCenter)))) + xSide = -1; + else + xSide = 1; + + vec4 c0 = texelFetch(tex, pixelCoord, bias); + vec4 c1 = texelFetch(tex, pixelCoord + ivec2(xSide, 0), bias); + if (isinf(c0.x) || isinf(c1.x)) + return c0; + else + return c0 + (c1 - c0) * dx; + } else if (texSize.x == 1) { // Linear interpolation + // Pick side with smallest variance (accounting for edges) + if ((pixelCoord.y == texSize.y - 1) || ((pixelCoord.y != 0) && (abs(dUp - dCenter) > abs(dDown - dCenter)))) + ySide = -1; + else + ySide = 1; + + vec4 c0 = texelFetch(tex, pixelCoord, bias); + vec4 c1 = texelFetch(tex, pixelCoord + ivec2(0, ySide), bias); + if (isinf(c0.x) || isinf(c1.x)) + return c0; + else + return c0 + (c1 - c0) * dy; + } +} + void main() { + ivec2 texSize = textureSize(inputTextures[FRONT], 0); + ivec2 pixelCoord = ivec2(round((texUv.x - 1 / texSize.x / 2) * texSize.x), round((texUv.y - 1 / texSize.y / 2) * texSize.y)); + vec3 coord3d; if (cylindrical) { @@ -61,46 +175,30 @@ void main() { coord3d = vec3(sx, sy, sz); } - // normalize the 3d coordinate - vec3 coord3dAbs = abs(coord3d); - int maxIndex = 0; - float signedValue = coord3d.x; - float absMax = coord3dAbs.x; - if (coord3dAbs.y > absMax) { - maxIndex = 1; - signedValue = coord3d.y; - absMax = coord3dAbs.y; - } - if (coord3dAbs.z > absMax) { - maxIndex = 2; - signedValue = coord3d.z; - absMax = coord3dAbs.z; + // determine on which face the 3d coordinate belongs + float maxDot = dot(coord3d, orientations[0]); + int face = FRONT; + for (int i = 1; i < 6; ++i) { + float current_dot = dot(vec3(coord3d.xy, coord3d.z / tan(min(fovY, pi_2) / 2)), orientations[i]); + if (maxDot < current_dot) { + maxDot = current_dot; + face = i; + } } + // scale the 3d coordinate to be on the cube + float absMax = 0.0; + if (face == FRONT || face == BACK) + absMax = abs(coord3d.x); + else if (face == LEFT || face == RIGHT) + absMax = abs(coord3d.y); + else if (face == UP || face == DOWN) + absMax = abs(coord3d.z / tan(min(fovY, pi_2) / 2)); + vec3 normalizedCoord3d = coord3d; if (absMax > 0.0) normalizedCoord3d /= absMax; - // determine on which face the 3d coordinate hits the cube - bool isNegative = (signedValue < 0.0); - int face = FRONT; - if (maxIndex == 0) { - if (isNegative) - face = BACK; - else - face = FRONT; - } else if (maxIndex == 1) { - if (isNegative) - face = RIGHT; - else - face = LEFT; - } else if (maxIndex == 2) { - if (isNegative) - face = DOWN; - else - face = UP; - } - // retrieve the x-y coordinate relatively to the current face // according to the 3D coordinate vec2 coord = vec2(0.0); @@ -121,39 +219,34 @@ void main() { coord.y = normalizedCoord3d.z; } - if (fovX < pi_2) - coord.x *= pi_2 / fovX; - if (fovY < pi_2) - coord.y *= pi_2 / fovY * fovYCorrectionCoefficient; + if (face != UP && face != DOWN) { + if (fovX < pi_2) + coord.x /= tan(fovX / 2); + if (fovY < pi_2) + coord.y /= tan(fovY / 2); + } vec2 faceCoord = vec2(0.5 * (1.0 - coord.x), 0.5 * (1.0 - coord.y)); - ivec2 imageIndex = ivec2(round(faceCoord.x * (subCamerasResolutionX - 1)), round(faceCoord.y * (subCamerasResolutionY - 1))); fragColor = vec4(0.0, 0.0, 0.0, 1.0); if (face == FRONT) - fragColor = texelFetch(inputTextures[0], imageIndex, 0); + fragColor = textureSmooth(inputTextures[0], faceCoord, 0); else if (face == RIGHT) - fragColor = texelFetch(inputTextures[1], imageIndex, 0); + fragColor = textureSmooth(inputTextures[1], faceCoord, 0); else if (face == BACK) - fragColor = texelFetch(inputTextures[2], imageIndex, 0); + fragColor = textureSmooth(inputTextures[2], faceCoord, 0); else if (face == LEFT) - fragColor = texelFetch(inputTextures[3], imageIndex, 0); + fragColor = textureSmooth(inputTextures[3], faceCoord, 0); else if (face == UP) - fragColor = texelFetch(inputTextures[4], imageIndex, 0); + fragColor = textureSmooth(inputTextures[4], faceCoord, 0); else if (face == DOWN) - fragColor = texelFetch(inputTextures[5], imageIndex, 0); + fragColor = textureSmooth(inputTextures[5], faceCoord, 0); // rectify the spherical transform if (rangeCamera) { float depth = fragColor.x; if (depth < maxRange) { - float cosine = 0.0f; - for (int i = 0; i < 6; ++i) { - float cosineTmp = dot(normalizedCoord3d, orientations[i]); - cosineTmp = cosineTmp / length(normalizedCoord3d); - if (cosineTmp > cosine) - cosine = cosineTmp; - } + float cosine = dot(coord3d, orientations[face]); depth = depth / cosine; } if (depth < minRange) diff --git a/scripts/icon_studio/controllers/icon_creator/objects.json b/scripts/icon_studio/controllers/icon_creator/objects.json index 4e23c2c4a64..fd9b05f3375 100644 --- a/scripts/icon_studio/controllers/icon_creator/objects.json +++ b/scripts/icon_studio/controllers/icon_creator/objects.json @@ -324,6 +324,10 @@ "fields": " rotation 0.0 0.0 1.0 5.01239", "nodeString": " Windmill{ rotation 0.0 0.0 1.0 5.01239 }" }, + "projects/buildings/protos/Silo": { + "fields": " rotation 0.0 0.0 1.0 0.0", + "nodeString": " Silo { rotation 0 0 1 -0.655 }" + }, "projects/objects/cabinet/protos/Cabinet": { "fields": " rotation 0.0 0.0 1.0 -2.0561953071795864", "nodeString": " Cabinet{ rotation 0.0 0.0 1.0 -2.0561953071795864 }" @@ -1786,4 +1790,5 @@ }, "projects/vehicles/protos/toyota/ToyotaPriusSimple.proto": { "nodeString": " ToyotaPriusSimple{ rotation 0 0 1 3.92699 }" + } } diff --git a/scripts/icon_studio/worlds/icon_studio.wbt b/scripts/icon_studio/worlds/icon_studio.wbt index 55e85edae4d..3410c0472be 100644 --- a/scripts/icon_studio/worlds/icon_studio.wbt +++ b/scripts/icon_studio/worlds/icon_studio.wbt @@ -61,6 +61,7 @@ IMPORTABLE EXTERNPROTO "webots://projects/objects/buildings/protos/TheThreeTower IMPORTABLE EXTERNPROTO "webots://projects/objects/buildings/protos/UBuilding.proto" IMPORTABLE EXTERNPROTO "webots://projects/objects/buildings/protos/Warehouse.proto" IMPORTABLE EXTERNPROTO "webots://projects/objects/buildings/protos/Windmill.proto" +IMPORTABLE EXTERNPROTO "webots://projects/objects/buildings/protos/Silo.proto" IMPORTABLE EXTERNPROTO "webots://projects/objects/cabinet/protos/Cabinet.proto" IMPORTABLE EXTERNPROTO "webots://projects/objects/cabinet/protos/CabinetHandle.proto" IMPORTABLE EXTERNPROTO "webots://projects/objects/chairs/protos/Chair.proto" diff --git a/scripts/install/bash_profile.mac b/scripts/install/bash_profile.mac index afd0b3779fe..9bbb812728a 100644 --- a/scripts/install/bash_profile.mac +++ b/scripts/install/bash_profile.mac @@ -1,3 +1,4 @@ + ##################################################################################################### # Uncomment / edit the following environment variables when the corresponding software is installed # ##################################################################################################### diff --git a/scripts/install/bash_profile.windows b/scripts/install/bash_profile.windows index eb19cd1172c..415bc8ad90c 100644 --- a/scripts/install/bash_profile.windows +++ b/scripts/install/bash_profile.windows @@ -1,3 +1,4 @@ + ##################################################################################################### # Uncomment / edit the following environment variables when the corresponding software is installed # ##################################################################################################### diff --git a/scripts/install/bashrc.linux b/scripts/install/bashrc.linux index b4237ca5663..0da85c8f14e 100644 --- a/scripts/install/bashrc.linux +++ b/scripts/install/bashrc.linux @@ -1,3 +1,4 @@ + ##################################################################################################### # Uncomment / edit the following environment variables when the corresponding software is installed # ##################################################################################################### diff --git a/scripts/install/linux_optional_compilation_dependencies.sh b/scripts/install/linux_optional_compilation_dependencies.sh index 1e0f56e1c21..812413cbed6 100755 --- a/scripts/install/linux_optional_compilation_dependencies.sh +++ b/scripts/install/linux_optional_compilation_dependencies.sh @@ -29,4 +29,7 @@ fi script_full_path=$(dirname "$0") $script_full_path/linux_test_dependencies.sh --norecurse $script_full_path/linux_compilation_dependencies.sh -$script_full_path/linux_web_viewer_dependencies.sh + +if [[ -z "$SNAP" ]]; then + $script_full_path/linux_web_viewer_dependencies.sh +fi diff --git a/scripts/install/qt_linux_installer.sh b/scripts/install/qt_linux_installer.sh index 11b37c0fb76..d44cfa4402c 100755 --- a/scripts/install/qt_linux_installer.sh +++ b/scripts/install/qt_linux_installer.sh @@ -49,6 +49,9 @@ mkdir lib/webots/qt/plugins/platforminputcontexts mkdir lib/webots/qt/plugins/printsupport mkdir lib/webots/qt/plugins/tls mkdir lib/webots/qt/plugins/xcbglintegrations +mkdir lib/webots/qt/plugins/wayland-graphics-integration-client +mkdir lib/webots/qt/plugins/wayland-shell-integration +mkdir lib/webots/qt/plugins/wayland-decoration-client mkdir lib/webots/qt/resources mkdir lib/webots/qt/translations @@ -85,12 +88,17 @@ cp $QT_INSTALLATION_PATH/lib/libicui18n.so.$ICU_VERSION.1 lib/webots/ cp $QT_INSTALLATION_PATH/lib/libicuuc.so.$ICU_VERSION.1 lib/webots/ echo $'[Paths]\nPrefix = ..\n' > lib/webots/qt/libexec/qt.conf cp $QT_INSTALLATION_PATH/plugins/platforms/libqxcb.so lib/webots/qt/plugins/platforms/ +cp $QT_INSTALLATION_PATH/plugins/platforms/libqwayland-egl.so lib/webots/qt/plugins/platforms/ +cp $QT_INSTALLATION_PATH/plugins/platforms/libqwayland-generic.so lib/webots/qt/plugins/platforms/ cp $QT_INSTALLATION_PATH/plugins/platformthemes/libqgtk3.so lib/webots/qt/plugins/platformthemes/ cp $QT_INSTALLATION_PATH/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so lib/webots/qt/plugins/platforminputcontexts/ cp $QT_INSTALLATION_PATH/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so lib/webots/qt/plugins/platforminputcontexts/ cp $QT_INSTALLATION_PATH/plugins/printsupport/libcupsprintersupport.so lib/webots/qt/plugins/printsupport/ cp $QT_INSTALLATION_PATH/plugins/tls/*.so lib/webots/qt/plugins/tls/ cp $QT_INSTALLATION_PATH/plugins/xcbglintegrations/libqxcb-glx-integration.so lib/webots/qt/plugins/xcbglintegrations/ +cp $QT_INSTALLATION_PATH/plugins/wayland-graphics-integration-client/* lib/webots/qt/plugins/wayland-graphics-integration-client/ +cp $QT_INSTALLATION_PATH/plugins/wayland-shell-integration/* lib/webots/qt/plugins/wayland-shell-integration/ +cp $QT_INSTALLATION_PATH/plugins/wayland-decoration-client/* lib/webots/qt/plugins/wayland-decoration-client/ cp $QT_INSTALLATION_PATH/plugins/imageformats/libqjpeg.so lib/webots/qt/plugins/imageformats/ cp -r $QT_INSTALLATION_PATH/translations/qt_* lib/webots/qt/translations/ cp -r $QT_INSTALLATION_PATH/translations/qtbase_* lib/webots/qt/translations/ @@ -124,6 +132,12 @@ ln -sf libQt6Xml.so.$QT_VERSION libQt6Xml.so.6 ln -sf libQt6Xml.so.$QT_VERSION libQt6Xml.so ln -sf libQt6XcbQpa.so.$QT_VERSION libQt6XcbQpa.so.6 ln -sf libQt6XcbQpa.so.$QT_VERSION libQt6XcbQpa.so +ln -sf libQt6WaylandClient.so.$QT_VERSION libQt6WaylandClient.so.6 +ln -sf libQt6WaylandClient.so.$QT_VERSION libQt6WaylandClient.so +ln -sf libQt6WaylandEglClientHwIntegration.so.$QT_VERSION libQt6WaylandEglClientHwIntegration.so.6 +ln -sf libQt6WaylandEglClientHwIntegration.so.$QT_VERSION libQt6WaylandEglClientHwIntegration.so +ln -sf libQt6WlShellIntegration.so.$QT_VERSION libQt6WlShellIntegration.so.6 +ln -sf libQt6WlShellIntegration.so.$QT_VERSIOn libQt6WlShellIntegration.so ln -sf libicudata.so.$ICU_VERSION.1 libicudata.so.$ICU_VERSION ln -sf libicui18n.so.$ICU_VERSION.1 libicui18n.so.$ICU_VERSION ln -sf libicuuc.so.$ICU_VERSION.1 libicuuc.so.$ICU_VERSION diff --git a/scripts/packaging/files_core.txt b/scripts/packaging/files_core.txt index d3b2ebf422f..b114b239a83 100644 --- a/scripts/packaging/files_core.txt +++ b/scripts/packaging/files_core.txt @@ -111,6 +111,9 @@ lib/webots/libQt6Qml.so* [linux] lib/webots/libQt6WebSockets.so* [linux] lib/webots/libQt6Widgets.so* [linux] lib/webots/libQt6XcbQpa.so* [linux] +lib/webots/libQt6WaylandClient.so* [linux] +lib/webots/libQt6WaylandEglClientHwIntegration.so* [linux] +lib/webots/libQt6WlShellIntegration.so* [linux] lib/webots/libQt6Xml.so* [linux] lib/webots/libicudata.so* [linux] lib/webots/libicui18n.so* [linux] @@ -134,6 +137,12 @@ lib/webots/qt/plugins/tls/ [linux] lib/webots/qt/plugins/tls/*.so [linux] lib/webots/qt/plugins/xcbglintegrations/ [linux] lib/webots/qt/plugins/xcbglintegrations/libqxcb-glx-integration.so [linux] +lib/webots/qt/plugins/wayland-graphics-integration-client/ [linux] +lib/webots/qt/plugins/wayland-graphics-integration-client/* [linux] +lib/webots/qt/plugins/wayland-decoration-client/ [linux] +lib/webots/qt/plugins/wayland-decoration-client/* [linux] +lib/webots/qt/plugins/wayland-shell-integration/ [linux] +lib/webots/qt/plugins/wayland-shell-integration/* [linux] lib/webots/qt/translations/ [linux] lib/webots/qt/translations/*.qm [linux] lib/webots/qt/libexec/ [linux] diff --git a/scripts/packaging/webots_version.txt b/scripts/packaging/webots_version.txt index d0235d228a2..0b9133bdfb4 100644 --- a/scripts/packaging/webots_version.txt +++ b/scripts/packaging/webots_version.txt @@ -1 +1 @@ -R2023b +R2023b revision 1 diff --git a/src/controller/c/Makefile b/src/controller/c/Makefile index e537e615fc8..4b4413aa254 100644 --- a/src/controller/c/Makefile +++ b/src/controller/c/Makefile @@ -172,6 +172,8 @@ $(TARGET): $(OBJECTS) @chmod a-x $(TARGET) endif +$(OBJDIR)/robot.o: $(WEBOTS_HOME_PATH)/resources/version.txt + $(OBJDIR)/%.o:%.c @echo "# compiling "$< @$(CC) -c $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) $< -o $@ diff --git a/src/controller/c/robot.c b/src/controller/c/robot.c index 3e59646f8c4..82737af5e20 100644 --- a/src/controller/c/robot.c +++ b/src/controller/c/robot.c @@ -358,8 +358,7 @@ static void robot_configure(WbRequest *r) { robot.device[0]->name = request_read_string(r); WEBOTS_VERSION = request_read_string(r); - if (strlen(WEBOTS_VERSION) && (strlen(WEBOTS_VERSION) != strlen(LIBCONTROLLER_VERSION) || - strncmp(WEBOTS_VERSION, LIBCONTROLLER_VERSION, strlen(WEBOTS_VERSION)))) + if (strlen(WEBOTS_VERSION) && (strncmp(WEBOTS_VERSION, LIBCONTROLLER_VERSION, 6))) fprintf(stderr, "Warning: Webots [%s] and libController [%s] versions are not the same for Robot '%s'! Different versions can lead " "to undefined behavior.\n", diff --git a/src/controller/launcher/webots_controller.c b/src/controller/launcher/webots_controller.c index 95150393964..4a2a387adf8 100644 --- a/src/controller/launcher/webots_controller.c +++ b/src/controller/launcher/webots_controller.c @@ -44,6 +44,7 @@ static char *controller; static char *controller_path; static char *controller_extension; static char *matlab_path; +static char *matlab_args; static char *current_path; static int nb_controller_arguments; static int next_argument_index; @@ -168,54 +169,60 @@ static bool get_webots_home() { // Gets and stores the path to the latest installed version of Matlab on the system. static bool get_matlab_path() { struct dirent *directory_entry; // Pointer for directory entry + #ifdef __APPLE__ const char *matlab_directory = "/Applications/"; const char *matlab_version_wc = "MATLAB_R20"; -#else - const char *matlab_version_wc = "R20"; -#ifdef _WIN32 + const char *matlab_exec_suffix = "/bin/matlab"; +#elif _WIN32 const char *matlab_directory = "C:\\Program Files\\MATLAB\\"; + const char *matlab_version_wc = "R20"; const char *matlab_exec_suffix = "\\bin\\matlab.exe"; -#else // __linux__ +#elif __linux__ const char *matlab_directory = "/usr/local/MATLAB/"; + const char *matlab_version_wc = "R20"; const char *matlab_exec_suffix = "/bin/matlab"; -#endif +#else +#error "OS not supported!" #endif DIR *directory = opendir(matlab_directory); -#ifndef __APPLE__ if (directory == NULL) { - fprintf(stderr, "No installation of Matlab available.\n"); +#ifdef __APPLE__ + fprintf(stderr, "Could not open Applications folder to search for MATLAB installation. Please specify it manually using " + "the option '--matlab-path'.\n"); +#else + fprintf(stderr, "No installation of MATLAB found. Please specify it manually using the option '--matlab-path'.\n"); +#endif return false; } -#endif + // Get latest available Matlab version char *latest_version = NULL; while ((directory_entry = readdir(directory)) != NULL) { const size_t directory_name_size = strlen(directory_entry->d_name) + 1; if (strncmp(matlab_version_wc, directory_entry->d_name, strlen(matlab_version_wc)) == 0) { - if (!latest_version) + if (!latest_version) { latest_version = malloc(directory_name_size); - else if (strcmp(latest_version, directory_entry->d_name) < 0) - memset(latest_version, '\0', directory_name_size); - strncpy(latest_version, directory_entry->d_name, directory_name_size); + strncpy(latest_version, directory_entry->d_name, directory_name_size); + } else if (strcmp(latest_version, directory_entry->d_name) < 0) { + char *tmp = realloc(latest_version, directory_name_size); + if (tmp) + latest_version = tmp; + strncpy(latest_version, directory_entry->d_name, directory_name_size); + } } } closedir(directory); if (!latest_version) { - fprintf(stderr, "No installation of Matlab available.\n"); + fprintf(stderr, "No installation of MATLAB found. Please specify it manually using the option '--matlab-path'.\n"); return false; } -#ifdef __APPLE__ - const size_t matlab_path_size = snprintf(NULL, 0, "%s%s", matlab_directory, latest_version) + 1; - matlab_path = malloc(matlab_path_size); - sprintf(matlab_path, "%s%s", matlab_directory, latest_version); -#else const size_t matlab_path_size = snprintf(NULL, 0, "%s%s%s", matlab_directory, latest_version, matlab_exec_suffix) + 1; matlab_path = malloc(matlab_path_size); sprintf(matlab_path, "%s%s%s", matlab_directory, latest_version, matlab_exec_suffix); -#endif + printf("Using the latest available MATLAB instance: %s\n", matlab_path); free(latest_version); return true; @@ -233,11 +240,12 @@ static void print_options() { "connect.\n 1234 is used by default, as it is the default port for Webots.\n This setting allows you to connect to a " "specific instance of Webots if\n there are multiple instances running on the target machine.\n The port of a Webots " "instance can be set at its launch.\n\n --robot-name=\n Target a specific robot by specifying its name in " - "case multiple robots wait\n for an extern controller in the Webots instance.\n\n --matlab-path=\n For " - "MATLAB controllers, this option allows to specify the path to the\n executable of a specific MATLAB version.\n By " - "default, the launcher checks in the default MATLAB installation folder.\n See " - "https://cyberbotics.com/doc/guide/running-extern-robot-controllers#running-a-matlab-extern-controller\n for more " - "information.\n\n --stdout-redirect\n Redirect the stdout of the controller to the Webots console.\n\n " + "case multiple robots wait\n for an extern controller in the Webots instance.\n\n --interactive\n Launch MATLAB " + "in interactive debugging mode.\n See https://cyberbotics.com/doc/guide/matlab#using-the-matlab-desktop for\n more " + "information.\n\n --matlab-path=\n For MATLAB controllers, this option allows to specify the path to the " + "\n executable of a specific MATLAB version.\n By default, the launcher checks in the default MATLAB installation " + "folder.\n See https://cyberbotics.com/doc/guide/running-extern-robot-controllers#running-a-matlab-extern-controller\n" + " for more information.\n\n --stdout-redirect\n Redirect the stdout of the controller to the Webots console.\n\n " "--stderr-redirect\n Redirect the stderr of the controller to the Webots console.\n\n"); } @@ -250,6 +258,7 @@ static bool parse_options(int nb_arguments, char **arguments) { controller = NULL; matlab_path = NULL; + matlab_args = NULL; char *protocol = NULL; char *ip_address = NULL; char *port = NULL; @@ -273,6 +282,14 @@ static bool parse_options(int nb_arguments, char **arguments) { const size_t robot_name_size = strlen(arguments[i] + 13) + 1; robot_name = malloc(robot_name_size); memcpy(robot_name, arguments[i] + 13, robot_name_size); + } else if (strncmp(arguments[i], "--interactive", 13) == 0) { +#ifdef _WIN32 + matlab_args = malloc(strlen("-wait -r") + 1); + sprintf(matlab_args, "-wait -r"); +#else + matlab_args = malloc(strlen("-r") + 1); + sprintf(matlab_args, "-r"); +#endif } else if (strncmp(arguments[i], "--matlab-path=", 14) == 0) { const size_t matlab_path_size = strlen(arguments[i] + 14) + 1; matlab_path = malloc(matlab_path_size); @@ -344,11 +361,12 @@ static bool parse_options(int nb_arguments, char **arguments) { // Show resulting target options to user const char *location = strncmp(protocol, "tcp", 3) == 0 ? "remote" : "local"; - printf("The started controller targets a %s instance (%s protocol) of Webots with port number %s.", location, protocol, port); - strncmp(protocol, "tcp", 3) == 0 ? printf(" The IP address of the remote Webots instance is '%s'. ", ip_address) : - printf(" "); - robot_name ? printf("Targeting robot '%s'.\n\n", robot_name) : - printf("Targeting the only robot waiting for an extern controller.\n\n"); + printf("\nThe started controller targets a %s instance (%s protocol) of Webots with port number %s.", location, protocol, + port); + strncmp(protocol, "tcp", 3) == 0 ? printf(" The IP address of the remote Webots instance is '%s'.\n", ip_address) : + printf("\n"); + robot_name ? printf("Targeting robot '%s'.\n", robot_name) : + printf("Targeting the only robot waiting for an extern controller.\n"); free(protocol); free(ip_address); @@ -723,6 +741,7 @@ static char **add_controller_arguments(char **argv, char **controller_argv, size static void free_memory() { free(WEBOTS_HOME); free(matlab_path); + free(matlab_args); free(current_path); free(controller); @@ -885,6 +904,13 @@ int main(int argc, char **argv) { if (!matlab_path && !get_matlab_path()) return -1; + if (!matlab_args) { + matlab_args = malloc(strlen("-batch") + 1); + sprintf(matlab_args, "-batch"); + } else { + printf("Running MATLAB in interactive mode...\n"); + } + #ifdef _WIN32 const char *launcher_path = "\\lib\\controller\\matlab"; #elif defined __APPLE__ @@ -902,7 +928,7 @@ int main(int argc, char **argv) { char **new_argv = NULL; new_argv = add_single_argument(new_argv, ¤t_size, matlab_path); new_argv = add_single_argument(new_argv, ¤t_size, matlab_command); - new_argv = add_single_argument(new_argv, ¤t_size, "-batch"); + new_argv = add_single_argument(new_argv, ¤t_size, matlab_args); new_argv = add_single_argument(new_argv, ¤t_size, "launcher"); if (nb_controller_arguments) new_argv = add_controller_arguments(new_argv, argv, ¤t_size, true); diff --git a/src/webots/app/WbSelection.cpp b/src/webots/app/WbSelection.cpp index b836b7e887a..b483212c339 100644 --- a/src/webots/app/WbSelection.cpp +++ b/src/webots/app/WbSelection.cpp @@ -79,7 +79,7 @@ void WbSelection::selectNode(WbBaseNode *n, bool handlesDisabled) { mSelectedNode = n; if (poseChanged) - mSelectedAbstractPose = mSelectedNode ? dynamic_cast(mSelectedNode) : NULL; + mSelectedAbstractPose = dynamic_cast(mSelectedNode); mResizeHandlesEnabledFromSceneTree = false; if (mSelectedNode) { diff --git a/src/webots/core/WbApplicationInfo.cpp b/src/webots/core/WbApplicationInfo.cpp index d0a5c7a32f4..9efa015b473 100644 --- a/src/webots/core/WbApplicationInfo.cpp +++ b/src/webots/core/WbApplicationInfo.cpp @@ -28,7 +28,7 @@ const WbVersion &WbApplicationInfo::version() { static bool firstCall = true; if (firstCall) { - static QString webotsVersionString = "R2023b"; // updated by script + static QString webotsVersionString = "R2023b revision 1"; // updated by script bool success = webotsVersion.fromString(webotsVersionString); if (!success) WbLog::fatal(QObject::tr("Internal error: the Webots version is not computable.")); diff --git a/src/webots/core/WbTemplateEngine.cpp b/src/webots/core/WbTemplateEngine.cpp index c0c132abc27..0f678932e44 100644 --- a/src/webots/core/WbTemplateEngine.cpp +++ b/src/webots/core/WbTemplateEngine.cpp @@ -137,6 +137,11 @@ const QString &WbTemplateEngine::closingToken() { return gClosingToken; } +QString WbTemplateEngine::escapeString(const QString &string) { + QString escaped(string); + return escaped.replace("\\", "\\\\").replace("\n", "\\n").replace("'", "\\'").toUtf8(); +} + bool WbTemplateEngine::generate(QHash tags, const QString &logHeaderName, const QString &templateLanguage) { bool output; @@ -340,11 +345,7 @@ bool WbTemplateEngine::generateLua(QHash tags, const QString & tags["cpath"] = "package.cpath = package.cpath .. \";?.dylib\""; #endif - tags["templateContent"] = mTemplateContent; - tags["templateContent"] = tags["templateContent"].replace("\\", "\\\\"); - tags["templateContent"] = tags["templateContent"].replace("\n", "\\n"); - tags["templateContent"] = tags["templateContent"].replace("'", "\\'"); - tags["templateContent"] = tags["templateContent"].toUtf8(); + tags["templateContent"] = escapeString(mTemplateContent); // make sure these key are set if (!tags.contains("fields")) diff --git a/src/webots/core/WbTemplateEngine.hpp b/src/webots/core/WbTemplateEngine.hpp index 1a51a3e4720..a98f3ff44be 100644 --- a/src/webots/core/WbTemplateEngine.hpp +++ b/src/webots/core/WbTemplateEngine.hpp @@ -31,6 +31,7 @@ class WbTemplateEngine : public QObject { static void setOpeningToken(const QString &token); static void setClosingToken(const QString &token); + static QString escapeString(const QString &string); explicit WbTemplateEngine(const QString &templateContent); virtual ~WbTemplateEngine() {} diff --git a/src/webots/gui/WbMainWindow.cpp b/src/webots/gui/WbMainWindow.cpp index 38b1b54fc06..4977a97b04a 100644 --- a/src/webots/gui/WbMainWindow.cpp +++ b/src/webots/gui/WbMainWindow.cpp @@ -180,7 +180,7 @@ WbMainWindow::WbMainWindow(bool minimizedOnStart, WbTcpServer *tcpServer, QWidge mFactoryLayout = new QByteArray(saveState()); - updateGui(); + updateWindowTitle(); if (WbPreferences::instance()->value("General/checkWebotsUpdateOnStartup").toBool() && WbMessageBox::enabled()) { WbWebotsUpdateManager *webotsUpdateManager = WbWebotsUpdateManager::instance(); @@ -383,8 +383,6 @@ void WbMainWindow::createMainTools() { setCentralWidget(mSimulationView); addDock(mSimulationView); connect(mSimulationView, &WbSimulationView::requestOpenUrl, this, &WbMainWindow::openUrl); - connect(mSimulationView->selection(), &WbSelection::selectionChangedFromSceneTree, this, &WbMainWindow::updateOverlayMenu); - connect(mSimulationView->selection(), &WbSelection::selectionChangedFromView3D, this, &WbMainWindow::updateOverlayMenu); connect(mSimulationView->sceneTree(), &WbSceneTree::editRequested, this, &WbMainWindow::openFileInTextEditor); if (mTcpServer) { mTcpServer->setMainWindow(this); @@ -708,17 +706,12 @@ QMenu *WbMainWindow::createOverlayMenu() { mOverlayMenu = new QMenu(this); mOverlayMenu->setTitle(tr("&Overlays")); - mRobotCameraMenu = mOverlayMenu->addMenu(tr("Ca&mera Devices")); - mRobotRangeFinderMenu = mOverlayMenu->addMenu(tr("&RangeFinder Devices")); - mRobotDisplayMenu = mOverlayMenu->addMenu(tr("&Display Devices")); - - WbContextMenuGenerator::setRobotCameraMenu(mRobotCameraMenu); - WbContextMenuGenerator::setRobotRangeFinderMenu(mRobotRangeFinderMenu); - WbContextMenuGenerator::setRobotDisplayMenu(mRobotDisplayMenu); - mOverlayMenu->addAction(WbActionManager::instance()->action(WbAction::HIDE_ALL_CAMERA_OVERLAYS)); mOverlayMenu->addAction(WbActionManager::instance()->action(WbAction::HIDE_ALL_RANGE_FINDER_OVERLAYS)); mOverlayMenu->addAction(WbActionManager::instance()->action(WbAction::HIDE_ALL_DISPLAY_OVERLAYS)); + mOverlayMenu->addSeparator(); + + WbContextMenuGenerator::setOverlaysMenu(mOverlayMenu); return mOverlayMenu; } @@ -1023,6 +1016,11 @@ void WbMainWindow::closeEvent(QCloseEvent *event) { logActiveControllersTermination(); + if (WbWorld::instance()) { + disconnect(WbWorld::instance(), &WbWorld::robotAdded, this, &WbMainWindow::addRobotInOverlaysMenu); + disconnect(WbWorld::instance(), &WbWorld::robotRemoved, this, &WbMainWindow::removeRobotInOverlaysMenu); + } + // disconnect from file changed signal before saving the perspective // if the perspective file is open, a segmentation fault is generated // due to double free of the reload QMessageBox on Linux @@ -1259,6 +1257,7 @@ void WbMainWindow::restoreRenderingDevicesPerspective() { device->restorePerspective(devicePerspective); } disconnect(mSimulationView->view3D(), &WbView3D::resized, this, &WbMainWindow::restoreRenderingDevicesPerspective); + updateOverlayMenu(); } void WbMainWindow::loadDifferentWorld(const QString &fileName) { @@ -1311,6 +1310,10 @@ void WbMainWindow::loadWorld(const QString &fileName, bool reloading) { return; } mSimulationView->cancelSupervisorMovieRecording(); + if (WbWorld::instance()) { + disconnect(WbWorld::instance(), &WbWorld::robotAdded, this, &WbMainWindow::addRobotInOverlaysMenu); + disconnect(WbWorld::instance(), &WbWorld::robotRemoved, this, &WbMainWindow::removeRobotInOverlaysMenu); + } logActiveControllersTermination(); WbLog::setConsoleLogsPostponed(true); WbApplication::instance()->loadWorld(fileName, reloading); @@ -1376,8 +1379,11 @@ void WbMainWindow::updateAfterWorldLoading(bool reloading, bool firstLoad) { connect(world, &WbWorld::modificationChanged, this, &WbMainWindow::updateWindowTitle); connect(world, &WbWorld::resetRequested, this, &WbMainWindow::resetGui, Qt::QueuedConnection); + // update 'overlays' menu + connect(WbWorld::instance(), &WbWorld::robotAdded, this, &WbMainWindow::addRobotInOverlaysMenu); + connect(WbWorld::instance(), &WbWorld::robotRemoved, this, &WbMainWindow::removeRobotInOverlaysMenu); - updateGui(); + updateWindowTitle(); if (!reloading) WbActionManager::instance()->resetApplicationActionsState(); @@ -2107,11 +2113,6 @@ void WbMainWindow::updateWindowTitle() { setWindowTitle(title); } -void WbMainWindow::updateGui() { - updateWindowTitle(); - updateOverlayMenu(); -} - void WbMainWindow::simulationEnabledChanged(bool e) { mSimulationMenu->setEnabled(e); } @@ -2226,91 +2227,136 @@ void WbMainWindow::deleteRobotWindow(WbRobot *robot) { mOnSocketOpen = true; } -static bool isRobotNode(WbBaseNode *node) { - return dynamic_cast(node); +void WbMainWindow::clearOverlaysMenu() { + QList actions = mOverlayMenu->actions(); + while (actions.size() > 4) { + QAction *action = actions.last(); + if (action->menu()) + mOverlayMenu->removeAction(action); + actions.removeLast(); + } } -void WbMainWindow::updateOverlayMenu() { - QList actions; - WbRobot *selectedRobot = mSimulationView->selectedRobot(); - - // remove camera and display item list - actions = mRobotCameraMenu->actions(); - for (int i = 0; i < actions.size(); ++i) { - mRobotCameraMenu->removeAction(actions[i]); - delete actions[i]; - } - actions = mRobotRangeFinderMenu->actions(); - for (int i = 0; i < actions.size(); ++i) { - mRobotRangeFinderMenu->removeAction(actions[i]); - delete actions[i]; - } - actions = mRobotDisplayMenu->actions(); - for (int i = 0; i < actions.size(); ++i) { - mRobotDisplayMenu->removeAction(actions[i]); - delete actions[i]; - } - - // add current robot and descendant robot specific camera and display items - if (selectedRobot) { - QAction *action = NULL; - bool hasDisplay = false; - bool hasCamera = false; - bool hasRangeFinder = false; - QList robotList = WbNodeUtilities::findDescendantNodesOfType(selectedRobot, isRobotNode, true); - robotList.prepend(selectedRobot); - foreach (WbNode *robotNode, robotList) { - WbRobot *robot = reinterpret_cast(robotNode); - QList devices = robot->renderingDevices(); - for (int i = 0; i < devices.size(); ++i) { - const WbRenderingDevice *device = devices[i]; - QString deviceName = device->name(); +void WbMainWindow::updateRobotNameInOverlaysMenu() { + const WbRobot *robot = static_cast(sender()); + QListIterator it(mOverlayMenu->actions()); + while (it.hasNext()) { + const QAction *action = it.next(); + QMenu *menu = action->menu(); + if (menu && menu->property("robot").value() == robot) { + QString robotName = robot->name(); #ifdef __linux__ - // fix Unity bug with underscores in menu item text - if (qgetenv("XDG_CURRENT_DESKTOP") == "Unity") - deviceName.replace("_", "__"); + // fix Unity bug with underscores in menu item text + if (qgetenv("XDG_CURRENT_DESKTOP") == "Unity") + robotName.replace("_", "__"); #endif - action = new QAction(this); - if (robot != selectedRobot) - action->setText(tr("Show '%2' overlay of robot '%1'").arg(robot->name()).arg(deviceName)); - else - action->setText(tr("Show '%1' overlay").arg(deviceName)); - if (device->nodeType() == WB_NODE_CAMERA) { - action->setStatusTip(tr("Show overlay of camera device '%1'.").arg(deviceName)); - mRobotCameraMenu->addAction(action); - hasCamera = true; - } else if (device->nodeType() == WB_NODE_RANGE_FINDER) { - action->setStatusTip(tr("Show overlay of range-finder device '%1'.").arg(deviceName)); - mRobotRangeFinderMenu->addAction(action); - hasRangeFinder = true; - } else if (device->nodeType() == WB_NODE_DISPLAY) { - action->setStatusTip(tr("Show overlay of display device '%1'.").arg(deviceName)); - mRobotDisplayMenu->addAction(action); - hasDisplay = true; - } else { - delete action; - continue; - } - action->setToolTip(mToggleFullScreenAction->statusTip()); - action->setCheckable(true); - action->setChecked(device->isOverlayEnabled()); - action->setEnabled(!device->isWindowActive()); - action->setProperty("renderingDevice", - QVariant::fromValue(static_cast(const_cast(device)))); - connect(action, &QAction::toggled, mSimulationView->view3D(), &WbView3D::setShowRenderingDevice); - connect(device, &WbRenderingDevice::overlayVisibilityChanged, action, &QAction::setChecked); - connect(device, &WbRenderingDevice::overlayStatusChanged, action, &QAction::setEnabled); - } + menu->setTitle(tr("'%1' Overlays").arg(robotName)); } + } +} - mRobotDisplayMenu->setEnabled(hasDisplay); - mRobotCameraMenu->setEnabled(hasCamera); - mRobotRangeFinderMenu->setEnabled(hasRangeFinder); +void WbMainWindow::removeRobotInOverlaysMenu(const WbRobot *robot) { + QListIterator it(mOverlayMenu->actions()); + while (it.hasNext()) { + QAction *action = it.next(); + const QMenu *menu = action->menu(); + if (menu && menu->property("robot").value() == robot) { + mOverlayMenu->removeAction(action); + return; + } } +} + +void WbMainWindow::addRobotInOverlaysMenu(WbRobot *robot) { + QAction *action = NULL; + QList cameraActions; + QList rangeFinderActions; + QList displayActions; + + QString robotName = robot->name(); +#ifdef __linux__ + // fix Unity bug with underscores in menu item text + if (qgetenv("XDG_CURRENT_DESKTOP") == "Unity") + robotName.replace("_", "__"); +#endif + QMenu *robotMenu = mOverlayMenu->addMenu(tr("'%1' Overlays").arg(robotName)); + robotMenu->setProperty("robot", QVariant::fromValue(static_cast(const_cast(robot)))); + connect(robot, &WbMatter::matterNameChanged, this, &WbMainWindow::updateRobotNameInOverlaysMenu); + + QListIterator devicesIt(robot->renderingDevices()); + while (devicesIt.hasNext()) { + const WbRenderingDevice *device = devicesIt.next(); + QString deviceName = device->name(); +#ifdef __linux__ + // fix Unity bug with underscores in menu item text + if (qgetenv("XDG_CURRENT_DESKTOP") == "Unity") + deviceName.replace("_", "__"); +#endif + action = new QAction(this); + action->setText(tr("Show '%1' Overlay").arg(deviceName)); + if (device->nodeType() == WB_NODE_CAMERA) { + action->setStatusTip(tr("Show overlay of camera device '%1' for robot '%2'.").arg(deviceName).arg(robotName)); + cameraActions << action; + } else if (device->nodeType() == WB_NODE_RANGE_FINDER) { + action->setStatusTip(tr("Show overlay of range-finder device '%1' for robot '%2'.").arg(deviceName).arg(robotName)); + rangeFinderActions << action; + } else if (device->nodeType() == WB_NODE_DISPLAY) { + action->setStatusTip(tr("Show overlay of display device '%1' for robot '%2'.").arg(deviceName).arg(robotName)); + displayActions << action; + } else { + delete action; + continue; + } + action->setToolTip(mToggleFullScreenAction->statusTip()); + action->setCheckable(true); + action->setChecked(device->isOverlayEnabled()); + action->setEnabled(!device->isWindowActive()); + action->setProperty("renderingDevice", QVariant::fromValue(static_cast(const_cast(device)))); + connect(action, &QAction::toggled, mSimulationView->view3D(), &WbView3D::setShowRenderingDevice); + connect(device, &WbRenderingDevice::overlayVisibilityChanged, action, &QAction::setChecked); + connect(device, &WbRenderingDevice::overlayStatusChanged, action, &QAction::setEnabled); + } + + if (cameraActions.isEmpty() && rangeFinderActions.isEmpty() && displayActions.isEmpty()) { + robotMenu->setEnabled(false); + return; + } + + QMenu *cameraMenu = robotMenu->addMenu(tr("Camera Devices")); + if (cameraActions.isEmpty()) + cameraMenu->setEnabled(false); + else { + QListIterator actionIt(cameraActions); + while (actionIt.hasNext()) + cameraMenu->addAction(actionIt.next()); + } + QMenu *rangeFinderMenu = robotMenu->addMenu(tr("RangeFinder Devices")); + if (rangeFinderActions.isEmpty()) + rangeFinderMenu->setEnabled(false); + else { + QListIterator actionIt(rangeFinderActions); + while (actionIt.hasNext()) + rangeFinderMenu->addAction(actionIt.next()); + } + QMenu *displayMenu = robotMenu->addMenu(tr("Display Devices")); + if (displayActions.isEmpty()) + displayMenu->setEnabled(false); + else { + QListIterator actionIt(displayActions); + while (actionIt.hasNext()) + displayMenu->addAction(actionIt.next()); + } +} + +void WbMainWindow::updateOverlayMenu() { + clearOverlaysMenu(); + + if (!WbWorld::instance()) + return; - actions = mOverlayMenu->actions(); - for (int i = 0; i < actions.size() - 3; ++i) - actions[i]->setVisible(selectedRobot != NULL); + QListIterator robotIt(WbWorld::instance()->robots()); + while (robotIt.hasNext()) + addRobotInOverlaysMenu(robotIt.next()); } void WbMainWindow::updateProjectPath(const QString &oldPath, const QString &newPath) { diff --git a/src/webots/gui/WbMainWindow.hpp b/src/webots/gui/WbMainWindow.hpp index 88607038f7a..fe7f2a88660 100644 --- a/src/webots/gui/WbMainWindow.hpp +++ b/src/webots/gui/WbMainWindow.hpp @@ -135,7 +135,11 @@ private slots: void showStatusBarMessage(WbLog::Level level, const QString &message); void editRobotController(); void showRobotWindow(); + void clearOverlaysMenu(); void updateOverlayMenu(); + void updateRobotNameInOverlaysMenu(); + void addRobotInOverlaysMenu(WbRobot *robot); + void removeRobotInOverlaysMenu(const WbRobot *robot); void createWorldLoadingProgressDialog(); void deleteWorldLoadingProgressDialog(); void setWorldLoadingProgress(const int progress); @@ -198,7 +202,6 @@ private slots: QString findHtmlFileName(const char *title); void enableToolsWidgetItems(bool enabled); void updateWindowTitle(); - void updateGui(); void updateSimulationMenu(); void writePreferences() const; void showDocument(const QString &url); diff --git a/src/webots/gui/WbUpdatedDialog.cpp b/src/webots/gui/WbUpdatedDialog.cpp index 1bbba1fde6b..5a8aa02accb 100644 --- a/src/webots/gui/WbUpdatedDialog.cpp +++ b/src/webots/gui/WbUpdatedDialog.cpp @@ -21,7 +21,7 @@ #include "WbUpdatedDialog.hpp" WbUpdatedDialog::WbUpdatedDialog(QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Welcome to Webots R2023b")); + setWindowTitle(tr("Welcome to Webots R2023b revision 1")); QPixmap webotsLogo("images:webots.png"); QLabel *image = new QLabel(this); @@ -31,7 +31,7 @@ WbUpdatedDialog::WbUpdatedDialog(QWidget *parent) : QDialog(parent) { QLabel *label = new QLabel(this); label->setGeometry(QRect(75, 30, 330, 30)); - label->setText(tr("Thank you for using Webots R2023b.")); + label->setText(tr("Thank you for using Webots R2023b revision 1.")); label->setStyleSheet("font-size: 15px;"); label->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop); label->setWordWrap(true); @@ -56,8 +56,9 @@ WbUpdatedDialog::WbUpdatedDialog(QWidget *parent) : QDialog(parent) { label = new QLabel(this); label->setGeometry(QRect(35, 200, 283, 44)); - label->setText(tr("Find out the new features, enhancements and bug fixes of Webots R2023b in the changelog.")); + label->setText( + tr("Find out the new features, enhancements and bug fixes of Webots R2023b revision 1 in the changelog.")); label->setOpenExternalLinks(true); label->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop); label->setWordWrap(true); diff --git a/src/webots/launcher/webots-linux.sh b/src/webots/launcher/webots-linux.sh index f0e5fe71710..f489716ad3d 100644 --- a/src/webots/launcher/webots-linux.sh +++ b/src/webots/launcher/webots-linux.sh @@ -86,10 +86,6 @@ else export QT_ENABLE_HIGHDPI_SCALING=1 fi -# Fixes warning on Ubuntu 22.04 -unset XDG_SESSION_TYPE -unset WAYLAND_DISPLAY - # execute the real Webots binary in a child process if command -v primusrun >/dev/null 2>&1; then primusrun "$webots_home/bin/webots-bin" "$@" & diff --git a/src/webots/maths/WbAxisAngle.hpp b/src/webots/maths/WbAxisAngle.hpp deleted file mode 100644 index bfe65a63ed9..00000000000 --- a/src/webots/maths/WbAxisAngle.hpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 1996-2023 Cyberbotics Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef WB_AXIS_ANGLE_HPP -#define WB_AXIS_ANGLE_HPP - -#include "WbVector3.hpp" - -// -// Description: Axis-angle representation -// - -class WbAxisAngle { -public: - WbAxisAngle(double x, double y, double z, double angle) : mAxis(WbVector3(x, y, z)), mAngle(angle){}; - - WbVector3 &axis() { return mAxis; }; - - double angle() const { return mAngle; } - - // text conversion - QString toString(WbPrecision::Level level = WbPrecision::Level::DOUBLE_MAX) const { - return QString("%1 %2 %3 %4") - .arg(WbPrecision::doubleToString(mAxis.x(), level)) - .arg(WbPrecision::doubleToString(mAxis.y(), level)) - .arg(WbPrecision::doubleToString(mAxis.z(), level)) - .arg(WbPrecision::doubleToString(mAngle, level)); - } - -private: - WbVector3 mAxis; - double mAngle; -}; - -#endif diff --git a/src/webots/maths/WbMatrix3.cpp b/src/webots/maths/WbMatrix3.cpp index 89521ee27b2..b038b7e8a82 100644 --- a/src/webots/maths/WbMatrix3.cpp +++ b/src/webots/maths/WbMatrix3.cpp @@ -13,9 +13,9 @@ // limitations under the License. #include "WbMatrix3.hpp" + #include "WbMathsUtilities.hpp" #include "WbQuaternion.hpp" -#include "WbRotation.hpp" static const double IDENTITY[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; @@ -23,10 +23,6 @@ void WbMatrix3::setIdentity() { memcpy(mM, IDENTITY, sizeof(mM)); } -void WbMatrix3::fromAxisAngle(const WbRotation &r) { - fromAxisAngle(r.x(), r.y(), r.z(), r.angle()); -} - void WbMatrix3::fromQuaternion(const WbQuaternion &q) { double twoQx = 2.0 * q.x(), twoQy = 2.0 * q.y(), twoQz = 2.0 * q.z(), twoQw = 2.0 * q.w(); double sX = twoQx * q.x(), sY = twoQy * q.y(), sZ = twoQz * q.z(), sW = twoQw * q.w(); @@ -95,66 +91,3 @@ WbVector3 WbMatrix3::toEulerAnglesZYX() const { } return angles; } - -WbAxisAngle WbMatrix3::toAxisAngle() const { - // Reference: https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - - const double sqrt2Half = sqrt(2) / 2; - const double epsilon = 0.01; - const double epsilon2 = 0.1; - - double angle, x, y, z; - if ((abs(mM[1] - mM[3]) < epsilon) && (abs(mM[2] - mM[6]) < epsilon) && (abs(mM[5] - mM[7]) < epsilon)) { - if ((abs(mM[1] + mM[3]) < epsilon2) && (abs(mM[2] + mM[6]) < epsilon2) && (abs(mM[5] + mM[7]) < epsilon2) && - (abs(mM[0] + mM[4] + mM[8] - 3) < epsilon2)) { - return WbAxisAngle(1, 0, 0, 0); - } - angle = M_PI; - double xx = (mM[0] + 1) / 2; - double yy = (mM[4] + 1) / 2; - double zz = (mM[8] + 1) / 2; - double xy = (mM[1] + mM[3]) / 4; - double xz = (mM[2] + mM[6]) / 4; - double yz = (mM[5] + mM[7]) / 4; - if ((xx > yy) && (xx > zz)) { - if (xx < epsilon) { - x = 0; - y = sqrt2Half; - z = sqrt2Half; - } else { - x = sqrt(xx); - y = xy / x; - z = xz / x; - } - } else if (yy > zz) { - if (yy < epsilon) { - x = sqrt2Half; - y = 0; - z = sqrt2Half; - } else { - y = sqrt(yy); - x = xy / y; - z = yz / y; - } - } else { - if (zz < epsilon) { - x = sqrt2Half; - y = sqrt2Half; - z = 0; - } else { - z = sqrt(zz); - x = xz / z; - y = yz / z; - } - } - return WbAxisAngle(x, y, z, angle); - } - double s = sqrt((mM[7] - mM[5]) * (mM[7] - mM[5]) + (mM[2] - mM[6]) * (mM[2] - mM[6]) + (mM[3] - mM[1]) * (mM[3] - mM[1])); - if (abs(s) < 0.001) - s = 1; - angle = WbMathsUtilities::clampedAcos((mM[0] + mM[4] + mM[8] - 1) / 2); - x = (mM[7] - mM[5]) / s; - y = (mM[2] - mM[6]) / s; - z = (mM[3] - mM[1]) / s; - return WbAxisAngle(x, y, z, angle); -} diff --git a/src/webots/maths/WbMatrix3.hpp b/src/webots/maths/WbMatrix3.hpp index b7276ae9c21..42db55d7cd8 100644 --- a/src/webots/maths/WbMatrix3.hpp +++ b/src/webots/maths/WbMatrix3.hpp @@ -20,14 +20,12 @@ // WbVector3 can be multiplied (=rotated) by this kind of matrix // -#include "WbAxisAngle.hpp" #include "WbVector3.hpp" #include #include class WbQuaternion; -class WbRotation; class WbMatrix3 { public: @@ -54,7 +52,6 @@ class WbMatrix3 { WbMatrix3(double rx, double ry, double rz, double angle) { fromAxisAngle(rx, ry, rz, angle); } WbMatrix3(const WbVector3 &r, double angle) { fromAxisAngle(r.x(), r.y(), r.z(), angle); } WbMatrix3(double rx, double ry, double rz) { fromEulerAngles(rx, ry, rz); }; - explicit WbMatrix3(const WbRotation &r) { fromAxisAngle(r); } explicit WbMatrix3(const WbQuaternion &q) { fromQuaternion(q); } // construct from a set column vectors @@ -118,7 +115,6 @@ class WbMatrix3 { // to other rotation types WbQuaternion toQuaternion() const; WbVector3 toEulerAnglesZYX() const; - WbAxisAngle toAxisAngle() const; QString toString(WbPrecision::Level level) const; @@ -127,7 +123,6 @@ class WbMatrix3 { void fromAxisAngle(double rx, double ry, double rz, double angle); void fromEulerAngles(double rx, double ry, double rz); void fromAxisAngle(const WbVector3 &axis, double angle) { fromAxisAngle(axis.x(), axis.y(), axis.z(), angle); } - void fromAxisAngle(const WbRotation &r); void fromQuaternion(const WbQuaternion &q); private: @@ -158,13 +153,13 @@ inline WbVector3 operator*(const WbVector3 &v, const WbMatrix3 &m) { // rotate (assuming the rotation vector is normalized) inline void WbMatrix3::fromAxisAngle(double rx, double ry, double rz, double angle) { - double c = cos(angle); - double s = sin(angle); + const double c = cos(angle); + const double s = sin(angle); - double t1 = 1.0 - c; - double t2 = rx * rz * t1; - double t3 = rx * ry * t1; - double t4 = ry * rz * t1; + const double t1 = 1.0 - c; + const double t2 = rx * rz * t1; + const double t3 = rx * ry * t1; + const double t4 = ry * rz * t1; mM[0] = rx * rx * t1 + c; mM[1] = t3 - rz * s; diff --git a/src/webots/maths/WbRotation.cpp b/src/webots/maths/WbRotation.cpp index 52fea46e1ac..d0ee60ebcf5 100644 --- a/src/webots/maths/WbRotation.cpp +++ b/src/webots/maths/WbRotation.cpp @@ -15,8 +15,6 @@ #include "WbRotation.hpp" #include "WbMathsUtilities.hpp" -#include "WbMatrix3.hpp" -#include "WbQuaternion.hpp" #include @@ -95,25 +93,6 @@ void WbRotation::fromBasisVectors(const WbVector3 &vx, const WbVector3 &vy, cons } } -WbQuaternion WbRotation::toQuaternion() const { - const double halfAngle = 0.5 * mAngle; - const double sinusHalfAngle = sin(halfAngle), cosinusHalfAngle = cos(halfAngle); - return WbQuaternion(cosinusHalfAngle, mX * sinusHalfAngle, mY * sinusHalfAngle, mZ * sinusHalfAngle); -} - -WbMatrix3 WbRotation::toMatrix3() const { - // Assuming that (x, y, z) is normalized - // Apply Rodrigues' formula: rotation matrix = cos(Angle) * I_3 + (1 - cos(Angle)) * r * r^{transpose} + sin(Angle) r_{cross - // product 3*3 matix} - const double c = cos(mAngle), s = sin(mAngle), t = 1 - c; - const double tTimesX = t * mX, tTimesY = t * mY, tTimesZ = t * mZ; - const double sTimesX = s * mX, sTimesY = s * mY, sTimesZ = s * mZ; - const double squareX = tTimesX * mX + c, squareY = tTimesY * mY + c, squareZ = tTimesZ * mZ + c; - const double tXY = tTimesX * mY, tXZ = tTimesX * mZ, tYZ = tTimesY * mZ; - return WbMatrix3(squareX, tXY - sTimesZ, tXZ + sTimesY, tXY + sTimesZ, squareY, tYZ - sTimesX, tXZ - sTimesY, tYZ + sTimesX, - squareZ); -} - void WbRotation::toFloatArray(float *rotation) const { rotation[0] = static_cast(mAngle); rotation[1] = static_cast(mX); diff --git a/src/webots/maths/WbRotation.hpp b/src/webots/maths/WbRotation.hpp index 11c4df6b745..7e1ede35fef 100644 --- a/src/webots/maths/WbRotation.hpp +++ b/src/webots/maths/WbRotation.hpp @@ -19,20 +19,19 @@ // Description: 3D rotation (VRML-like) angle/axis representation // +#include "WbMatrix3.hpp" #include "WbPrecision.hpp" +#include "WbQuaternion.hpp" #include "WbVector3.hpp" #include #include -class WbQuaternion; -class WbMatrix3; - class WbRotation { public: // construct as identity rotation - WbRotation() : mX(0.0), mY(1.0), mZ(0.0), mAngle(0.0) {} + WbRotation() : mX(0.0), mY(0.0), mZ(1.0), mAngle(0.0) {} // construct from other types of rotations WbRotation(const WbRotation &r) : mX(r.x()), mY(r.y()), mZ(r.z()), mAngle(r.angle()) {} @@ -57,8 +56,9 @@ class WbRotation { void fromOpenGlMatrix(const double m[16]); // conversion into other types - WbQuaternion toQuaternion() const; - WbMatrix3 toMatrix3() const; + WbQuaternion toQuaternion() const { return WbQuaternion(axis(), mAngle); } + WbMatrix3 toMatrix3() const { return WbMatrix3(mX, mY, mZ, mAngle); } + void toFloatArray(float *rotation) const; // set |axis| = 1.0 diff --git a/src/webots/nodes/WbAbstractPose.cpp b/src/webots/nodes/WbAbstractPose.cpp index f7f6ce70854..669024e3638 100644 --- a/src/webots/nodes/WbAbstractPose.cpp +++ b/src/webots/nodes/WbAbstractPose.cpp @@ -74,6 +74,7 @@ void WbAbstractPose::updateTranslation() { void WbAbstractPose::updateRotation() { mBaseNode->setMatrixNeedUpdate(); + mRelativeQuaternion = mRotation->value().toQuaternion(); if (mBaseNode->areWrenObjectsInitialized()) applyRotationToWren(); @@ -87,6 +88,7 @@ void WbAbstractPose::updateRotation() { void WbAbstractPose::updateTranslationAndRotation() { mBaseNode->setMatrixNeedUpdate(); + mRelativeQuaternion = mRotation->value().toQuaternion(); if (mBaseNode->areWrenObjectsInitialized()) applyTranslationAndRotationToWren(); @@ -175,6 +177,19 @@ void WbAbstractPose::applyTranslationAndRotationToWren() { // for performance o wr_transform_set_position_and_orientation(mBaseNode->wrenNode(), newTranslation, newRotation); } +WbMatrix3 WbAbstractPose::rotationMatrix() const { + const WbMatrix4 &m = matrix(); + WbMatrix3 rm = m.extracted3x3Matrix(); + const WbTransform *t = dynamic_cast(mBaseNode); + if (!t) + t = mBaseNode->upperTransform(); + if (t) { + const WbVector3 s = t->absoluteScale(); + rm.scale(1.0 / s.x(), 1.0 / s.y(), 1.0 / s.z()); + } + return rm; +} + // Matrix 4-by-4 const WbMatrix4 &WbAbstractPose::matrix() const { @@ -195,15 +210,30 @@ const WbMatrix4 &WbAbstractPose::matrix() const { void WbAbstractPose::updateMatrix() const { assert(mMatrix); - mMatrix->fromVrml(mTranslation->x(), mTranslation->y(), mTranslation->z(), mRotation->x(), mRotation->y(), mRotation->z(), - mRotation->angle(), 1.0, 1.0, 1.0); - - // multiply with upper matrix if any - const WbPose *pose = mBaseNode->upperPose(); + // combine with upper matrix if any + const WbPose *const pose = mBaseNode->upperPose(); + WbVector3 t, s; + WbRotation r; if (pose) { - const WbTransform *const transform = dynamic_cast(pose); - *mMatrix = transform ? transform->matrix() * *mMatrix : pose->matrix() * *mMatrix; + // to prevent shear effect in case of non-uniform scaling, it is not possible to multiply the transform matrix directly + // note that this computation matches the one in WREN + const WbTransform *transform = dynamic_cast(pose); + if (!transform) + transform = pose->upperTransform(); + s = transform ? transform->absoluteScale() : WbVector3(1.0, 1.0, 1.0); + WbQuaternion q = pose->rotationMatrix().toQuaternion(); + t = pose->position() + q * (s * mTranslation->value()); + mRelativeQuaternion = mRotation->value().toQuaternion(); + q = q * mRelativeQuaternion; + q.normalize(); + r.fromQuaternion(q); + } else { + t = mTranslation->value(); + r = mRotation->value(); + s = WbVector3(1.0, 1.0, 1.0); } + + mMatrix->fromVrml(t.x(), t.y(), t.z(), r.x(), r.y(), r.z(), r.angle(), s.x(), s.y(), s.z()); mMatrixNeedUpdate = false; } @@ -239,7 +269,7 @@ void WbAbstractPose::setTranslation(double tx, double ty, double tz) { void WbAbstractPose::rotate(const WbVector3 &v) { WbMatrix3 rotation(v.normalized(), v.length()); - WbRotation newRotation = WbRotation(rotation * WbMatrix3(mRotation->value())); + WbRotation newRotation = WbRotation(rotation * mRotation->value().toMatrix3()); newRotation.normalize(); setRotation(newRotation); } diff --git a/src/webots/nodes/WbAbstractPose.hpp b/src/webots/nodes/WbAbstractPose.hpp index f25d28a5d9c..b94d730e460 100644 --- a/src/webots/nodes/WbAbstractPose.hpp +++ b/src/webots/nodes/WbAbstractPose.hpp @@ -73,13 +73,9 @@ class WbAbstractPose { WbVector3 zAxis() const { return matrix().zAxis(); } // 3x3 absolute rotation matrix - virtual WbMatrix3 rotationMatrix() const { - const WbMatrix4 &m = matrix(); - const WbVector3 &s = m.scale(); - WbMatrix3 rm = m.extracted3x3Matrix(); - rm.scale(1.0 / s.x(), 1.0 / s.y(), 1.0 / s.z()); - return rm; - } + WbMatrix3 rotationMatrix() const; + + const WbQuaternion &relativeQuaternion() const { return mRelativeQuaternion; } // position in 'world' coordinates WbVector3 position() const { return matrix().translation(); } @@ -131,6 +127,7 @@ class WbAbstractPose { mutable bool mMatrixNeedUpdate; mutable WbMatrix4 mVrmlMatrix; mutable bool mVrmlMatrixNeedUpdate; + mutable WbQuaternion mRelativeQuaternion; private: mutable bool mIsTranslationFieldVisible; diff --git a/src/webots/nodes/WbElevationGrid.cpp b/src/webots/nodes/WbElevationGrid.cpp index 2c6d5b1ceb1..1d2c5023b1f 100644 --- a/src/webots/nodes/WbElevationGrid.cpp +++ b/src/webots/nodes/WbElevationGrid.cpp @@ -429,11 +429,11 @@ bool WbElevationGrid::setOdeHeightfieldData() { mHeight->copyItemsTo(mData, xdyd); // Inverse mData lines for ODE - for (int i = 0; i < xd / 2; i++) { // integer division - for (int j = 0; j < yd; j++) { - double temp = mData[i * yd + j]; - mData[i * yd + j] = mData[(xd - 1 - i) * yd + j]; - mData[(xd - 1 - i) * yd + j] = temp; + for (int i = 0; i < yd / 2; i++) { // integer division + for (int j = 0; j < xd; j++) { + double temp = mData[i * xd + j]; + mData[i * xd + j] = mData[(yd - 1 - i) * xd + j]; + mData[(yd - 1 - i) * xd + j] = temp; } } diff --git a/src/webots/nodes/WbIndexedLineSet.cpp b/src/webots/nodes/WbIndexedLineSet.cpp index 2f0929d5317..9c02a0481c7 100644 --- a/src/webots/nodes/WbIndexedLineSet.cpp +++ b/src/webots/nodes/WbIndexedLineSet.cpp @@ -228,7 +228,7 @@ void WbIndexedLineSet::recomputeBoundingSphere() const { assert(mBoundingSphere); mBoundingSphere->empty(); - if (!coord()) + if (!coord() || mCoordIndex->isEmpty()) return; const WbMFVector3 &points = coord()->point(); diff --git a/src/webots/nodes/WbInertialUnit.cpp b/src/webots/nodes/WbInertialUnit.cpp index 595b2f35452..e1fc33b6807 100644 --- a/src/webots/nodes/WbInertialUnit.cpp +++ b/src/webots/nodes/WbInertialUnit.cpp @@ -137,15 +137,15 @@ void WbInertialUnit::computeValue() { } if (!mXAxis->isTrue() || !mYAxis->isTrue() || !mZAxis->isTrue()) { - WbAxisAngle aa = e.toAxisAngle(); + WbRotation rotation(e); if (!mXAxis->isTrue()) - aa.axis().setX(0); + rotation.setX(0); if (!mYAxis->isTrue()) - aa.axis().setZ(0); + rotation.setZ(0); if (!mZAxis->isTrue()) - aa.axis().setY(0); - aa.axis().normalize(); - e = WbMatrix3(aa.axis(), aa.angle()); + rotation.setY(0); + rotation.normalizeAxis(); + e = rotation.toMatrix3(); } mQuaternion = e.toQuaternion(); diff --git a/src/webots/nodes/WbLidar.cpp b/src/webots/nodes/WbLidar.cpp index c91b215abd9..17dc364c792 100644 --- a/src/webots/nodes/WbLidar.cpp +++ b/src/webots/nodes/WbLidar.cpp @@ -423,10 +423,10 @@ void WbLidar::updatePointCloud(int minWidth, int maxWidth) { const double cosPhi0 = cos(phi0); const double sinPhi0 = sin(phi0); - const double dtheta = mIsActuallyRotating ? (-2 * M_PI / (double)resolution) : (-actualFieldOfView() / (w - 1.0)); + const double dtheta = mIsActuallyRotating ? (-2 * M_PI / (double)resolution) : (-actualFieldOfView() / w); const double cosdTheta = cos(dtheta); const double sindTheta = sin(dtheta); - const double theta0 = mIsActuallyRotating ? (minWidth * dtheta) : (actualFieldOfView() / 2 + minWidth * dtheta); + const double theta0 = mIsActuallyRotating ? (minWidth * dtheta) : (actualFieldOfView() / 2 + minWidth * dtheta + dtheta / 2); const double cosTheta0 = cos(theta0); const double sinTheta0 = sin(theta0); diff --git a/src/webots/nodes/WbShape.cpp b/src/webots/nodes/WbShape.cpp index db6537fa505..eeafadcd4f9 100644 --- a/src/webots/nodes/WbShape.cpp +++ b/src/webots/nodes/WbShape.cpp @@ -523,3 +523,10 @@ void WbShape::exportBoundingObjectToX3D(WbWriter &writer) const { writer << ""; } } + +QStringList WbShape::fieldsToSynchronizeWithX3D() const { + QStringList fields; + fields << "isPickable" + << "castShadows"; + return fields; +} diff --git a/src/webots/nodes/WbShape.hpp b/src/webots/nodes/WbShape.hpp index 5c496bdb340..dcbb8981391 100644 --- a/src/webots/nodes/WbShape.hpp +++ b/src/webots/nodes/WbShape.hpp @@ -78,6 +78,7 @@ class WbShape : public WbBaseNode { // export bool exportNodeHeader(WbWriter &writer) const override; void exportBoundingObjectToX3D(WbWriter &writer) const override; + QStringList fieldsToSynchronizeWithX3D() const override; signals: void wrenMaterialChanged(); diff --git a/src/webots/nodes/WbTrackWheel.cpp b/src/webots/nodes/WbTrackWheel.cpp index 10717e72005..c6ac7a1a6e9 100644 --- a/src/webots/nodes/WbTrackWheel.cpp +++ b/src/webots/nodes/WbTrackWheel.cpp @@ -80,8 +80,7 @@ void WbTrackWheel::rotate(double traveledDistance) { if (mInner->value()) angle = -angle; - WbMatrix3 currentRotation(rotation()); - WbRotation newRotation(WbMatrix3(0, -1, 0, angle) * currentRotation); + WbRotation newRotation(WbMatrix3(0, -1, 0, angle) * rotation().toMatrix3()); newRotation.normalize(); setRotation(newRotation); updateRotation(); diff --git a/src/webots/nodes/WbTransform.cpp b/src/webots/nodes/WbTransform.cpp index 1c74250999a..6b1bad89c84 100644 --- a/src/webots/nodes/WbTransform.cpp +++ b/src/webots/nodes/WbTransform.cpp @@ -173,14 +173,30 @@ void WbTransform::setScaleNeedUpdateFlag() const { void WbTransform::updateMatrix() const { assert(mMatrix); - mMatrix->fromVrml(mTranslation->x(), mTranslation->y(), mTranslation->z(), mRotation->x(), mRotation->y(), mRotation->z(), - mRotation->angle(), mScale->x(), mScale->y(), mScale->z()); - - // multiply with upper matrix if any - const WbPose *pose = mBaseNode->upperPose(); + // combine with upper matrix if any + const WbPose *const pose = upperPose(); + WbVector3 t, s; + WbRotation r; if (pose) { + // to prevent shear effect in case of non-uniform scaling, it is not possible to multiply the transform matrix directly + // note that this computation matches the one in WREN const WbTransform *transform = dynamic_cast(pose); - *mMatrix = transform ? transform->matrix() * *mMatrix : pose->matrix() * *mMatrix; + if (!transform) + transform = pose->upperTransform(); + s = transform ? transform->absoluteScale() : WbVector3(1.0, 1.0, 1.0); + WbQuaternion q = pose->rotationMatrix().toQuaternion(); + t = pose->position() + q * (s * mTranslation->value()); + mRelativeQuaternion = mRotation->value().toQuaternion(); + q = q * mRelativeQuaternion; + q.normalize(); + r.fromQuaternion(q); + s = absoluteScale(); + } else { + t = mTranslation->value(); + r = mRotation->value(); + s = mScale->value(); } + + mMatrix->fromVrml(t.x(), t.y(), t.z(), r.x(), r.y(), r.z(), r.angle(), s.x(), s.y(), s.z()); mMatrixNeedUpdate = false; } diff --git a/src/webots/nodes/utils/WbBoundingSphere.cpp b/src/webots/nodes/utils/WbBoundingSphere.cpp index 86d9b37f32d..62c68626e1c 100644 --- a/src/webots/nodes/utils/WbBoundingSphere.cpp +++ b/src/webots/nodes/utils/WbBoundingSphere.cpp @@ -324,7 +324,6 @@ void WbBoundingSphere::parentUpdateNotification() const { } void WbBoundingSphere::setOwnerMoved() { - assert(mPoseOwner); if (mParentBoundingSphere && !mParentCoordinatesDirty) { mParentCoordinatesDirty = true; if (gUpdatesEnabled) diff --git a/src/webots/nodes/utils/WbObjectDetection.cpp b/src/webots/nodes/utils/WbObjectDetection.cpp index 04a4c8394d1..7e2b2ec4a81 100644 --- a/src/webots/nodes/utils/WbObjectDetection.cpp +++ b/src/webots/nodes/utils/WbObjectDetection.cpp @@ -322,11 +322,12 @@ bool WbObjectDetection::isWithinBounds(const WbAffinePlane *frustumPlanes, const return false; } } + // add points at the back of the device to ensure the whole object is detected + pointsInFrustum << pointsAtBack; // move the points in the device referential for (int i = 0; i < pointsInFrustum.size(); ++i) pointsInFrustum[i] = deviceInverseRotation * (pointsInFrustum[i] - devicePosition); - // add points at the back of the device to ensure the whole object is detected - pointsInFrustum << pointsAtBack; + double minX = pointsInFrustum[0].x(); double maxX = minX; double minY = pointsInFrustum[0].y(); @@ -420,7 +421,7 @@ bool WbObjectDetection::isWithinBounds(const WbAffinePlane *frustumPlanes, const } objectRelativePosition = deviceInverseRotation * (objectPosition - devicePosition); - if (!mIsOmniDirectional && mHorizontalFieldOfView <= M_PI_2) { + if (mHorizontalFieldOfView <= M_PI_2) { // do not recompute the object size and position if partly outside in case of fovX > PI // (a more complete computation will be needed and currently it seems to work quite well as-is) objectSize.setY(objectSize.y() - outsidePart[RIGHT] - outsidePart[LEFT]); diff --git a/src/webots/nodes/utils/WbObjectDetection.hpp b/src/webots/nodes/utils/WbObjectDetection.hpp index af54cfe2b1f..3a1e7033631 100644 --- a/src/webots/nodes/utils/WbObjectDetection.hpp +++ b/src/webots/nodes/utils/WbObjectDetection.hpp @@ -98,7 +98,7 @@ class WbObjectDetection { QList mRaysCollisionDepth; // rays collision depth QList mRayGeoms; // rays that checks collision of this packet double mHorizontalFieldOfView; - bool mIsOmniDirectional; // is sensor omnidirectional (horizontal FOV < PI/2) + bool mIsOmniDirectional; // is sensor omnidirectional (horizontal FOV > PI) int mOcclusion; }; diff --git a/src/webots/nodes/utils/WbWorld.cpp b/src/webots/nodes/utils/WbWorld.cpp index abf7adef930..1a28eb14815 100644 --- a/src/webots/nodes/utils/WbWorld.cpp +++ b/src/webots/nodes/utils/WbWorld.cpp @@ -591,6 +591,7 @@ void WbWorld::removeRobotIfPresent(WbRobot *robot) { return; mRobots.removeAll(robot); + emit robotRemoved(robot); } void WbWorld::addRobotIfNotAlreadyPresent(WbRobot *robot) { diff --git a/src/webots/nodes/utils/WbWorld.hpp b/src/webots/nodes/utils/WbWorld.hpp index 8020af2f0dd..55d9ef94f23 100644 --- a/src/webots/nodes/utils/WbWorld.hpp +++ b/src/webots/nodes/utils/WbWorld.hpp @@ -158,6 +158,7 @@ class WbWorld : public QObject { void worldLoadingHasProgressed(int percent); void viewpointChanged(); void robotAdded(WbRobot *robot); + void robotRemoved(WbRobot *robot); void resetRequested(bool restartControllers); public slots: diff --git a/src/webots/scene_tree/WbPositionViewer.cpp b/src/webots/scene_tree/WbPositionViewer.cpp index 0dcbe40f251..09d34af6b3a 100644 --- a/src/webots/scene_tree/WbPositionViewer.cpp +++ b/src/webots/scene_tree/WbPositionViewer.cpp @@ -17,6 +17,7 @@ #include "WbGuiRefreshOracle.hpp" #include "WbPose.hpp" #include "WbSolid.hpp" +#include "WbTransform.hpp" #include #include @@ -37,9 +38,11 @@ WbPositionViewer::WbPositionViewer(QWidget *parent) : &WbPositionViewer::updateRelativeTo); // Labels + mScaleTitleLabel = new QLabel(this); QGridLayout *labelLayout = new QGridLayout(); labelLayout->addWidget(new QLabel(tr("Position:")), 0, 0); labelLayout->addWidget(new QLabel(tr("Rotation:")), 1, 0); + labelLayout->addWidget(mScaleTitleLabel, 2, 0); mPositionLabels.resize(3); for (int i = 0; i < mPositionLabels.size(); ++i) { @@ -53,6 +56,12 @@ WbPositionViewer::WbPositionViewer(QWidget *parent) : mRotationLabels[i]->setTextInteractionFlags(Qt::TextSelectableByMouse); labelLayout->addWidget(mRotationLabels[i], 1, i + 1, Qt::AlignVCenter | Qt::AlignLeft); } + mScaleLabels.resize(3); + for (int i = 0; i < mScaleLabels.size(); ++i) { + mScaleLabels[i] = new QLabel(this); + mScaleLabels[i]->setTextInteractionFlags(Qt::TextSelectableByMouse); + labelLayout->addWidget(mScaleLabels[i], 2, i + 1, Qt::AlignVCenter | Qt::AlignLeft); + } vBoxLayout->addLayout(labelLayout); } @@ -101,31 +110,64 @@ void WbPositionViewer::requestUpdate() { void WbPositionViewer::update() { if (mIsSelected && mPose) { - WbVector3 position(0, 0, 0); - WbRotation rotation(0, 0, 0, 0); - if (mRelativeToComboBox->currentIndex() == 0) { - position = mPose->position(); - rotation = WbRotation(mPose->rotationMatrix()); - rotation.normalize(); - } else { + WbVector3 position(mPose->position()); + WbVector3 scale; + const WbTransform *transform = dynamic_cast(mPose); + if (transform) + scale = transform->absoluteScale(); + else { + transform = mPose->upperTransform(); + scale = transform ? transform->absoluteScale() : WbVector3(1.0, 1.0, 1.0); + } + + WbRotation rotation; + if (mRelativeToComboBox->currentIndex() == 0) + rotation.fromMatrix3(mPose->rotationMatrix()); + else { const WbPose *pose = mPose; - for (int i = 0; i < mRelativeToComboBox->currentIndex(); ++i) + WbQuaternion q; + for (int i = 0; i < mRelativeToComboBox->currentIndex(); ++i) { + assert(pose); + q = pose->relativeQuaternion() * q; pose = pose->upperPose(); - position = mPose->position() - pose->position(); - position = position * WbMatrix3(pose->rotation().toQuaternion()); - WbRotation currentRotation = WbRotation(mPose->rotationMatrix()); - WbRotation referenceRotation = WbRotation(mPose->rotationMatrix()); - currentRotation.normalize(); - referenceRotation.normalize(); - if (currentRotation == referenceRotation) // if there is no orientation difference, return 0 0 1 0 - rotation = WbRotation(0, 0, 1, 0); - else - rotation = WbRotation(currentRotation.toQuaternion() * referenceRotation.toQuaternion().conjugated()); + } + // compute relative rotation + q.normalize(); + rotation.fromQuaternion(q); + + // compute relative scale + WbVector3 otherAbsoluteScale; + transform = dynamic_cast(pose); + if (transform) + otherAbsoluteScale = transform->absoluteScale(); + else { + transform = pose->upperTransform(); + otherAbsoluteScale = transform ? transform->absoluteScale() : WbVector3(1.0, 1.0, 1.0); + } + scale /= otherAbsoluteScale; + + // compute relative translation + position = pose->rotationMatrix().transposed() * ((position - pose->position())); + position /= otherAbsoluteScale; } + + rotation.normalize(); + if (rotation.almostEquals(WbRotation(), 0.000001)) + rotation = WbRotation(); + for (int i = 0; i < mPositionLabels.size(); ++i) mPositionLabels[i]->setText(WbPrecision::doubleToString(position[i], WbPrecision::GUI_MEDIUM)); for (int i = 0; i < mRotationLabels.size(); ++i) mRotationLabels[i]->setText(WbPrecision::doubleToString(rotation[i], WbPrecision::GUI_MEDIUM)); + if (!scale.almostEquals(WbVector3(1, 1, 1))) { + mScaleTitleLabel->setText(tr("Scale:")); + for (int i = 0; i < mScaleLabels.size(); ++i) + mScaleLabels[i]->setText(WbPrecision::doubleToString(scale[i], WbPrecision::GUI_MEDIUM)); + } else { + mScaleTitleLabel->clear(); + for (int i = 0; i < mScaleLabels.size(); ++i) + mScaleLabels[i]->clear(); + } return; } @@ -133,6 +175,8 @@ void WbPositionViewer::update() { mPositionLabels[i]->clear(); for (int i = 0; i < mRotationLabels.size(); ++i) mRotationLabels[i]->clear(); + for (int i = 0; i < mScaleLabels.size(); ++i) + mScaleLabels[i]->clear(); } void WbPositionViewer::updateRelativeTo(int index) { diff --git a/src/webots/scene_tree/WbPositionViewer.hpp b/src/webots/scene_tree/WbPositionViewer.hpp index ed27e118a2c..611c3ba2cc8 100644 --- a/src/webots/scene_tree/WbPositionViewer.hpp +++ b/src/webots/scene_tree/WbPositionViewer.hpp @@ -59,6 +59,8 @@ public slots: // Labels QVector mPositionLabels; QVector mRotationLabels; + QVector mScaleLabels; + QLabel *mScaleTitleLabel; private slots: void updateRelativeTo(int index); diff --git a/src/webots/user_commands/WbContextMenuGenerator.cpp b/src/webots/user_commands/WbContextMenuGenerator.cpp index 13afb92bc22..1ef6a45aa7d 100644 --- a/src/webots/user_commands/WbContextMenuGenerator.cpp +++ b/src/webots/user_commands/WbContextMenuGenerator.cpp @@ -29,9 +29,7 @@ namespace WbContextMenuGenerator { static bool gAreRobotActionsEnabled = false; static bool gAreProtoActionsEnabled = false; static bool gAreExternProtoActionsEnabled = false; - static QMenu *gRobotCameraMenu = NULL; - static QMenu *gRobotRangeFinderMenu = NULL; - static QMenu *gRobotDisplayMenu = NULL; + static QMenu *gOverlaysMenu = NULL; void enableNodeActions(bool enabled) { gAreNodeActionsEnabled = enabled; @@ -45,14 +43,8 @@ namespace WbContextMenuGenerator { void enableExternProtoActions(bool enabled) { gAreExternProtoActionsEnabled = enabled; } - void setRobotCameraMenu(QMenu *menu) { - gRobotCameraMenu = menu; - } - void setRobotRangeFinderMenu(QMenu *menu) { - gRobotRangeFinderMenu = menu; - } - void setRobotDisplayMenu(QMenu *menu) { - gRobotDisplayMenu = menu; + void setOverlaysMenu(QMenu *menu) { + gOverlaysMenu = menu; } const QStringList fillTransformToItems(const WbNode *selectedNode) { @@ -105,10 +97,29 @@ namespace WbContextMenuGenerator { if (gAreRobotActionsEnabled) { contextMenu->addAction(WbActionManager::instance()->action(WbAction::EDIT_CONTROLLER)); contextMenu->addAction(WbActionManager::instance()->action(WbAction::SHOW_ROBOT_WINDOW)); + + assert(gOverlaysMenu); QMenu *subMenu = contextMenu->addMenu(QObject::tr("Overlays")); - subMenu->addMenu(gRobotCameraMenu); - subMenu->addMenu(gRobotRangeFinderMenu); - subMenu->addMenu(gRobotDisplayMenu); + subMenu->setEnabled(false); + QListIterator actionIt(gOverlaysMenu->actions()); + while (actionIt.hasNext()) { + const QAction *action = actionIt.next(); + const QMenu *robotMenu = action->menu(); + if (robotMenu && robotMenu->property("robot").value() == selectedNode) { + if (!robotMenu->isEnabled()) + break; + assert(!robotMenu->actions().isEmpty()); + QListIterator menuIt(robotMenu->actions()); + bool enabled = true; + while (menuIt.hasNext()) { + QMenu *deviceMenu = menuIt.next()->menu(); + enabled = enabled || deviceMenu->isEnabled(); + subMenu->addMenu(deviceMenu); + } + subMenu->setEnabled(enabled); + } + } + contextMenu->addSeparator(); } diff --git a/src/webots/user_commands/WbContextMenuGenerator.hpp b/src/webots/user_commands/WbContextMenuGenerator.hpp index c50aaeb7e88..feb7ea8bc38 100644 --- a/src/webots/user_commands/WbContextMenuGenerator.hpp +++ b/src/webots/user_commands/WbContextMenuGenerator.hpp @@ -31,9 +31,7 @@ namespace WbContextMenuGenerator { void enableProtoActions(bool enabled); void enableExternProtoActions(bool enabled); void enableRobotActions(bool enabled); - void setRobotCameraMenu(QMenu *menu); - void setRobotRangeFinderMenu(QMenu *menu); - void setRobotDisplayMenu(QMenu *menu); + void setOverlaysMenu(QMenu *menu); }; // namespace WbContextMenuGenerator #endif diff --git a/src/webots/vrml/WbProtoTemplateEngine.cpp b/src/webots/vrml/WbProtoTemplateEngine.cpp index 62c0a309a4d..6d870211f3f 100644 --- a/src/webots/vrml/WbProtoTemplateEngine.cpp +++ b/src/webots/vrml/WbProtoTemplateEngine.cpp @@ -38,11 +38,6 @@ static QString gCoordinateSystem; -static QString escapeString(const QString &string) { - QString escaped(string); - return escaped.replace("'", "\\'"); -} - WbProtoTemplateEngine::WbProtoTemplateEngine(const QString &templateContent) : WbTemplateEngine(templateContent) { } diff --git a/src/webots/wren/WbWrenCamera.cpp b/src/webots/wren/WbWrenCamera.cpp index 3b510984628..a692032c4ee 100644 --- a/src/webots/wren/WbWrenCamera.cpp +++ b/src/webots/wren/WbWrenCamera.cpp @@ -194,10 +194,9 @@ void WbWrenCamera::setFieldOfView(float fov) { init(); } - if (fov > M_PI_2) { // maximum X field of view of the sub-camera is pi / 2 - aspectRatio = aspectRatio * (M_PI_2 / fov); - fov = M_PI_2; - } + fov = fov > M_PI_2 ? M_PI_2 : fov; // maximum X field of view of the sub-camera is pi / 2 + aspectRatio = tan((mSphericalFieldOfViewX > M_PI_2 ? M_PI_2 : mSphericalFieldOfViewX) / 2) / + tan((mSphericalFieldOfViewY > M_PI_2 ? M_PI_2 : mSphericalFieldOfViewY) / 2); fieldOfViewY = computeFieldOfViewY(fov, aspectRatio); if (fieldOfViewY > M_PI_2) { // maximum Y field of view of the sub-camera is pi / 2 @@ -766,7 +765,7 @@ void WbWrenCamera::setupSphericalSubCameras() { if (verticalCameraNumber == 1) { // this coefficient is set to work even in the worse case (just before enabling top and bottom cameras) - mSphericalFovYCorrectionCoefficient = 1.27; + mSphericalFovYCorrectionCoefficient = 1.4; mSphericalFieldOfViewY *= mSphericalFovYCorrectionCoefficient; } else mSphericalFovYCorrectionCoefficient = 1.0; @@ -883,17 +882,25 @@ void WbWrenCamera::setCamerasOrientations() { } void WbWrenCamera::setFovy(float fov) { - for (int i = 0; i < CAMERA_ORIENTATION_COUNT; ++i) { + for (int i = 0; i < CAMERA_ORIENTATION_COUNT - 2; ++i) { // Skip the UP/DOWN camera if (mIsCameraActive[i]) wr_camera_set_fovy(mCamera[i], fov); } + if (mIsCameraActive[CAMERA_ORIENTATION_UP]) + wr_camera_set_fovy(mCamera[CAMERA_ORIENTATION_UP], M_PI - fov); + if (mIsCameraActive[CAMERA_ORIENTATION_DOWN]) + wr_camera_set_fovy(mCamera[CAMERA_ORIENTATION_DOWN], M_PI - fov); } void WbWrenCamera::setAspectRatio(float aspectRatio) { - for (int i = 0; i < CAMERA_ORIENTATION_COUNT; ++i) { + for (int i = 0; i < CAMERA_ORIENTATION_COUNT - 2; ++i) { // Skip the UP/DOWN camera if (mIsCameraActive[i]) wr_camera_set_aspect_ratio(mCamera[i], aspectRatio); } + if (mIsCameraActive[CAMERA_ORIENTATION_UP]) + wr_camera_set_aspect_ratio(mCamera[CAMERA_ORIENTATION_UP], 1.0); + if (mIsCameraActive[CAMERA_ORIENTATION_DOWN]) + wr_camera_set_aspect_ratio(mCamera[CAMERA_ORIENTATION_DOWN], 1.0); } void WbWrenCamera::updatePostProcessingParameters(int index) { diff --git a/tests/api/controllers/camera_recognition/camera_recognition.c b/tests/api/controllers/camera_recognition/camera_recognition.c index 0dba96c8e4b..ac871171b59 100644 --- a/tests/api/controllers/camera_recognition/camera_recognition.c +++ b/tests/api/controllers/camera_recognition/camera_recognition.c @@ -9,7 +9,7 @@ #include "../../../lib/ts_utils.h" #define TIME_STEP 32 -#define VISIBLE_SOLID_NUMBER 6 +#define VISIBLE_SOLID_NUMBER 7 // This test is mainly testing the functionalities for a planar camera. // Some basic tests for spherical and cylindrical cameras are also performed checking mainly @@ -17,7 +17,8 @@ // objects visible in planar camera static const char *visible_solid_models[VISIBLE_SOLID_NUMBER] = { - "visible sphere", "visible box", "sub solid", "visible capsule", "composed solid", "visible sphere without BO"}; + "visible sphere", "visible box", "sub solid", "visible capsule", "composed solid", "visible sphere without BO", + "perpendicular box"}; static const char *occcluded_solid_model = "occluded box"; @@ -45,13 +46,16 @@ int main(int argc, char **argv) { VISIBLE_SOLID_NUMBER + 1, object_number); object_number = wb_camera_recognition_get_number_of_objects(camera_spherical); - ts_assert_int_equal(object_number, 10, "The spherical camera should initially see %d objects and not %d (with occlusion).", 9, - object_number); + ts_assert_int_equal(object_number, VISIBLE_SOLID_NUMBER + 4, + "The spherical camera should initially see %d objects" + " and not %d (with occlusion).", + VISIBLE_SOLID_NUMBER + 4, object_number); object_number = wb_camera_recognition_get_number_of_objects(camera_cylindrical); - ts_assert_int_equal(object_number, 8, "The cylindrical camera should initially see %d objects and not %d (with occlusion).", - 7, object_number); - + ts_assert_int_equal(object_number, VISIBLE_SOLID_NUMBER + 2, + "The cylindrical camera should initially see %d objects" + " and not %d (with occlusion).", + VISIBLE_SOLID_NUMBER + 2, object_number); // enable occlusion WbNodeRef recognition_node = wb_supervisor_node_get_from_def("RECOGNITION"); WbFieldRef occlusion_field = wb_supervisor_node_get_field(recognition_node, "occlusion"); @@ -60,7 +64,7 @@ int main(int argc, char **argv) { ts_assert_boolean_equal(wb_camera_recognition_has_segmentation(camera), "The Recognition.segmentation field should be set to TRUE."); const unsigned char *image = wb_camera_recognition_get_segmentation_image(camera); - ts_assert_boolean_equal(image == NULL, "No segmentation image should be returned if segmentaton is disabled."); + ts_assert_boolean_equal(image == NULL, "No segmentation image should be returned if segmentation is disabled."); wb_robot_step(TIME_STEP); @@ -137,7 +141,7 @@ int main(int argc, char **argv) { objects[i].position_on_image[0], objects[i].position_on_image[1], expected_position_on_image[0], expected_position_on_image[1]); } - // check objct is one of the visible solid + // check object is one of the visible solid bool found = false; for (j = 0; j < VISIBLE_SOLID_NUMBER; ++j) { if (strcmp(objects[i].model, visible_solid_models[j]) == 0) @@ -158,6 +162,50 @@ int main(int argc, char **argv) { ts_assert_double_is_bigger(composed_solid_size, sub_solid_size, "Object: '%s' should have a bigger pixel size than '%s'.", objects[composed_solid_index].model, objects[sub_solid_index].model); + // check if perpendicular object is recognized correctly + const double perpendicular_box_position[3] = {-0.528470, -1.191827, -0.105723}; + const double perpendicular_box_orientation[4] = {0.577352, -0.577349, 0.577350, 2.094393}; + // case spherical + object_number = wb_camera_recognition_get_number_of_objects(camera_spherical); + objects = wb_camera_recognition_get_objects(camera_spherical); + + for (i = 0; i < object_number; ++i) { + if (strcmp(objects[i].model, "perpendicular box") == 0) { + ts_assert_doubles_in_delta(3, objects[i].position, perpendicular_box_position, 0.001, + "Position of 'perpendicular box' is not correct for spherical camera: found=(" + "%f, %f, %f), expected=(%f, %f, %f).", + objects[i].position[0], objects[i].position[1], objects[i].position[2], + perpendicular_box_position[0], perpendicular_box_position[1], perpendicular_box_position[2]); + // orientation + ts_assert_doubles_in_delta(4, objects[i].orientation, perpendicular_box_orientation, 0.001, + "Orientation of 'perpendicular box' is not correct for spherical camera: found=(" + "%f, %f, %f, %f), expected=(%f, %f, %f, %f).", + objects[i].orientation[0], objects[i].orientation[1], objects[i].orientation[2], + objects[i].orientation[3], perpendicular_box_orientation[0], perpendicular_box_orientation[1], + perpendicular_box_orientation[2], perpendicular_box_orientation[3]); + } + } + // case cylindrical + object_number = wb_camera_recognition_get_number_of_objects(camera_cylindrical); + objects = wb_camera_recognition_get_objects(camera_cylindrical); + for (i = 0; i < object_number; ++i) { + if (strcmp(objects[i].model, "perpendicular box") == 0) { + // position + ts_assert_doubles_in_delta(3, objects[i].position, perpendicular_box_position, 0.001, + "Position of 'perpendicular box' is not correct for cylindrical camera: found=(" + "%f, %f, %f), expected=(%f, %f, %f).", + objects[i].position[0], objects[i].position[1], objects[i].position[2], + perpendicular_box_position[0], perpendicular_box_position[1], perpendicular_box_position[2]); + // orientation + ts_assert_doubles_in_delta(4, objects[i].orientation, perpendicular_box_orientation, 0.001, + "Orientation of 'perpendicular box' is not correct for cylindrical camera: found=(" + "%f, %f, %f, %f), expected=(%f, %f, %f, %f).", + objects[i].orientation[0], objects[i].orientation[1], objects[i].orientation[2], + objects[i].orientation[3], perpendicular_box_orientation[0], perpendicular_box_orientation[1], + perpendicular_box_orientation[2], perpendicular_box_orientation[3]); + } + } + wb_robot_step(TIME_STEP); object_number = wb_camera_recognition_get_number_of_objects(camera); @@ -212,6 +260,7 @@ int main(int argc, char **argv) { const double invisible_capsule_position[3] = {0.369, 1.650, 0.899}; const double invisible_capsule_orientation[4] = {0.577350, -0.577350, -0.577350, 2.094390}; const double invisible_capsule_size[2] = {0.1, 0.1}; + object_number = wb_camera_recognition_get_number_of_objects(camera_spherical); ts_assert_int_equal(object_number, 4, "The spherical camera should see only 4 objects after removal of the initial objects and not %d.", @@ -266,12 +315,12 @@ int main(int argc, char **argv) { // position ts_assert_doubles_in_delta( 3, objects[i].position, invisible_capsule_position, 0.001, - "Position of 'invisble capsule' is not correct for spherical camera: found=(%f, %f, %f), expected=(%f, %f, %f).", + "Position of 'invisible capsule' is not correct for spherical camera: found=(%f, %f, %f), expected=(%f, %f, %f).", objects[i].position[0], objects[i].position[1], objects[i].position[2], invisible_capsule_position[0], invisible_capsule_position[1], invisible_capsule_position[2]); // orientation ts_assert_doubles_in_delta(4, objects[i].orientation, invisible_capsule_orientation, 0.001, - "Orientation of 'invisble capsule' is not correct for spherical camera: found=(%f, %f, %f, " + "Orientation of 'invisible capsule' is not correct for spherical camera: found=(%f, %f, %f, " "%f), expected=(%f, %f, %f, %f).", objects[i].orientation[0], objects[i].orientation[1], objects[i].orientation[2], objects[i].orientation[3], invisible_capsule_orientation[0], invisible_capsule_orientation[1], @@ -279,19 +328,19 @@ int main(int argc, char **argv) { // size ts_assert_doubles_in_delta( 2, objects[i].size, invisible_capsule_size, 0.001, - "Size of 'invisble capsule' is not correct for spherical camera: found=(%f, %f), expected=(%f, %f).", + "Size of 'invisible capsule' is not correct for spherical camera: found=(%f, %f), expected=(%f, %f).", objects[i].size[0], objects[i].size[1], invisible_capsule_size[0], invisible_capsule_size[1]); // size on image int expected_size_on_image[2] = {14, 28}; ts_assert_integers_in_delta( 2, objects[i].size_on_image, expected_size_on_image, 1, - "Size on image of 'invisble capsule' is not correct for spherical camera: found=(%d, %d), expected=(%d, %d).", + "Size on image of 'invisible capsule' is not correct for spherical camera: found=(%d, %d), expected=(%d, %d).", objects[i].size_on_image[0], objects[i].size_on_image[1], expected_size_on_image[0], expected_size_on_image[1]); // position on image int expected_position_on_image[2] = {79, 102}; ts_assert_integers_in_delta( 2, objects[i].position_on_image, expected_position_on_image, 1, - "Position on image of 'invisble capsule' is not correct for spherical camera: found=(%d, %d), expected=(%d, %d).", + "Position on image of 'invisible capsule' is not correct for spherical camera: found=(%d, %d), expected=(%d, %d).", objects[i].position_on_image[0], objects[i].position_on_image[1], expected_position_on_image[0], expected_position_on_image[1]); } @@ -315,7 +364,7 @@ int main(int argc, char **argv) { invisible_capsule_position[1], invisible_capsule_position[2]); // orientation ts_assert_doubles_in_delta(4, objects[i].orientation, invisible_capsule_orientation, 0.001, - "Orientation of 'invisble capsule' is not correct for cylindrical camera: found=(%f, %f, %f, " + "Orientation of 'invisible capsule' is not correct for cylindrical camera: found=(%f, %f, %f, " "%f), expected=(%f, %f, %f, %f).", objects[i].orientation[0], objects[i].orientation[1], objects[i].orientation[2], objects[i].orientation[3], invisible_capsule_orientation[0], invisible_capsule_orientation[1], @@ -323,19 +372,19 @@ int main(int argc, char **argv) { // size ts_assert_doubles_in_delta( 2, objects[i].size, invisible_capsule_size, 0.001, - "Size of 'invisble capsule' is not correct for cylindrical camera: found=(%f, %f), expected=(%f, %f).", + "Size of 'invisible capsule' is not correct for cylindrical camera: found=(%f, %f), expected=(%f, %f).", objects[i].size[0], objects[i].size[1], invisible_capsule_size[0], invisible_capsule_size[1]); // size on image int expected_size_on_image[2] = {6, 45}; ts_assert_integers_in_delta( 2, objects[i].size_on_image, expected_size_on_image, 1, - "Size on image of 'invisble capsule' is not correct for cylindrical camera: found=(%d, %d), expected=(%d, %d).", + "Size on image of 'invisible capsule' is not correct for cylindrical camera: found=(%d, %d), expected=(%d, %d).", objects[i].size_on_image[0], objects[i].size_on_image[1], expected_size_on_image[0], expected_size_on_image[1]); // position on image int expected_position_on_image[2] = {12, 88}; ts_assert_integers_in_delta( 2, objects[i].position_on_image, expected_position_on_image, 1, - "Position on image of 'invisble capsule' is not correct for cylindrical camera: found=(%d, %d), expected=(%d, %d).", + "Position on image of 'invisible capsule' is not correct for cylindrical camera: found=(%d, %d), expected=(%d, %d).", objects[i].position_on_image[0], objects[i].position_on_image[1], expected_position_on_image[0], expected_position_on_image[1]); diff --git a/tests/api/worlds/camera_color.wbt b/tests/api/worlds/camera_color.wbt index e1d533e815b..a9a19bbd2e6 100644 --- a/tests/api/worlds/camera_color.wbt +++ b/tests/api/worlds/camera_color.wbt @@ -26,7 +26,7 @@ Transform { appearance PBRAppearance { baseColorMap ImageTexture { url [ - "https://raw.githubusercontent.com/cyberbotics/webots/R2021a/tests/api/worlds/textures/color_checker_chart.png" + "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/tests/api/worlds/textures/color_checker_chart.png" ] } roughness 1 diff --git a/tests/api/worlds/camera_recognition.wbt b/tests/api/worlds/camera_recognition.wbt index 6688ef97eff..d43bdbfb4d7 100644 --- a/tests/api/worlds/camera_recognition.wbt +++ b/tests/api/worlds/camera_recognition.wbt @@ -288,6 +288,28 @@ Solid { 0 0 1 ] } +Solid { + translation -1.0 1.6 -2.25 + rotation 0.599079 0.531234 0.599078 -2.82327 + children [ + Shape { + appearance Appearance { + material Material { + diffuseColor 0.756863 0.207843 0.207843 + } + } + geometry DEF BOX Box { + size 0.9 1.9 0.1 + } + } + ] + name "solid(10)" + model "perpendicular box" + boundingObject USE BOX + recognitionColors [ + 0.756863 0.207843 0.207843 + ] +} Robot { translation -0.116489 0.196931 -0.132093 rotation 0.6457879227325559 0.5398879322844559 0.5398879322844559 1.36968 diff --git a/tests/protos/controllers/hinge_2_joint_with_backlash_supervisor/hinge_2_joint_with_backlash_supervisor.c b/tests/protos/controllers/hinge_2_joint_with_backlash_supervisor/hinge_2_joint_with_backlash_supervisor.c index 7481232517a..55a19a16a26 100644 --- a/tests/protos/controllers/hinge_2_joint_with_backlash_supervisor/hinge_2_joint_with_backlash_supervisor.c +++ b/tests/protos/controllers/hinge_2_joint_with_backlash_supervisor/hinge_2_joint_with_backlash_supervisor.c @@ -168,7 +168,6 @@ int main(int argc, char **argv) { "In test case 'BACKLASH_ON_NEITHER' the endpoint breached reference's maximal X position."); // check against the minima - tolerance = 1e-10; ts_assert_double_is_bigger(end_point_position[BACKLASH_ON_BOTH][X], reference_min_x - tolerance, "In test case 'BACKLASH_ON_BOTH' the endpoint breached reference's minimal X position."); ts_assert_double_is_bigger(end_point_position[BACKLASH_ON_AXIS][X], reference_min_x - tolerance, @@ -180,7 +179,6 @@ int main(int argc, char **argv) { // TEST: assert if behavior is consistent (those with backlash behave all the same, those without as well) // (BACKLASH_ON_BOTH, BACKLASH_ON_AXIS) should behave in the same manner - tolerance = 1e-8; ts_assert_vec3_in_delta(end_point_position[BACKLASH_ON_BOTH][X], end_point_position[BACKLASH_ON_BOTH][Y], end_point_position[BACKLASH_ON_BOTH][Z], end_point_position[BACKLASH_ON_AXIS][X], end_point_position[BACKLASH_ON_AXIS][Y], end_point_position[BACKLASH_ON_AXIS][Z] + 0.25, tolerance,