├── .eslintrc ├── .github ├── dependabot.yml └── workflows │ ├── docs.yml │ └── main.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── build ├── ros3d.cjs.js ├── ros3d.esm.js ├── ros3d.js └── ros3d.min.js ├── es6-support └── index.js ├── es6-transpiler.js ├── examples ├── continuousmap.html ├── depthcloud.html ├── html-import │ ├── README.md │ ├── markers.html │ ├── package.json │ ├── server.js │ └── yarn.lock ├── interactivemarkers.html ├── js │ └── ColladaLoader.js ├── kitti.html ├── map.html ├── markers.html ├── navsatfix.html ├── octree.html ├── pointcloud2.html ├── ros3djs.launch └── urdf.html ├── jsdoc_conf.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── shims └── three │ ├── ColladaLoader.js │ ├── MTLLoader.js │ ├── OBJLoader.js │ ├── STLLoader.js │ └── core.js ├── src ├── Ros3D.js ├── depthcloud │ └── DepthCloud.js ├── interactivemarkers │ ├── InteractiveMarker.js │ ├── InteractiveMarkerClient.js │ ├── InteractiveMarkerControl.js │ ├── InteractiveMarkerHandle.js │ └── InteractiveMarkerMenu.js ├── markers │ ├── Marker.js │ ├── MarkerArrayClient.js │ └── MarkerClient.js ├── models │ ├── Arrow.js │ ├── Arrow2.js │ ├── Axes.js │ ├── Grid.js │ ├── MeshLoader.js │ ├── MeshResource.js │ └── TriangleList.js ├── navigation │ ├── ColorOcTree.js │ ├── OcTree.js │ ├── OcTreeBase.js │ ├── OcTreeBaseNode.js │ ├── OcTreeClient.js │ ├── OccupancyGrid.js │ ├── OccupancyGridClient.js │ ├── Odometry.js │ ├── Path.js │ ├── Point.js │ ├── Polygon.js │ ├── Pose.js │ ├── PoseArray.js │ └── PoseWithCovariance.js ├── sensors │ ├── LaserScan.js │ ├── NavSatFix.js │ ├── PointCloud2.js │ ├── Points.js │ └── TFAxes.js ├── urdf │ ├── Urdf.js │ └── UrdfClient.js └── visualization │ ├── SceneNode.js │ ├── Viewer.js │ └── interaction │ ├── Highlighter.js │ ├── MouseHandler.js │ └── OrbitControls.js ├── test ├── depthCloud │ └── DepthCloud.test.js ├── karma.conf.js ├── markers │ └── Marker.test.js └── models │ ├── Arrow.test.js │ └── Grid.test.js └── tests ├── index.html └── tests.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "mocha": true 7 | }, 8 | "globals": { 9 | "EventEmitter3": true, 10 | "THREE": true, 11 | "ROS3D": true, 12 | "ROSLIB": true, 13 | "requestAnimationFrame": true, 14 | "cancelAnimationFrame": true 15 | }, 16 | "parserOptions": { 17 | "ecmaVersion": 5, 18 | "sourceType": "module" 19 | }, 20 | "rules": { 21 | "semi": ["warn", "always"], 22 | "curly": "error", 23 | "eqeqeq": "error", 24 | "wrap-iife": ["error", "any"], 25 | "no-use-before-define": "off", 26 | "new-cap": "error", 27 | "no-caller": "error", 28 | "dot-notation": "off", 29 | "no-undef": "error", 30 | "no-cond-assign": "off", 31 | "no-eq-null": "off", 32 | "no-proto": "off", 33 | "no-console": "off", 34 | "no-unused-vars": "off", 35 | "strict": "off", 36 | "quotes": ["error", "single"], 37 | "linebreak-style": "error" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | ignore: 8 | - dependency-name: "*" 9 | update-types: ["version-update:semver-patch"] 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs Deployment 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | docs: 9 | name: Docs Deployment 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | cache: npm 16 | node-version: 20 17 | - name: Install 18 | run: npm ci 19 | - name: Generate docs 20 | run: npm run doc 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v4 23 | with: 24 | github_token: ${{ secrets.RWT_BOT_PAT }} 25 | publish_dir: doc 26 | destination_dir: . 27 | enable_jekyll: false 28 | force_orphan: false # So we keep the doc history 29 | commit_message: JSDoc ${{ github.event.release.name }} 30 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: ${{ matrix.node_version }} 8 | if: ${{ github.actor != 'RWT-bot' }} 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | node_version: [18, 20] 14 | steps: 15 | - uses: actions/checkout@v4 16 | env: 17 | TOKEN: "${{ github.event_name == 'push' && endsWith(github.ref, 'develop') && matrix.node_version == 20 && secrets.RWT_BOT_PAT || github.token }}" 18 | with: 19 | token: ${{ env.TOKEN }} 20 | - uses: actions/setup-node@v4 21 | with: 22 | cache: npm 23 | node-version: ${{ matrix.node_version }} 24 | - name: Install grunt-cli 25 | run: npm install -g grunt-cli 26 | - name: Install 27 | run: npm install 28 | - name: Build 29 | run: npm run build 30 | - name: Test 31 | run: npm test 32 | - uses: stefanzweifel/git-auto-commit-action@v5 33 | if: ${{ github.event_name == 'push' && endsWith(github.ref, 'develop') && matrix.node_version == 20 }} 34 | with: 35 | commit_message: Update Build 36 | file_pattern: 'build/*.js' 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vagrant 3 | node_modules 4 | doc 5 | src-esm 6 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Original Authors 2 | ---------------- 3 | 4 | * [Russell Toris](http://users.wpi.edu/~rctoris/) (rctoris@wpi.edu) 5 | * Jihoon Lee (jihoonlee.in@gmail.com) 6 | * David Gossow (dgossow@willowgarage.com) 7 | 8 | Contributors 9 | ------------ 10 | 11 | * Brandon Alexander (balexander@willowgarage.com) 12 | * David V. Lu!! (davidvlu@gmail.com) 13 | * Sarah Osentoski (sarah.osentoski@us.bosch.com) 14 | * Benjamin Pitzer (ben.pitzer@gmail.com) 15 | * Xueqiao Xu (xueqiaoxu@gmail.com) 16 | * Mr.doob - (http://mrdoob.com) 17 | * AlteredQualia - (http://alteredqualia.com) 18 | 19 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | const { 2 | debugRules, 3 | dependencies, 4 | inheritance, 5 | injectImports, 6 | transpileToEs6 7 | } = require('./es6-transpiler'); 8 | 9 | // Export Grunt config 10 | module.exports = function(grunt) { 11 | 12 | grunt.initConfig({ 13 | pkg: grunt.file.readJSON('package.json'), 14 | eslint: { 15 | lint: { 16 | options: { 17 | configFile: '.eslintrc', 18 | }, 19 | src: [ 20 | 'Gruntfile.js', 21 | './src/*.js', 22 | './src/**/*.js', 23 | './tests/*.js' 24 | ], 25 | }, 26 | fix: { 27 | options: { 28 | configFile: '<%= eslint.lint.options.configFile %>', 29 | fix: true 30 | }, 31 | src: '<%= eslint.lint.src %>', 32 | } 33 | }, 34 | shell: { 35 | build: { 36 | command: 'rollup -c' 37 | } 38 | }, 39 | karma: { 40 | build: { 41 | configFile: './test/karma.conf.js', 42 | singleRun: true, 43 | browsers: process.env.CI ? ['FirefoxHeadless'] : ['Firefox'] // eslint-disable-line 44 | } 45 | }, 46 | watch: { 47 | build_and_watch: { 48 | options: { 49 | interrupt: true 50 | }, 51 | files: [ 52 | 'Gruntfile.js', 53 | '.eslintrc', 54 | './src/*.js', 55 | './src/**/*.js' 56 | ], 57 | tasks: ['build'] 58 | } 59 | }, 60 | clean: { 61 | options: { 62 | force: true 63 | }, 64 | doc: ['./doc'] 65 | }, 66 | jsdoc: { 67 | doc: { 68 | src: [ 69 | './src/*.js', 70 | './src/**/*.js' 71 | ], 72 | options: { 73 | destination: './doc', 74 | configure: 'jsdoc_conf.json' 75 | } 76 | } 77 | }, 78 | pipe: { 79 | transpile: { 80 | options: { 81 | process: transpileToEs6, 82 | }, 83 | files: [{ 84 | expand: true, 85 | cwd: 'src', 86 | src: [ 87 | '*.js', 88 | '**/*.js', 89 | ], 90 | dest: 'src-esm/', 91 | }] 92 | }, 93 | transpile_imports: { 94 | options: { 95 | process: injectImports, 96 | }, 97 | files: [{ 98 | expand: true, 99 | cwd: 'src-esm', 100 | src: [ 101 | '*.js', 102 | '**/*.js', 103 | ], 104 | dest: 'src-esm/', 105 | }] 106 | }, 107 | transpile_index: { 108 | files: [{ 109 | expand: true, 110 | cwd: 'es6-support', 111 | src: [ 112 | 'index.js' 113 | ], 114 | dest: 'src-esm/' 115 | }] 116 | } 117 | }, 118 | execute: { 119 | transpile: { 120 | call: (grunt, options) => { 121 | console.log(); 122 | if (debugRules.logInternalDepsAtEnd) { 123 | console.log('Internal dependencies'); 124 | console.log(dependencies.internalToString()); 125 | } 126 | if (debugRules.logExternalDepsAtEnd) { 127 | console.log('External dependencies'); 128 | console.log(dependencies.externalToString()); 129 | } 130 | if (debugRules.logInheritanceAtEnd) { 131 | console.log('Inheritance hierarchy'); 132 | console.log(inheritance.toString()); 133 | } 134 | 135 | console.log(); 136 | }, 137 | } 138 | } 139 | }); 140 | 141 | grunt.loadNpmTasks('grunt-contrib-watch'); 142 | grunt.loadNpmTasks('grunt-contrib-clean'); 143 | grunt.loadNpmTasks('grunt-jsdoc'); 144 | grunt.loadNpmTasks('grunt-karma'); 145 | grunt.loadNpmTasks('grunt-pipe'); 146 | grunt.loadNpmTasks('grunt-execute'); 147 | grunt.loadNpmTasks('grunt-shell'); 148 | grunt.loadNpmTasks('gruntify-eslint'); 149 | 150 | grunt.registerTask('transpile', ['pipe', 'execute']); 151 | grunt.registerTask('build', ['eslint:lint', 'pipe', 'shell']); 152 | grunt.registerTask('build_and_watch', ['build', 'watch']); 153 | grunt.registerTask('doc', ['clean', 'jsdoc']); 154 | grunt.registerTask('lint', ['eslint:lint',]); 155 | grunt.registerTask('lint-fix', ['eslint:fix',]); 156 | grunt.registerTask('test', ['karma',]); 157 | }; 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (c) 2014, Worcester Polytechnic Institute, Robert Bosch 4 | LLC, Yujin Robot. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | * Neither the name of Worcester Polytechnic Institute, Robert 17 | Bosch LLC, Yujin Robot nor the names of its contributors may be 18 | used to endorse or promote products derived from this software 19 | without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ros3djs 2 | 3 | [![CI](https://github.com/RobotWebTools/ros3djs/actions/workflows/main.yml/badge.svg)](https://github.com/RobotWebTools/ros3djs/actions/workflows/main.yml) 4 | 5 | 6 | #### 3D Visualization Library for use with the ROS JavaScript Libraries 7 | 8 | For full documentation, see [the ROS wiki](http://ros.org/wiki/ros3djs) or check out some [working demos](https://robotwebtools.github.io/demos.html). 9 | 10 | [JSDoc](https://robotwebtools.github.io/ros3djs) can be found on the Robot Web Tools website. 11 | 12 | This project is released as part of the [Robot Web Tools](https://robotwebtools.github.io/) effort. 13 | 14 | ### Usage 15 | 16 | Pre-built files can be found in either [ros3d.js](build/ros3d.js) or [ros3d.min.js](build/ros3d.min.js). 17 | 18 | 19 | Alternatively, you can use the current release via the [JsDelivr](https://www.jsdelivr.com/) CDN: ([full](https://cdn.jsdelivr.net/npm/ros3d@1/build/ros3d.js)) | ([min](https://cdn.jsdelivr.net/npm/ros3d@1/build/ros3d.min.js)) 20 | 21 | ### Dependencies 22 | 23 | ros3djs depends on: 24 | 25 | [EventEmitter3](https://github.com/primus/eventemitter3). The current supported version is 5.0. The current supported version can be found on the JsDeliver CDN: ([full](https://cdn.jsdelivr.net/npm/eventemitter3@5.0/dist/eventemitter3.umd.js)) | ([min](https://cdn.jsdelivr.net/npm/eventemitter3@5.0/dist/eventemitter3.umd.js)) 26 | 27 | [three.js](https://github.com/mrdoob/three.js/). The current supported version is r89. The current supported version can be found on the Robot Web Tools CDN: ([full](https://cdn.jsdelivr.net/npm/three@0.89.0/build/three.js)) | ([min](https://cdn.jsdelivr.net/npm/three@0.89.0/build/three.min.js)) 28 | 29 | [THREE.ColladaLoader](https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/ColladaLoader.js). The current supported version is r89. The current supported version can be found on the Robot Web Tools CDN: ~([full](https://static.robotwebtools.org/threejs/r89/ColladaLoader.js))~ This CDN is gone. 30 | 31 | [THREE.STLLoader](https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/STLLoader.js). The current supported version is r89. The current supported version can be found on the Robot Web Tools CDN: ~([full](https://static.robotwebtools.org/threejs/r89/STLLoader.js))~ This CDN is gone. 32 | 33 | (ROS)ColladaLoader. We support patched version of ColladaLoader to workaround ros-visualization/rviz#1045. This version can be found on the Robot Web Tools CDN: ~([full](https://static.robotwebtools.org/ros3djs/0.18.0/ColladaLoader.js))~ This CDN is gone. 34 | 35 | [roslibjs](https://github.com/RobotWebTools/roslibjs). The current supported version is 1.3.0. The current supported version can be found on the JsDeliver CDN: ([full](https://cdn.jsdelivr.net/npm/roslib@1/build/roslib.js)) | ([min](https://cdn.jsdelivr.net/npm/roslib@1/build/roslib.min.js)) 36 | 37 | ### Build 38 | 39 | [Grunt](http://gruntjs.com/) is used for building, including concatenating, minimizing, documenting, linting, and testing. 40 | 41 | ### Install Grunt and its Dependencies 42 | 43 | #### Ubuntu 18.04/20.04 44 | 45 | 1. Install Node.js and its package manager, NPM 46 | * `sudo apt-get install nodejs nodejs-legacy npm` or any other way you like 47 | 2. Install Grunt 48 | * `sudo npm install -g grunt-cli` 49 | * (optional) `sudo rm -rf ~/.npm ~/tmp` 50 | 3. Install the Grunt tasks specific to this project 51 | * `cd /path/to/ros3djs/` 52 | * `npm install .` 53 | 54 | ### Build with Grunt 55 | 56 | Before proceeding, please confirm you have installed the dependencies above. 57 | 58 | To run the build tasks: 59 | 60 | 1. `cd /path/to/ros3djs/` 61 | 2. `grunt build` 62 | 63 | `grunt build` will concatenate and minimize the files under src and replace ros3d.js and ros3d.min.js in the build directory. It will also run the linter and test cases. This is what [GitHub Actions](https://github.com/RobotWebTools/ros3djs/actions/workflows/main.yml) runs when a Pull Request is submitted. 64 | 65 | `grunt dev` will watch for any changes to any of the src/ files and automatically concatenate and minimize the files. This is ideal for those developing as you should only have to run `grunt dev` once. 66 | 67 | `grunt doc` will rebuild all JSDoc for the project. 68 | 69 | ### Testing 70 | 71 | Utilizes [mocha](https://mochajs.org/) and [chai](http://chaijs.com/) for in browser testing. 72 | 73 | To run tests simply open `tests/index.html` in a web browser 74 | 75 | ### Examples 76 | 77 | There are a variety of [examples](examples) of the different things that can be done with ros3djs. 78 | 79 | There are also some examples of how ros3djs can be used in different environments: 80 | 81 | - [Classic html script tag inclusion](examples) 82 | - [Modular html script tag inclusion](examples/html-import) 83 | 84 | ### License 85 | 86 | ros3djs is released with a BSD license. For full terms and conditions, see the [LICENSE](LICENSE) file. 87 | 88 | ### Authors 89 | 90 | See the [AUTHORS.md](AUTHORS.md) file for a full list of contributors. 91 | -------------------------------------------------------------------------------- /es6-support/index.js: -------------------------------------------------------------------------------- 1 | 2 | export * from './Ros3D' 3 | 4 | export * from './depthcloud/DepthCloud' 5 | 6 | export * from './interactivemarkers/InteractiveMarker' 7 | export * from './interactivemarkers/InteractiveMarkerClient' 8 | export * from './interactivemarkers/InteractiveMarkerControl' 9 | export * from './interactivemarkers/InteractiveMarkerHandle' 10 | export * from './interactivemarkers/InteractiveMarkerMenu' 11 | 12 | export * from './markers/Marker' 13 | export * from './markers/MarkerArrayClient' 14 | export * from './markers/MarkerClient' 15 | 16 | export * from './models/Arrow' 17 | export * from './models/Arrow2' 18 | export * from './models/Axes' 19 | export * from './models/Grid' 20 | export * from './models/MeshLoader' 21 | export * from './models/MeshResource' 22 | export * from './models/TriangleList' 23 | 24 | export * from './navigation/OccupancyGrid' 25 | export * from './navigation/OccupancyGridClient' 26 | export * from './navigation/OcTree' 27 | export * from './navigation/ColorOcTree' 28 | export * from './navigation/OcTreeClient' 29 | export * from './navigation/Odometry' 30 | export * from './navigation/Path' 31 | export * from './navigation/Point' 32 | export * from './navigation/Polygon' 33 | export * from './navigation/Pose' 34 | export * from './navigation/PoseArray' 35 | export * from './navigation/PoseWithCovariance' 36 | 37 | export * from './sensors/LaserScan' 38 | export * from './sensors/NavSatFix' 39 | export * from './sensors/Points' 40 | export * from './sensors/PointCloud2' 41 | export * from './sensors/TFAxes' 42 | 43 | export * from './urdf/Urdf' 44 | export * from './urdf/UrdfClient' 45 | 46 | export * from './visualization/interaction/Highlighter' 47 | export * from './visualization/interaction/MouseHandler' 48 | export * from './visualization/interaction/OrbitControls' 49 | export * from './visualization/SceneNode' 50 | export * from './visualization/Viewer' 51 | -------------------------------------------------------------------------------- /examples/continuousmap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 37 | 38 | 39 | 40 |

Continuous Map Example

41 |

42 | Use any method to publish continuous updates to topic /map and use this page to visualize updates. Follow these commands: 43 |

44 |
    45 |
  1. roscore
  2. 46 |
  3. (method of choice to publish to /map)
  4. 47 |
  5. roslaunch rosbridge_server rosbridge_websocket.launch
  6. 48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/depthcloud.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 54 | 55 | 56 | 57 |

Simple DepthCloud Example

58 |

Run the following commands in the terminal then refresh the page

59 |
    60 |
  1. roscore
  2. 61 |
  3. roslaunch rosbridge_server rosbridge_websocket.launch
  4. 62 |
  5. rosrun tf2_web_republisher tf2_web_republisher
  6. 63 |
  7. roslaunch openni_launch openni.launch depth_registration:=true
  8. 64 |
  9. rosrun web_video_server web_video_server _port:=9999
  10. 65 |
  11. rosrun depthcloud_encoder depthcloud_encoder_node _depth:=/camera/depth_registered/image_float _rgb:=/camera/rgb/image_rect_color
  12. 66 |
  13. rosrun nodelet nodelet standalone depth_image_proc/convert_metric image_raw:=/camera/depth_registered/image_raw image:=/camera/depth_registered/image_float
  14. 67 | 68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/html-import/README.md: -------------------------------------------------------------------------------- 1 | # Examples of ROS3D as an ECMAScript (ES) module in a static webpage 2 | 3 | > Providing `type="module"` on a `script` element tells HTML5-compliant browsers to treat the script as an ECMAScript module. A second fallback script can be provided to older browsers by providing the `nomodule` attribute 4 | 5 | > These examples must be run by a webserver as HTML imports can only load resources from the same (non-file) domain or domains that support CORS. (see [this issue](https://github.com/Polymer/polymer/issues/1535) for more info) 6 | 7 | ## Build Setup 8 | 9 | ``` bash 10 | # 1. install dependencies 11 | yarn install # or npm install 12 | 13 | # 2. start simple express webserver at localhost:3000 14 | yarn start # or npm start 15 | 16 | # 3. open app and view examples 17 | # http://localhost:3000 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/html-import/markers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 45 | 46 | 47 | 48 |

Simple Marker Example

49 |

Run the following commands in the terminal then refresh this page.

50 |
    51 |
  1. roscore
  2. 52 |
  3. rosrun visualization_marker_tutorials basic_shapes
  4. 53 |
  5. rosrun tf2_web_republisher tf2_web_republisher
  6. 54 |
  7. roslaunch rosbridge_server rosbridge_websocket.launch
  8. 55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/html-import/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-example-test", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.20.0", 13 | "ros3d": "file:../.." 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/html-import/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | 4 | app.use(express.static('.')) 5 | 6 | const examples = { 7 | markers: './markers.html' 8 | } 9 | 10 | app.get('/', (req, res) => res.send([ 11 | `

These examples demonstrate how to use ROS3D's es6 module within an html script tag:

`, 12 | ``, 17 | ].join('\n'))) 18 | 19 | app.listen(3000, () => console.log('Example app listening on port 3000!')) 20 | -------------------------------------------------------------------------------- /examples/interactivemarkers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 49 | 50 | 51 | 52 |

Simple Interactive Marker Example

53 |

Run the following commands in the terminal then refresh this page.

54 |
    55 |
  1. roscore
  2. 56 |
  3. rosrun interactive_marker_tutorials basic_controls
  4. 57 |
  5. rosrun interactive_marker_proxy proxy topic_ns:=/basic_controls 58 | target_frame:=/rotating_frame
  6. 59 |
  7. rosrun tf2_web_republisher tf2_web_republisher
  8. 60 |
  9. roslaunch rosbridge_server rosbridge_websocket.launch
  10. 61 |
62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/kitti.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 55 | 56 | 57 | 58 |

Kitti PointCloud2 Example

59 |

Run the following commands in the terminal then refresh the page.

60 |
    61 |
  1. roslaunch ros3djs.launch
  2. 62 |
  3. rosparam set use_sim_time true
  4. 63 |
  5. rosbag play -l --clock kitti_2011_09_26_drive_0002_synced.bag
  6. 64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 38 | 39 |

Simple Map Example

40 |

41 | Run the following commands in the terminal then refresh this page. This will load a map from the 42 | ros-groovy-rail-maps 43 | package. 44 |

45 |
    46 |
  1. roscore
  2. 47 |
  3. rosrun map_server map_server /opt/ros/groovy/share/rail_maps/maps/ilab.pgm 48 | 0.05
  4. 49 |
  5. roslaunch rosbridge_server rosbridge_websocket.launch
  6. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/markers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 48 | 49 | 50 | 51 |

Simple Marker Example

52 |

Run the following commands in the terminal then refresh this page.

53 |
    54 |
  1. roscore
  2. 55 |
  3. rosrun visualization_marker_tutorials basic_shapes
  4. 56 |
  5. rosrun tf2_web_republisher tf2_web_republisher
  6. 57 |
  7. roslaunch rosbridge_server rosbridge_websocket.launch
  8. 58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /examples/navsatfix.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 68 | 69 | 70 | 71 |

Kitti NavSatFix Example

72 |

Run the following commands in the terminal then refresh the page.

73 |
    74 |
  1. roslaunch ros3djs.launch
  2. 75 |
  3. rosparam set use_sim_time true
  4. 76 |
  5. rosbag play -l --clock kitti_2011_09_26_drive_0002_synced.bag
  6. 77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/octree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 37 | 38 | 39 | 40 |

Simple Octomap Example

41 | 42 |

43 | To run this example you'll require octomap_server ROS node. 44 | Run the following commands in the terminal from your ros workspace, then refresh this page. 45 |

46 | 47 |
    48 |
  1. roscore
  2. 49 |
  3. rosrun octomap_server map_server ./src/octomap_server/tests/teapot.32.bt
  4. 50 |
  5. roslaunch rosbridge_server rosbridge_websocket.launch
  6. 51 |
52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/pointcloud2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 47 | 48 | 49 | 50 |

Simple PointCloud2 Example

51 |

Run the following commands in the terminal then refresh the page.

52 |
    53 |
  1. roscore
  2. 54 |
  3. roslaunch rosbridge_server rosbridge_websocket.launch
  4. 55 |
  5. rosrun tf2_web_republisher tf2_web_republisher
  6. 56 |
  7. roslaunch openni_launch openni.launch depth_registration:=true
  8. 57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/ros3djs.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/urdf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 52 | 53 | 54 | 55 |

Simple URDF Example

56 |

Run the following commands in the terminal then refresh this page.

57 |
    58 |
  1. roslaunch pr2_description upload_pr2.launch
  2. 59 |
  3. rosrun robot_state_publisher robot_state_publisher
  4. 60 |
  5. rosparam set use_gui true
  6. 61 |
  7. rosrun joint_state_publisher joint_state_publisher
  8. 62 |
  9. rosrun tf2_web_republisher tf2_web_republisher
  10. 63 |
  11. roslaunch rosbridge_server rosbridge_websocket.launch
  12. 64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /jsdoc_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ "plugins/markdown" ], 3 | "markdown": { 4 | "parser": "gfm" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ros3d", 3 | "homepage": "https://robotwebtools.github.io", 4 | "description": "The standard ROS Javascript Visualization Library", 5 | "version": "1.1.0", 6 | "license": "BSD-3-Clause", 7 | "main": "./build/ros3d.cjs.js", 8 | "module": "./build/ros3d.esm.js", 9 | "dependencies": { 10 | "eventemitter3": "^5.0.1", 11 | "roslib": "^1.0.0", 12 | "three": "^0.89.0" 13 | }, 14 | "devDependencies": { 15 | "@rollup/plugin-buble": "^1.0.0", 16 | "@rollup/plugin-commonjs": "^28.0.0", 17 | "@rollup/plugin-node-resolve": "^16.0.0", 18 | "chai": "^4.3.0", 19 | "debug": "^4.3.0", 20 | "grunt": "^1.0.0", 21 | "grunt-contrib-clean": "^2.0.0", 22 | "grunt-contrib-watch": "^1.0.0", 23 | "grunt-execute": "git+https://github.com/gruntjs-updater/grunt-execute.git#peerDep", 24 | "grunt-jsdoc": "^2.1.0", 25 | "grunt-karma": "^4.0.0", 26 | "grunt-pipe": "git+https://github.com/RobotWebTools/grunt-pipe.git", 27 | "grunt-shell": "^4.0.0", 28 | "gruntify-eslint": "^5.0.0", 29 | "karma": "^6.3.0", 30 | "karma-chai": "^0.1.0", 31 | "karma-firefox-launcher": "^2.1.0", 32 | "karma-mocha": "^2.0.0", 33 | "mocha": "^11.0.1", 34 | "rollup": "^2.47.0", 35 | "rollup-plugin-filesize": "^10.0.0", 36 | "rollup-plugin-terser": "^7.0.0" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/RobotWebTools/ros3djs/releases" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/RobotWebTools/ros3djs/issues" 44 | }, 45 | "scripts": { 46 | "build": "grunt build", 47 | "doc": "grunt doc", 48 | "lint": "grunt lint", 49 | "lint-fix": "grunt lint-fix", 50 | "transpile": "DEBUG=* grunt transpile", 51 | "test": "grunt test", 52 | "publish": "npm run build && npm run test" 53 | }, 54 | "keywords": [ 55 | "ROS", 56 | "ros", 57 | "roslib", 58 | "roslibjs", 59 | "ros3d", 60 | "ros3djs", 61 | "robot" 62 | ], 63 | "author": "Robot Webtools Team (https://robotwebtools.github.io)", 64 | "directories": { 65 | "example": "examples" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | 3 | // plugin that transpiles output into commonjs format 4 | const commonjs = require('@rollup/plugin-commonjs'); 5 | // plugin that transpiles es6 to es5 for legacy platforms 6 | const buble = require('@rollup/plugin-buble'); 7 | // plugin that shows output file info 8 | const filesize = require('rollup-plugin-filesize'); 9 | /// plugin that resolves node module imports 10 | const { nodeResolve } = require('@rollup/plugin-node-resolve'); 11 | // plugin that minifies and obfuscates code 12 | const { terser } = require('rollup-plugin-terser'); 13 | 14 | const pkg = require('./package.json'); 15 | const input = 'src-esm/index.js'; 16 | 17 | const browserGlobals = { 18 | roslib: 'ROSLIB', 19 | }; 20 | 21 | const moduleGlobals = { 22 | roslib: 'ROSLIB', 23 | }; 24 | 25 | const outputFiles = { 26 | commonModule: pkg.main, 27 | esModule: pkg.module, 28 | browserGlobal: './build/ros3d.js', 29 | browserGlobalMinified: './build/ros3d.min.js', 30 | }; 31 | 32 | export default [ 33 | // build main as ES5 in CommonJS format for compatibility 34 | { 35 | input, 36 | output: { 37 | name: 'ROS3D', 38 | file: outputFiles.commonModule, 39 | format: 'cjs', 40 | globals: { 41 | ...moduleGlobals, 42 | } 43 | }, 44 | external: [ 45 | ...Object.keys(moduleGlobals) 46 | ], 47 | plugins: [ 48 | nodeResolve({ browser: true }), 49 | commonjs(), 50 | buble(), 51 | filesize(), 52 | ], 53 | }, 54 | // build module as ES5 in ES module format for modern tooling 55 | { 56 | input, 57 | output: { 58 | name: 'ROS3D', 59 | file: outputFiles.esModule, 60 | format: 'es', 61 | globals: { 62 | ...moduleGlobals, 63 | } 64 | }, 65 | external: [ 66 | ...Object.keys(moduleGlobals) 67 | ], 68 | plugins: [ 69 | nodeResolve({ browser: true }), 70 | commonjs(), 71 | buble(), 72 | filesize(), 73 | ], 74 | }, 75 | // build browser as IIFE module for script tag inclusion, unminified 76 | // Usage: 77 | // 78 | { 79 | input, 80 | output: { 81 | name: 'ROS3D', 82 | file: outputFiles.browserGlobal, 83 | format: 'iife', 84 | globals: { 85 | ...browserGlobals, 86 | }, 87 | }, 88 | external: [ 89 | ...Object.keys(browserGlobals), 90 | ], 91 | plugins: [ 92 | nodeResolve({ browser: true }), 93 | commonjs(), 94 | filesize(), 95 | ], 96 | }, 97 | // build browser as IIFE module for script tag inclusion, minified 98 | // Usage: 99 | // 100 | { 101 | input, 102 | output: { 103 | name: 'ROS3D', 104 | file: outputFiles.browserGlobalMinified, 105 | format: 'iife', 106 | globals: { 107 | ...browserGlobals, 108 | }, 109 | }, 110 | external: [ 111 | ...Object.keys(browserGlobals), 112 | ], 113 | plugins: [ 114 | nodeResolve({ browser: true }), 115 | commonjs(), 116 | filesize(), 117 | terser(), 118 | ], 119 | }, 120 | ]; 121 | -------------------------------------------------------------------------------- /shims/three/STLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author aleeper / http://adamleeper.com/ 4 | * @author mrdoob / http://mrdoob.com/ 5 | * @author gero3 / https://github.com/gero3 6 | * @author Mugen87 / https://github.com/Mugen87 7 | * 8 | * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. 9 | * 10 | * Supports both binary and ASCII encoded files, with automatic detection of type. 11 | * 12 | * The loader returns a non-indexed buffer geometry. 13 | * 14 | * Limitations: 15 | * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). 16 | * There is perhaps some question as to how valid it is to always assume little-endian-ness. 17 | * ASCII decoding assumes file is UTF-8. 18 | * 19 | * Usage: 20 | * var loader = new THREE.STLLoader(); 21 | * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { 22 | * scene.add( new THREE.Mesh( geometry ) ); 23 | * }); 24 | * 25 | * For binary STLs geometry might contain colors for vertices. To use it: 26 | * // use the same code to load STL as above 27 | * if (geometry.hasColors) { 28 | * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors }); 29 | * } else { .... } 30 | * var mesh = new THREE.Mesh( geometry, material ); 31 | */ 32 | 33 | import THREE from './core' 34 | 35 | THREE.STLLoader = function (manager) { 36 | 37 | this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager; 38 | 39 | }; 40 | 41 | THREE.STLLoader.prototype = { 42 | 43 | constructor: THREE.STLLoader, 44 | 45 | load: function (url, onLoad, onProgress, onError) { 46 | 47 | var scope = this; 48 | 49 | var loader = new THREE.FileLoader(scope.manager); 50 | loader.setResponseType('arraybuffer'); 51 | loader.load(url, function (text) { 52 | 53 | onLoad(scope.parse(text)); 54 | 55 | }, onProgress, onError); 56 | 57 | }, 58 | 59 | parse: function (data) { 60 | 61 | function isBinary(data) { 62 | 63 | var expect, face_size, n_faces, reader; 64 | reader = new DataView(data); 65 | face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8); 66 | n_faces = reader.getUint32(80, true); 67 | expect = 80 + (32 / 8) + (n_faces * face_size); 68 | 69 | if (expect === reader.byteLength) { 70 | 71 | return true; 72 | 73 | } 74 | 75 | // An ASCII STL data must begin with 'solid ' as the first six bytes. 76 | // However, ASCII STLs lacking the SPACE after the 'd' are known to be 77 | // plentiful. So, check the first 5 bytes for 'solid'. 78 | 79 | // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' 80 | 81 | var solid = [115, 111, 108, 105, 100]; 82 | 83 | for (var i = 0; i < 5; i++) { 84 | 85 | // If solid[ i ] does not match the i-th byte, then it is not an 86 | // ASCII STL; hence, it is binary and return true. 87 | 88 | if (solid[i] != reader.getUint8(i, false)) return true; 89 | 90 | } 91 | 92 | // First 5 bytes read "solid"; declare it to be an ASCII STL 93 | 94 | return false; 95 | 96 | } 97 | 98 | function parseBinary(data) { 99 | 100 | var reader = new DataView(data); 101 | var faces = reader.getUint32(80, true); 102 | 103 | var r, g, b, hasColors = false, colors; 104 | var defaultR, defaultG, defaultB, alpha; 105 | 106 | // process STL header 107 | // check for default color in header ("COLOR=rgba" sequence). 108 | 109 | for (var index = 0; index < 80 - 10; index++) { 110 | 111 | if ((reader.getUint32(index, false) == 0x434F4C4F /*COLO*/) && 112 | (reader.getUint8(index + 4) == 0x52 /*'R'*/) && 113 | (reader.getUint8(index + 5) == 0x3D /*'='*/)) { 114 | 115 | hasColors = true; 116 | colors = []; 117 | 118 | defaultR = reader.getUint8(index + 6) / 255; 119 | defaultG = reader.getUint8(index + 7) / 255; 120 | defaultB = reader.getUint8(index + 8) / 255; 121 | alpha = reader.getUint8(index + 9) / 255; 122 | 123 | } 124 | 125 | } 126 | 127 | var dataOffset = 84; 128 | var faceLength = 12 * 4 + 2; 129 | 130 | var geometry = new THREE.BufferGeometry(); 131 | 132 | var vertices = []; 133 | var normals = []; 134 | 135 | for (var face = 0; face < faces; face++) { 136 | 137 | var start = dataOffset + face * faceLength; 138 | var normalX = reader.getFloat32(start, true); 139 | var normalY = reader.getFloat32(start + 4, true); 140 | var normalZ = reader.getFloat32(start + 8, true); 141 | 142 | if (hasColors) { 143 | 144 | var packedColor = reader.getUint16(start + 48, true); 145 | 146 | if ((packedColor & 0x8000) === 0) { 147 | 148 | // facet has its own unique color 149 | 150 | r = (packedColor & 0x1F) / 31; 151 | g = ((packedColor >> 5) & 0x1F) / 31; 152 | b = ((packedColor >> 10) & 0x1F) / 31; 153 | 154 | } else { 155 | 156 | r = defaultR; 157 | g = defaultG; 158 | b = defaultB; 159 | 160 | } 161 | 162 | } 163 | 164 | for (var i = 1; i <= 3; i++) { 165 | 166 | var vertexstart = start + i * 12; 167 | 168 | vertices.push(reader.getFloat32(vertexstart, true)); 169 | vertices.push(reader.getFloat32(vertexstart + 4, true)); 170 | vertices.push(reader.getFloat32(vertexstart + 8, true)); 171 | 172 | normals.push(normalX, normalY, normalZ); 173 | 174 | if (hasColors) { 175 | 176 | colors.push(r, g, b); 177 | 178 | } 179 | 180 | } 181 | 182 | } 183 | 184 | geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3)); 185 | geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(normals), 3)); 186 | 187 | if (hasColors) { 188 | 189 | geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3)); 190 | geometry.hasColors = true; 191 | geometry.alpha = alpha; 192 | 193 | } 194 | 195 | return geometry; 196 | 197 | } 198 | 199 | function parseASCII(data) { 200 | 201 | var geometry = new THREE.BufferGeometry(); 202 | var patternFace = /facet([\s\S]*?)endfacet/g; 203 | var faceCounter = 0; 204 | 205 | var patternFloat = /[\s]+([+-]?(?:\d+.\d+|\d+.|\d+|.\d+)(?:[eE][+-]?\d+)?)/.source; 206 | var patternVertex = new RegExp('vertex' + patternFloat + patternFloat + patternFloat, 'g'); 207 | var patternNormal = new RegExp('normal' + patternFloat + patternFloat + patternFloat, 'g'); 208 | 209 | var vertices = []; 210 | var normals = []; 211 | 212 | var normal = new THREE.Vector3(); 213 | 214 | var result; 215 | 216 | while ((result = patternFace.exec(data)) !== null) { 217 | 218 | var vertexCountPerFace = 0; 219 | var normalCountPerFace = 0; 220 | 221 | var text = result[0]; 222 | 223 | while ((result = patternNormal.exec(text)) !== null) { 224 | 225 | normal.x = parseFloat(result[1]); 226 | normal.y = parseFloat(result[2]); 227 | normal.z = parseFloat(result[3]); 228 | normalCountPerFace++; 229 | 230 | } 231 | 232 | while ((result = patternVertex.exec(text)) !== null) { 233 | 234 | vertices.push(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])); 235 | normals.push(normal.x, normal.y, normal.z); 236 | vertexCountPerFace++; 237 | 238 | } 239 | 240 | // every face have to own ONE valid normal 241 | 242 | if (normalCountPerFace !== 1) { 243 | 244 | console.error('THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter); 245 | 246 | } 247 | 248 | // each face have to own THREE valid vertices 249 | 250 | if (vertexCountPerFace !== 3) { 251 | 252 | console.error('THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter); 253 | 254 | } 255 | 256 | faceCounter++; 257 | 258 | } 259 | 260 | geometry.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); 261 | geometry.addAttribute('normal', new THREE.Float32BufferAttribute(normals, 3)); 262 | 263 | return geometry; 264 | 265 | } 266 | 267 | function ensureString(buffer) { 268 | 269 | if (typeof buffer !== 'string') { 270 | 271 | var array_buffer = new Uint8Array(buffer); 272 | 273 | if (window.TextDecoder !== undefined) { 274 | 275 | return new TextDecoder().decode(array_buffer); 276 | 277 | } 278 | 279 | var str = ''; 280 | 281 | for (var i = 0, il = buffer.byteLength; i < il; i++) { 282 | 283 | str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian 284 | 285 | } 286 | 287 | return str; 288 | 289 | } else { 290 | 291 | return buffer; 292 | 293 | } 294 | 295 | } 296 | 297 | function ensureBinary(buffer) { 298 | 299 | if (typeof buffer === 'string') { 300 | 301 | var array_buffer = new Uint8Array(buffer.length); 302 | for (var i = 0; i < buffer.length; i++) { 303 | 304 | array_buffer[i] = buffer.charCodeAt(i) & 0xff; // implicitly assumes little-endian 305 | 306 | } 307 | return array_buffer.buffer || array_buffer; 308 | 309 | } else { 310 | 311 | return buffer; 312 | 313 | } 314 | 315 | } 316 | 317 | // start 318 | 319 | var binData = ensureBinary(data); 320 | 321 | return isBinary(binData) ? parseBinary(binData) : parseASCII(ensureString(data)); 322 | 323 | } 324 | 325 | }; 326 | -------------------------------------------------------------------------------- /shims/three/core.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export default Object.assign({}, THREE) 4 | -------------------------------------------------------------------------------- /src/Ros3D.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Russell Toris - rctoris@wpi.edu 4 | * @author David Gossow - dgossow@willowgarage.com 5 | */ 6 | 7 | var ROS3D = ROS3D || { 8 | /** 9 | * @default 10 | * @description Library version 11 | */ 12 | REVISION : '1.1.0' 13 | }; 14 | 15 | // Marker types 16 | ROS3D.MARKER_ARROW = 0; 17 | ROS3D.MARKER_CUBE = 1; 18 | ROS3D.MARKER_SPHERE = 2; 19 | ROS3D.MARKER_CYLINDER = 3; 20 | ROS3D.MARKER_LINE_STRIP = 4; 21 | ROS3D.MARKER_LINE_LIST = 5; 22 | ROS3D.MARKER_CUBE_LIST = 6; 23 | ROS3D.MARKER_SPHERE_LIST = 7; 24 | ROS3D.MARKER_POINTS = 8; 25 | ROS3D.MARKER_TEXT_VIEW_FACING = 9; 26 | ROS3D.MARKER_MESH_RESOURCE = 10; 27 | ROS3D.MARKER_TRIANGLE_LIST = 11; 28 | 29 | // Interactive marker feedback types 30 | ROS3D.INTERACTIVE_MARKER_KEEP_ALIVE = 0; 31 | ROS3D.INTERACTIVE_MARKER_POSE_UPDATE = 1; 32 | ROS3D.INTERACTIVE_MARKER_MENU_SELECT = 2; 33 | ROS3D.INTERACTIVE_MARKER_BUTTON_CLICK = 3; 34 | ROS3D.INTERACTIVE_MARKER_MOUSE_DOWN = 4; 35 | ROS3D.INTERACTIVE_MARKER_MOUSE_UP = 5; 36 | 37 | // Interactive marker control types 38 | ROS3D.INTERACTIVE_MARKER_NONE = 0; 39 | ROS3D.INTERACTIVE_MARKER_MENU = 1; 40 | ROS3D.INTERACTIVE_MARKER_BUTTON = 2; 41 | ROS3D.INTERACTIVE_MARKER_MOVE_AXIS = 3; 42 | ROS3D.INTERACTIVE_MARKER_MOVE_PLANE = 4; 43 | ROS3D.INTERACTIVE_MARKER_ROTATE_AXIS = 5; 44 | ROS3D.INTERACTIVE_MARKER_MOVE_ROTATE = 6; 45 | ROS3D.INTERACTIVE_MARKER_MOVE_3D = 7; 46 | ROS3D.INTERACTIVE_MARKER_ROTATE_3D = 8; 47 | ROS3D.INTERACTIVE_MARKER_MOVE_ROTATE_3D = 9; 48 | 49 | // Interactive marker rotation behavior 50 | ROS3D.INTERACTIVE_MARKER_INHERIT = 0; 51 | ROS3D.INTERACTIVE_MARKER_FIXED = 1; 52 | ROS3D.INTERACTIVE_MARKER_VIEW_FACING = 2; 53 | 54 | /** 55 | * @function makeColorMaterial 56 | * @description Create a THREE material based on the given RGBA values. 57 | * 58 | * @param r - the red value 59 | * @param g - the green value 60 | * @param b - the blue value 61 | * @param a - the alpha value 62 | * @returns the THREE material 63 | */ 64 | ROS3D.makeColorMaterial = function(r, g, b, a) { 65 | var color = new THREE.Color(); 66 | color.setRGB(r, g, b); 67 | if (a <= 0.99) { 68 | return new THREE.MeshBasicMaterial({ 69 | color : color.getHex(), 70 | opacity : a + 0.1, 71 | transparent : true, 72 | depthWrite : true, 73 | blendSrc : THREE.SrcAlphaFactor, 74 | blendDst : THREE.OneMinusSrcAlphaFactor, 75 | blendEquation : THREE.ReverseSubtractEquation, 76 | blending : THREE.NormalBlending 77 | }); 78 | } else { 79 | return new THREE.MeshPhongMaterial({ 80 | color : color.getHex(), 81 | opacity : a, 82 | blending : THREE.NormalBlending 83 | }); 84 | } 85 | }; 86 | 87 | /** 88 | * @function intersectPlane 89 | * @description Return the intersection between the mouseray and the plane. 90 | * 91 | * @param mouseRay - the mouse ray 92 | * @param planeOrigin - the origin of the plane 93 | * @param planeNormal - the normal of the plane 94 | * @returns the intersection point 95 | */ 96 | ROS3D.intersectPlane = function(mouseRay, planeOrigin, planeNormal) { 97 | var vector = new THREE.Vector3(); 98 | var intersectPoint = new THREE.Vector3(); 99 | vector.subVectors(planeOrigin, mouseRay.origin); 100 | var dot = mouseRay.direction.dot(planeNormal); 101 | 102 | // bail if ray and plane are parallel 103 | if (Math.abs(dot) < mouseRay.precision) { 104 | return undefined; 105 | } 106 | 107 | // calc distance to plane 108 | var scalar = planeNormal.dot(vector) / dot; 109 | 110 | intersectPoint.addVectors(mouseRay.origin, mouseRay.direction.clone().multiplyScalar(scalar)); 111 | return intersectPoint; 112 | }; 113 | 114 | /** 115 | * @function findClosestPoint 116 | * @description Find the closest point on targetRay to any point on mouseRay. Math taken from 117 | * http://paulbourke.net/geometry/lineline3d/ 118 | * 119 | * @param targetRay - the target ray to use 120 | * @param mouseRay - the mouse ray 121 | * @param the closest point between the two rays 122 | */ 123 | ROS3D.findClosestPoint = function(targetRay, mouseRay) { 124 | var v13 = new THREE.Vector3(); 125 | v13.subVectors(targetRay.origin, mouseRay.origin); 126 | var v43 = mouseRay.direction.clone(); 127 | var v21 = targetRay.direction.clone(); 128 | var d1343 = v13.dot(v43); 129 | var d4321 = v43.dot(v21); 130 | var d1321 = v13.dot(v21); 131 | var d4343 = v43.dot(v43); 132 | var d2121 = v21.dot(v21); 133 | 134 | var denom = d2121 * d4343 - d4321 * d4321; 135 | // check within a delta 136 | if (Math.abs(denom) <= 0.0001) { 137 | return undefined; 138 | } 139 | var numer = d1343 * d4321 - d1321 * d4343; 140 | 141 | var mua = numer / denom; 142 | return mua; 143 | }; 144 | 145 | /** 146 | * @function closestAxisPoint 147 | * @description Find the closest point between the axis and the mouse. 148 | * 149 | * @param axisRay - the ray from the axis 150 | * @param camera - the camera to project from 151 | * @param mousePos - the mouse position 152 | * @returns the closest axis point 153 | */ 154 | ROS3D.closestAxisPoint = function(axisRay, camera, mousePos) { 155 | // project axis onto screen 156 | var o = axisRay.origin.clone(); 157 | o.project(camera); 158 | var o2 = axisRay.direction.clone().add(axisRay.origin); 159 | o2.project(camera); 160 | 161 | // d is the axis vector in screen space (d = o2-o) 162 | var d = o2.clone().sub(o); 163 | 164 | // t is the 2d ray param of perpendicular projection of mousePos onto o 165 | var tmp = new THREE.Vector2(); 166 | // (t = (mousePos - o) * d / (d*d)) 167 | var t = tmp.subVectors(mousePos, o).dot(d) / d.dot(d); 168 | 169 | // mp is the final 2d-projected mouse pos (mp = o + d*t) 170 | var mp = new THREE.Vector2(); 171 | mp.addVectors(o, d.clone().multiplyScalar(t)); 172 | 173 | // go back to 3d by shooting a ray 174 | var vector = new THREE.Vector3(mp.x, mp.y, 0.5); 175 | vector.unproject(camera); 176 | var mpRay = new THREE.Ray(camera.position, vector.sub(camera.position).normalize()); 177 | 178 | return ROS3D.findClosestPoint(axisRay, mpRay); 179 | }; 180 | -------------------------------------------------------------------------------- /src/interactivemarkers/InteractiveMarkerClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * A client for an interactive marker topic. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * ros - a handle to the ROS connection 13 | * * tfClient - a handle to the TF client 14 | * * topic (optional) - the topic to subscribe to, like '/basic_controls', if not provided use subscribe() to start message receiving 15 | * * path (optional) - the base path to any meshes that will be loaded 16 | * * camera - the main camera associated with the viewer for this marker client 17 | * * rootObject (optional) - the root THREE 3D object to render to 18 | * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER) 19 | * * menuFontSize (optional) - the menu font size 20 | */ 21 | ROS3D.InteractiveMarkerClient = function(options) { 22 | options = options || {}; 23 | this.ros = options.ros; 24 | this.tfClient = options.tfClient; 25 | this.topicName = options.topic; 26 | this.path = options.path || '/'; 27 | this.camera = options.camera; 28 | this.rootObject = options.rootObject || new THREE.Object3D(); 29 | this.loader = options.loader; 30 | this.menuFontSize = options.menuFontSize || '0.8em'; 31 | 32 | this.interactiveMarkers = {}; 33 | this.updateTopic = null; 34 | this.feedbackTopic = null; 35 | this.processUpdateBound = this.processUpdate.bind(this); 36 | 37 | // check for an initial topic 38 | if (this.topicName) { 39 | this.subscribe(this.topicName); 40 | } 41 | }; 42 | 43 | /** 44 | * Subscribe to the given interactive marker topic. This will unsubscribe from any current topics. 45 | * 46 | * @param topic - the topic to subscribe to, like '/basic_controls' 47 | */ 48 | ROS3D.InteractiveMarkerClient.prototype.subscribe = function(topic) { 49 | // unsubscribe to the other topics 50 | this.unsubscribe(); 51 | 52 | this.updateTopic = new ROSLIB.Topic({ 53 | ros : this.ros, 54 | name : topic + '/tunneled/update', 55 | messageType : 'visualization_msgs/InteractiveMarkerUpdate', 56 | compression : 'png' 57 | }); 58 | this.updateTopic.subscribe(this.processUpdateBound); 59 | 60 | this.feedbackTopic = new ROSLIB.Topic({ 61 | ros : this.ros, 62 | name : topic + '/feedback', 63 | messageType : 'visualization_msgs/InteractiveMarkerFeedback', 64 | compression : 'png' 65 | }); 66 | this.feedbackTopic.advertise(); 67 | 68 | this.initService = new ROSLIB.Service({ 69 | ros : this.ros, 70 | name : topic + '/tunneled/get_init', 71 | serviceType : 'demo_interactive_markers/GetInit' 72 | }); 73 | var request = new ROSLIB.ServiceRequest({}); 74 | this.initService.callService(request, this.processInit.bind(this)); 75 | }; 76 | 77 | /** 78 | * Unsubscribe from the current interactive marker topic. 79 | */ 80 | ROS3D.InteractiveMarkerClient.prototype.unsubscribe = function() { 81 | if (this.updateTopic) { 82 | this.updateTopic.unsubscribe(this.processUpdateBound); 83 | } 84 | if (this.feedbackTopic) { 85 | this.feedbackTopic.unadvertise(); 86 | } 87 | // erase all markers 88 | for (var intMarkerName in this.interactiveMarkers) { 89 | this.eraseIntMarker(intMarkerName); 90 | } 91 | this.interactiveMarkers = {}; 92 | }; 93 | 94 | /** 95 | * Process the given interactive marker initialization message. 96 | * 97 | * @param initMessage - the interactive marker initialization message to process 98 | */ 99 | ROS3D.InteractiveMarkerClient.prototype.processInit = function(initMessage) { 100 | var message = initMessage.msg; 101 | 102 | // erase any old markers 103 | message.erases = []; 104 | for (var intMarkerName in this.interactiveMarkers) { 105 | message.erases.push(intMarkerName); 106 | } 107 | message.poses = []; 108 | 109 | // treat it as an update 110 | this.processUpdate(message); 111 | }; 112 | 113 | /** 114 | * Process the given interactive marker update message. 115 | * 116 | * @param initMessage - the interactive marker update message to process 117 | */ 118 | ROS3D.InteractiveMarkerClient.prototype.processUpdate = function(message) { 119 | // erase any markers 120 | message.erases.forEach(function(name) { 121 | this.eraseIntMarker(name); 122 | }); 123 | 124 | // updates marker poses 125 | message.poses.forEach(function(poseMessage) { 126 | var marker = this.interactiveMarkers[poseMessage.name]; 127 | if (marker) { 128 | marker.setPoseFromServer(poseMessage.pose); 129 | } 130 | }); 131 | 132 | // add new markers 133 | message.markers.forEach(function(msg) { 134 | // get rid of anything with the same name 135 | var oldhandle = this.interactiveMarkers[msg.name]; 136 | if (oldhandle) { 137 | this.eraseIntMarker(oldhandle.name); 138 | } 139 | 140 | // create the handle 141 | var handle = new ROS3D.InteractiveMarkerHandle({ 142 | message : msg, 143 | feedbackTopic : this.feedbackTopic, 144 | tfClient : this.tfClient, 145 | menuFontSize : this.menuFontSize 146 | }); 147 | this.interactiveMarkers[msg.name] = handle; 148 | 149 | // create the actual marker 150 | var intMarker = new ROS3D.InteractiveMarker({ 151 | handle : handle, 152 | camera : this.camera, 153 | path : this.path, 154 | loader : this.loader 155 | }); 156 | // add it to the scene 157 | intMarker.name = msg.name; 158 | this.rootObject.add(intMarker); 159 | 160 | // listen for any pose updates from the server 161 | handle.on('pose', function(pose) { 162 | intMarker.onServerSetPose({ 163 | pose : pose 164 | }); 165 | }); 166 | 167 | // add bound versions of UI handlers 168 | intMarker.addEventListener('user-pose-change', handle.setPoseFromClientBound); 169 | intMarker.addEventListener('user-mousedown', handle.onMouseDownBound); 170 | intMarker.addEventListener('user-mouseup', handle.onMouseUpBound); 171 | intMarker.addEventListener('user-button-click', handle.onButtonClickBound); 172 | intMarker.addEventListener('menu-select', handle.onMenuSelectBound); 173 | 174 | // now listen for any TF changes 175 | handle.subscribeTf(); 176 | }); 177 | }; 178 | 179 | /** 180 | * Erase the interactive marker with the given name. 181 | * 182 | * @param intMarkerName - the interactive marker name to delete 183 | */ 184 | ROS3D.InteractiveMarkerClient.prototype.eraseIntMarker = function(intMarkerName) { 185 | if (this.interactiveMarkers[intMarkerName]) { 186 | // remove the object 187 | var targetIntMarker = this.rootObject.getObjectByName(intMarkerName); 188 | this.rootObject.remove(targetIntMarker); 189 | // unsubscribe from TF topic! 190 | var handle = this.interactiveMarkers[intMarkerName]; 191 | handle.unsubscribeTf(); 192 | 193 | // remove all other listeners 194 | 195 | targetIntMarker.removeEventListener('user-pose-change', handle.setPoseFromClientBound); 196 | targetIntMarker.removeEventListener('user-mousedown', handle.onMouseDownBound); 197 | targetIntMarker.removeEventListener('user-mouseup', handle.onMouseUpBound); 198 | targetIntMarker.removeEventListener('user-button-click', handle.onButtonClickBound); 199 | targetIntMarker.removeEventListener('menu-select', handle.onMenuSelectBound); 200 | 201 | // remove the handle from the map - after leaving this function's scope, there should be no references to the handle 202 | delete this.interactiveMarkers[intMarkerName]; 203 | targetIntMarker.dispose(); 204 | } 205 | }; 206 | -------------------------------------------------------------------------------- /src/interactivemarkers/InteractiveMarkerControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * The main marker control object for an interactive marker. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * parent - the parent of this control 13 | * * message - the interactive marker control message 14 | * * camera - the main camera associated with the viewer for this marker client 15 | * * path (optional) - the base path to any meshes that will be loaded 16 | * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER) 17 | */ 18 | ROS3D.InteractiveMarkerControl = function(options) { 19 | THREE.Object3D.call(this); 20 | var that = this; 21 | 22 | options = options || {}; 23 | this.parent = options.parent; 24 | var handle = options.handle; 25 | var message = options.message; 26 | this.message = message; 27 | this.name = message.name; 28 | this.camera = options.camera; 29 | this.path = options.path || '/'; 30 | this.loader = options.loader; 31 | this.dragging = false; 32 | this.startMousePos = new THREE.Vector2(); 33 | this.isShift = false; 34 | 35 | 36 | // orientation for the control 37 | var controlOri = new THREE.Quaternion(message.orientation.x, message.orientation.y, 38 | message.orientation.z, message.orientation.w); 39 | controlOri.normalize(); 40 | 41 | // transform x axis into local frame 42 | var controlAxis = new THREE.Vector3(1, 0, 0); 43 | controlAxis.applyQuaternion(controlOri); 44 | 45 | this.currentControlOri = new THREE.Quaternion(); 46 | 47 | // determine mouse interaction 48 | switch (message.interaction_mode) { 49 | case ROS3D.INTERACTIVE_MARKER_MOVE_ROTATE_3D: 50 | case ROS3D.INTERACTIVE_MARKER_MOVE_3D: 51 | this.addEventListener('mousemove', this.parent.move3d.bind(this.parent, this, controlAxis)); 52 | break; 53 | case ROS3D.INTERACTIVE_MARKER_MOVE_AXIS: 54 | this.addEventListener('mousemove', this.parent.moveAxis.bind(this.parent, this, controlAxis)); 55 | this.addEventListener('touchmove', this.parent.moveAxis.bind(this.parent, this, controlAxis)); 56 | break; 57 | case ROS3D.INTERACTIVE_MARKER_ROTATE_AXIS: 58 | this 59 | .addEventListener('mousemove', this.parent.rotateAxis.bind(this.parent, this, controlOri)); 60 | break; 61 | case ROS3D.INTERACTIVE_MARKER_MOVE_PLANE: 62 | this 63 | .addEventListener('mousemove', this.parent.movePlane.bind(this.parent, this, controlAxis)); 64 | break; 65 | case ROS3D.INTERACTIVE_MARKER_BUTTON: 66 | this.addEventListener('click', this.parent.buttonClick.bind(this.parent, this)); 67 | break; 68 | default: 69 | break; 70 | } 71 | 72 | /** 73 | * Install default listeners for highlighting / dragging. 74 | * 75 | * @param event - the event to stop 76 | */ 77 | function stopPropagation(event) { 78 | event.stopPropagation(); 79 | } 80 | 81 | // check the mode 82 | if (message.interaction_mode !== ROS3D.INTERACTIVE_MARKER_NONE) { 83 | this.addEventListener('mousedown', this.parent.startDrag.bind(this.parent, this)); 84 | this.addEventListener('mouseup', this.parent.stopDrag.bind(this.parent, this)); 85 | this.addEventListener('contextmenu', this.parent.showMenu.bind(this.parent, this)); 86 | this.addEventListener('mouseup', function(event3d) { 87 | if (that.startMousePos.distanceToSquared(event3d.mousePos) === 0) { 88 | event3d.type = 'contextmenu'; 89 | that.dispatchEvent(event3d); 90 | } 91 | }); 92 | this.addEventListener('mouseover', stopPropagation); 93 | this.addEventListener('mouseout', stopPropagation); 94 | this.addEventListener('click', stopPropagation); 95 | this.addEventListener('mousedown', function(event3d) { 96 | that.startMousePos = event3d.mousePos; 97 | }); 98 | 99 | // touch support 100 | this.addEventListener('touchstart', function(event3d) { 101 | if (event3d.domEvent.touches.length === 1) { 102 | event3d.type = 'mousedown'; 103 | event3d.domEvent.button = 0; 104 | that.dispatchEvent(event3d); 105 | } 106 | }); 107 | this.addEventListener('touchmove', function(event3d) { 108 | if (event3d.domEvent.touches.length === 1) { 109 | event3d.type = 'mousemove'; 110 | event3d.domEvent.button = 0; 111 | that.dispatchEvent(event3d); 112 | } 113 | }); 114 | this.addEventListener('touchend', function(event3d) { 115 | if (event3d.domEvent.touches.length === 0) { 116 | event3d.domEvent.button = 0; 117 | event3d.type = 'mouseup'; 118 | that.dispatchEvent(event3d); 119 | event3d.type = 'click'; 120 | that.dispatchEvent(event3d); 121 | } 122 | }); 123 | 124 | window.addEventListener('keydown', function(event){ 125 | if(event.keyCode === 16){ 126 | that.isShift = true; 127 | } 128 | }); 129 | window.addEventListener('keyup', function(event){ 130 | if(event.keyCode === 16){ 131 | that.isShift = false; 132 | } 133 | }); 134 | } 135 | 136 | // rotation behavior 137 | var rotInv = new THREE.Quaternion(); 138 | var posInv = this.parent.position.clone().multiplyScalar(-1); 139 | switch (message.orientation_mode) { 140 | case ROS3D.INTERACTIVE_MARKER_INHERIT: 141 | rotInv = this.parent.quaternion.clone().inverse(); 142 | break; 143 | case ROS3D.INTERACTIVE_MARKER_FIXED: 144 | break; 145 | case ROS3D.INTERACTIVE_MARKER_VIEW_FACING: 146 | break; 147 | default: 148 | console.error('Unkown orientation mode: ' + message.orientation_mode); 149 | break; 150 | } 151 | 152 | // temporary TFClient to get transformations from InteractiveMarker 153 | // frame to potential child Marker frames 154 | var localTfClient = new ROSLIB.TFClient({ 155 | ros : handle.tfClient.ros, 156 | fixedFrame : handle.message.header.frame_id, 157 | serverName : handle.tfClient.serverName 158 | }); 159 | 160 | // create visuals (markers) 161 | message.markers.forEach(function(markerMsg) { 162 | var addMarker = function(transformMsg) { 163 | var markerHelper = new ROS3D.Marker({ 164 | message : markerMsg, 165 | path : that.path, 166 | loader : that.loader 167 | }); 168 | 169 | // if transformMsg isn't null, this was called by TFClient 170 | if (transformMsg !== null) { 171 | // get the current pose as a ROSLIB.Pose... 172 | var newPose = new ROSLIB.Pose({ 173 | position : markerHelper.position, 174 | orientation : markerHelper.quaternion 175 | }); 176 | // so we can apply the transform provided by the TFClient 177 | newPose.applyTransform(new ROSLIB.Transform(transformMsg)); 178 | 179 | // get transform between parent marker's location and its frame 180 | // apply it to sub-marker position to get sub-marker position 181 | // relative to parent marker 182 | var transformMarker = new ROS3D.Marker({ 183 | message : markerMsg, 184 | path : that.path, 185 | loader : that.loader 186 | }); 187 | transformMarker.position.add(posInv); 188 | transformMarker.position.applyQuaternion(rotInv); 189 | transformMarker.quaternion.multiplyQuaternions(rotInv, transformMarker.quaternion); 190 | var translation = new THREE.Vector3(transformMarker.position.x, transformMarker.position.y, transformMarker.position.z); 191 | var transform = new ROSLIB.Transform({ 192 | translation : translation, 193 | orientation : transformMarker.quaternion 194 | }); 195 | 196 | // apply that transform too 197 | newPose.applyTransform(transform); 198 | 199 | markerHelper.setPose(newPose); 200 | 201 | markerHelper.updateMatrixWorld(); 202 | // we only need to set the pose once - at least, this is what RViz seems to be doing, might change in the future 203 | localTfClient.unsubscribe(markerMsg.header.frame_id); 204 | } 205 | 206 | // add the marker 207 | that.add(markerHelper); 208 | }; 209 | 210 | // If the marker is not relative to the parent marker's position, 211 | // ask the *local* TFClient for the transformation from the 212 | // InteractiveMarker frame to the sub-Marker frame 213 | if (markerMsg.header.frame_id !== '') { 214 | localTfClient.subscribe(markerMsg.header.frame_id, addMarker); 215 | } 216 | // If not, just add the marker without changing its pose 217 | else { 218 | addMarker(null); 219 | } 220 | }); 221 | }; 222 | ROS3D.InteractiveMarkerControl.prototype.__proto__ = THREE.Object3D.prototype; 223 | 224 | ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld = function (force) { 225 | var that = this; 226 | var message = this.message; 227 | switch (message.orientation_mode) { 228 | case ROS3D.INTERACTIVE_MARKER_INHERIT: 229 | ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force); 230 | that.currentControlOri.copy(that.quaternion); 231 | that.currentControlOri.normalize(); 232 | break; 233 | case ROS3D.INTERACTIVE_MARKER_FIXED: 234 | that.quaternion.copy(that.parent.quaternion.clone().inverse()); 235 | that.updateMatrix(); 236 | that.matrixWorldNeedsUpdate = true; 237 | ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force); 238 | that.currentControlOri.copy(that.quaternion); 239 | break; 240 | case ROS3D.INTERACTIVE_MARKER_VIEW_FACING: 241 | that.camera.updateMatrixWorld(); 242 | var cameraRot = new THREE.Matrix4().extractRotation(that.camera.matrixWorld); 243 | 244 | var ros2Gl = new THREE.Matrix4(); 245 | var r90 = Math.PI * 0.5; 246 | var rv = new THREE.Euler(-r90, 0, r90); 247 | ros2Gl.makeRotationFromEuler(rv); 248 | 249 | var worldToLocal = new THREE.Matrix4(); 250 | worldToLocal.getInverse(that.parent.matrixWorld); 251 | 252 | cameraRot.multiplyMatrices(cameraRot, ros2Gl); 253 | cameraRot.multiplyMatrices(worldToLocal, cameraRot); 254 | 255 | that.currentControlOri.setFromRotationMatrix(cameraRot); 256 | 257 | // check the orientation 258 | if (!message.independent_marker_orientation) { 259 | that.quaternion.copy(that.currentControlOri); 260 | that.updateMatrix(); 261 | that.matrixWorldNeedsUpdate = true; 262 | } 263 | ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force); 264 | break; 265 | default: 266 | console.error('Unkown orientation mode: ' + message.orientation_mode); 267 | break; 268 | } 269 | }; 270 | -------------------------------------------------------------------------------- /src/interactivemarkers/InteractiveMarkerHandle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * Handle with signals for a single interactive marker. 8 | * 9 | * Emits the following events: 10 | * 11 | * * 'pose' - emitted when a new pose comes from the server 12 | * 13 | * @constructor 14 | * @param options - object with following keys: 15 | * 16 | * * message - the interactive marker message 17 | * * feedbackTopic - the ROSLIB.Topic associated with the feedback 18 | * * tfClient - a handle to the TF client to use 19 | * * menuFontSize (optional) - the menu font size 20 | */ 21 | ROS3D.InteractiveMarkerHandle = function(options) { 22 | options = options || {}; 23 | this.message = options.message; 24 | this.feedbackTopic = options.feedbackTopic; 25 | this.tfClient = options.tfClient; 26 | this.menuFontSize = options.menuFontSize || '0.8em'; 27 | this.name = this.message.name; 28 | this.header = this.message.header; 29 | this.controls = this.message.controls; 30 | this.menuEntries = this.message.menu_entries; 31 | this.dragging = false; 32 | this.timeoutHandle = null; 33 | this.tfTransform = new ROSLIB.Transform(); 34 | this.pose = new ROSLIB.Pose(); 35 | 36 | this.setPoseFromClientBound = this.setPoseFromClient.bind(this); 37 | this.onMouseDownBound = this.onMouseDown.bind(this); 38 | this.onMouseUpBound = this.onMouseUp.bind(this); 39 | this.onButtonClickBound = this.onButtonClick.bind(this); 40 | this.onMenuSelectBound = this.onMenuSelect.bind(this); 41 | 42 | // start by setting the pose 43 | this.setPoseFromServer(this.message.pose); 44 | this.tfUpdateBound = this.tfUpdate.bind(this); 45 | }; 46 | ROS3D.InteractiveMarkerHandle.prototype.__proto__ = EventEmitter3.prototype; 47 | 48 | /** 49 | * Subscribe to the TF associated with this interactive marker. 50 | */ 51 | ROS3D.InteractiveMarkerHandle.prototype.subscribeTf = function() { 52 | // subscribe to tf updates if frame-fixed 53 | if (this.message.header.stamp.secs === 0.0 && this.message.header.stamp.nsecs === 0.0) { 54 | this.tfClient.subscribe(this.message.header.frame_id, this.tfUpdateBound); 55 | } 56 | }; 57 | 58 | ROS3D.InteractiveMarkerHandle.prototype.unsubscribeTf = function() { 59 | this.tfClient.unsubscribe(this.message.header.frame_id, this.tfUpdateBound); 60 | }; 61 | 62 | /** 63 | * Emit the new pose that has come from the server. 64 | */ 65 | ROS3D.InteractiveMarkerHandle.prototype.emitServerPoseUpdate = function() { 66 | var poseTransformed = new ROSLIB.Pose(this.pose); 67 | poseTransformed.applyTransform(this.tfTransform); 68 | this.emit('pose', poseTransformed); 69 | }; 70 | 71 | /** 72 | * Update the pose based on the pose given by the server. 73 | * 74 | * @param poseMsg - the pose given by the server 75 | */ 76 | ROS3D.InteractiveMarkerHandle.prototype.setPoseFromServer = function(poseMsg) { 77 | this.pose = new ROSLIB.Pose(poseMsg); 78 | this.emitServerPoseUpdate(); 79 | }; 80 | 81 | /** 82 | * Update the pose based on the TF given by the server. 83 | * 84 | * @param transformMsg - the TF given by the server 85 | */ 86 | ROS3D.InteractiveMarkerHandle.prototype.tfUpdate = function(transformMsg) { 87 | this.tfTransform = new ROSLIB.Transform(transformMsg); 88 | this.emitServerPoseUpdate(); 89 | }; 90 | 91 | /** 92 | * Set the pose from the client based on the given event. 93 | * 94 | * @param event - the event to base the change off of 95 | */ 96 | ROS3D.InteractiveMarkerHandle.prototype.setPoseFromClient = function(event) { 97 | // apply the transform 98 | this.pose = new ROSLIB.Pose(event); 99 | var inv = this.tfTransform.clone(); 100 | inv.rotation.invert(); 101 | inv.translation.multiplyQuaternion(inv.rotation); 102 | inv.translation.x *= -1; 103 | inv.translation.y *= -1; 104 | inv.translation.z *= -1; 105 | this.pose.applyTransform(inv); 106 | 107 | // send feedback to the server 108 | this.sendFeedback(ROS3D.INTERACTIVE_MARKER_POSE_UPDATE, undefined, 0, event.controlName); 109 | 110 | // keep sending pose feedback until the mouse goes up 111 | if (this.dragging) { 112 | if (this.timeoutHandle) { 113 | clearTimeout(this.timeoutHandle); 114 | } 115 | this.timeoutHandle = setTimeout(this.setPoseFromClient.bind(this, event), 250); 116 | } 117 | }; 118 | 119 | /** 120 | * Send the button click feedback to the server. 121 | * 122 | * @param event - the event associated with the button click 123 | */ 124 | ROS3D.InteractiveMarkerHandle.prototype.onButtonClick = function(event) { 125 | this.sendFeedback(ROS3D.INTERACTIVE_MARKER_BUTTON_CLICK, event.clickPosition, 0, 126 | event.controlName); 127 | }; 128 | 129 | /** 130 | * Send the mousedown feedback to the server. 131 | * 132 | * @param event - the event associated with the mousedown 133 | */ 134 | ROS3D.InteractiveMarkerHandle.prototype.onMouseDown = function(event) { 135 | this.sendFeedback(ROS3D.INTERACTIVE_MARKER_MOUSE_DOWN, event.clickPosition, 0, event.controlName); 136 | this.dragging = true; 137 | }; 138 | 139 | /** 140 | * Send the mouseup feedback to the server. 141 | * 142 | * @param event - the event associated with the mouseup 143 | */ 144 | ROS3D.InteractiveMarkerHandle.prototype.onMouseUp = function(event) { 145 | this.sendFeedback(ROS3D.INTERACTIVE_MARKER_MOUSE_UP, event.clickPosition, 0, event.controlName); 146 | this.dragging = false; 147 | if (this.timeoutHandle) { 148 | clearTimeout(this.timeoutHandle); 149 | } 150 | }; 151 | 152 | /** 153 | * Send the menu select feedback to the server. 154 | * 155 | * @param event - the event associated with the menu select 156 | */ 157 | ROS3D.InteractiveMarkerHandle.prototype.onMenuSelect = function(event) { 158 | this.sendFeedback(ROS3D.INTERACTIVE_MARKER_MENU_SELECT, undefined, event.id, event.controlName); 159 | }; 160 | 161 | /** 162 | * Send feedback to the interactive marker server. 163 | * 164 | * @param eventType - the type of event that happened 165 | * @param clickPosition (optional) - the position in ROS space the click happened 166 | * @param menuEntryID (optional) - the menu entry ID that is associated 167 | * @param controlName - the name of the control 168 | */ 169 | ROS3D.InteractiveMarkerHandle.prototype.sendFeedback = function(eventType, clickPosition, 170 | menuEntryID, controlName) { 171 | 172 | // check for the click position 173 | var mousePointValid = clickPosition !== undefined; 174 | clickPosition = clickPosition || { 175 | x : 0, 176 | y : 0, 177 | z : 0 178 | }; 179 | 180 | var feedback = { 181 | header : this.header, 182 | client_id : this.clientID, 183 | marker_name : this.name, 184 | control_name : controlName, 185 | event_type : eventType, 186 | pose : this.pose, 187 | mouse_point : clickPosition, 188 | mouse_point_valid : mousePointValid, 189 | menu_entry_id : menuEntryID 190 | }; 191 | this.feedbackTopic.publish(feedback); 192 | }; 193 | -------------------------------------------------------------------------------- /src/interactivemarkers/InteractiveMarkerMenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * A menu for an interactive marker. This will be overlayed on the canvas. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * menuEntries - the menu entries to add 13 | * * className (optional) - a custom CSS class for the menu div 14 | * * entryClassName (optional) - a custom CSS class for the menu entry 15 | * * overlayClassName (optional) - a custom CSS class for the menu overlay 16 | * * menuFontSize (optional) - the menu font size 17 | */ 18 | ROS3D.InteractiveMarkerMenu = function(options) { 19 | THREE.EventDispatcher.call(this); 20 | var that = this; 21 | options = options || {}; 22 | var menuEntries = options.menuEntries; 23 | var className = options.className || 'default-interactive-marker-menu'; 24 | var entryClassName = options.entryClassName || 'default-interactive-marker-menu-entry'; 25 | var overlayClassName = options.overlayClassName || 'default-interactive-marker-overlay'; 26 | var menuFontSize = options.menuFontSize || '0.8em'; 27 | 28 | // holds the menu tree 29 | var allMenus = []; 30 | allMenus[0] = { 31 | children : [] 32 | }; 33 | 34 | 35 | // create the CSS for this marker if it has not been created 36 | if (document.getElementById('default-interactive-marker-menu-css') === null) { 37 | var style = document.createElement('style'); 38 | style.id = 'default-interactive-marker-menu-css'; 39 | style.type = 'text/css'; 40 | style.innerHTML = '.default-interactive-marker-menu {' + 'background-color: #444444;' 41 | + 'border: 1px solid #888888;' + 'border: 1px solid #888888;' + 'padding: 0px 0px 0px 0px;' 42 | + 'color: #FFFFFF;' + 'font-family: sans-serif;' + 'font-size: ' + menuFontSize +';' + 'z-index: 1002;' 43 | + '}' + '.default-interactive-marker-menu ul {' + 'padding: 0px 0px 5px 0px;' 44 | + 'margin: 0px;' + 'list-style-type: none;' + '}' 45 | + '.default-interactive-marker-menu ul li div {' + '-webkit-touch-callout: none;' 46 | + '-webkit-user-select: none;' + '-khtml-user-select: none;' + '-moz-user-select: none;' 47 | + '-ms-user-select: none;' + 'user-select: none;' + 'cursor: default;' 48 | + 'padding: 3px 10px 3px 10px;' + '}' + '.default-interactive-marker-menu-entry:hover {' 49 | + ' background-color: #666666;' + ' cursor: pointer;' + '}' 50 | + '.default-interactive-marker-menu ul ul {' + ' font-style: italic;' 51 | + ' padding-left: 10px;' + '}' + '.default-interactive-marker-overlay {' 52 | + ' position: absolute;' + ' top: 0%;' + ' left: 0%;' + ' width: 100%;' 53 | + ' height: 100%;' + ' background-color: black;' + ' z-index: 1001;' 54 | + ' -moz-opacity: 0.0;' + ' opacity: .0;' + ' filter: alpha(opacity = 0);' + '}'; 55 | document.getElementsByTagName('head')[0].appendChild(style); 56 | } 57 | 58 | // place the menu in a div 59 | this.menuDomElem = document.createElement('div'); 60 | this.menuDomElem.style.position = 'absolute'; 61 | this.menuDomElem.className = className; 62 | this.menuDomElem.addEventListener('contextmenu', function(event) { 63 | event.preventDefault(); 64 | }); 65 | 66 | // create the overlay DOM 67 | this.overlayDomElem = document.createElement('div'); 68 | this.overlayDomElem.className = overlayClassName; 69 | 70 | this.hideListener = this.hide.bind(this); 71 | this.overlayDomElem.addEventListener('contextmenu', this.hideListener); 72 | this.overlayDomElem.addEventListener('click', this.hideListener); 73 | this.overlayDomElem.addEventListener('touchstart', this.hideListener); 74 | 75 | // parse all entries and link children to parents 76 | var i, entry, id; 77 | for ( i = 0; i < menuEntries.length; i++) { 78 | entry = menuEntries[i]; 79 | id = entry.id; 80 | allMenus[id] = { 81 | title : entry.title, 82 | id : id, 83 | children : [] 84 | }; 85 | } 86 | for ( i = 0; i < menuEntries.length; i++) { 87 | entry = menuEntries[i]; 88 | id = entry.id; 89 | var menu = allMenus[id]; 90 | var parent = allMenus[entry.parent_id]; 91 | parent.children.push(menu); 92 | } 93 | 94 | function emitMenuSelect(menuEntry, domEvent) { 95 | this.dispatchEvent({ 96 | type : 'menu-select', 97 | domEvent : domEvent, 98 | id : menuEntry.id, 99 | controlName : this.controlName 100 | }); 101 | this.hide(domEvent); 102 | } 103 | 104 | /** 105 | * Create the HTML UL element for the menu and link it to the parent. 106 | * 107 | * @param parentDomElem - the parent DOM element 108 | * @param parentMenu - the parent menu 109 | */ 110 | function makeUl(parentDomElem, parentMenu) { 111 | 112 | var ulElem = document.createElement('ul'); 113 | parentDomElem.appendChild(ulElem); 114 | 115 | var children = parentMenu.children; 116 | 117 | for ( var i = 0; i < children.length; i++) { 118 | var liElem = document.createElement('li'); 119 | var divElem = document.createElement('div'); 120 | divElem.appendChild(document.createTextNode(children[i].title)); 121 | ulElem.appendChild(liElem); 122 | liElem.appendChild(divElem); 123 | 124 | if (children[i].children.length > 0) { 125 | makeUl(liElem, children[i]); 126 | divElem.addEventListener('click', that.hide.bind(that)); 127 | divElem.addEventListener('touchstart', that.hide.bind(that)); 128 | } else { 129 | divElem.addEventListener('click', emitMenuSelect.bind(that, children[i])); 130 | divElem.addEventListener('touchstart', emitMenuSelect.bind(that, children[i])); 131 | divElem.className = 'default-interactive-marker-menu-entry'; 132 | } 133 | } 134 | 135 | } 136 | 137 | // construct DOM element 138 | makeUl(this.menuDomElem, allMenus[0]); 139 | }; 140 | 141 | /** 142 | * Shoe the menu DOM element. 143 | * 144 | * @param control - the control for the menu 145 | * @param event - the event that caused this 146 | */ 147 | ROS3D.InteractiveMarkerMenu.prototype.show = function(control, event) { 148 | if (event && event.preventDefault) { 149 | event.preventDefault(); 150 | } 151 | 152 | this.controlName = control.name; 153 | 154 | // position it on the click 155 | if (event.domEvent.changedTouches !== undefined) { 156 | // touch click 157 | this.menuDomElem.style.left = event.domEvent.changedTouches[0].pageX + 'px'; 158 | this.menuDomElem.style.top = event.domEvent.changedTouches[0].pageY + 'px'; 159 | } else { 160 | // mouse click 161 | this.menuDomElem.style.left = event.domEvent.clientX + 'px'; 162 | this.menuDomElem.style.top = event.domEvent.clientY + 'px'; 163 | } 164 | document.body.appendChild(this.overlayDomElem); 165 | document.body.appendChild(this.menuDomElem); 166 | }; 167 | 168 | /** 169 | * Hide the menu DOM element. 170 | * 171 | * @param event (optional) - the event that caused this 172 | */ 173 | ROS3D.InteractiveMarkerMenu.prototype.hide = function(event) { 174 | if (event && event.preventDefault) { 175 | event.preventDefault(); 176 | } 177 | 178 | document.body.removeChild(this.overlayDomElem); 179 | document.body.removeChild(this.menuDomElem); 180 | }; 181 | 182 | Object.assign(ROS3D.InteractiveMarkerMenu.prototype, THREE.EventDispatcher.prototype); 183 | -------------------------------------------------------------------------------- /src/markers/MarkerArrayClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Russell Toris - rctoris@wpi.edu 4 | * @author Nils Berg - berg.nils@gmail.com 5 | */ 6 | 7 | /** 8 | * A MarkerArray client that listens to a given topic. 9 | * 10 | * Emits the following events: 11 | * 12 | * * 'change' - there was an update or change in the MarkerArray 13 | * 14 | * @constructor 15 | * @param options - object with following keys: 16 | * 17 | * * ros - the ROSLIB.Ros connection handle 18 | * * topic - the marker topic to listen to 19 | * * tfClient - the TF client handle to use 20 | * * rootObject (optional) - the root object to add the markers to 21 | * * path (optional) - the base path to any meshes that will be loaded 22 | */ 23 | ROS3D.MarkerArrayClient = function(options) { 24 | options = options || {}; 25 | this.ros = options.ros; 26 | this.topicName = options.topic; 27 | this.tfClient = options.tfClient; 28 | this.rootObject = options.rootObject || new THREE.Object3D(); 29 | this.path = options.path || '/'; 30 | 31 | // Markers that are displayed (Map ns+id--Marker) 32 | this.markers = {}; 33 | this.rosTopic = undefined; 34 | 35 | this.processMessageBound = this.processMessage.bind(this); 36 | this.subscribe(); 37 | }; 38 | ROS3D.MarkerArrayClient.prototype.__proto__ = EventEmitter3.prototype; 39 | 40 | ROS3D.MarkerArrayClient.prototype.subscribe = function(){ 41 | this.unsubscribe(); 42 | 43 | // subscribe to MarkerArray topic 44 | this.rosTopic = new ROSLIB.Topic({ 45 | ros : this.ros, 46 | name : this.topicName, 47 | messageType : 'visualization_msgs/MarkerArray', 48 | compression : 'png' 49 | }); 50 | this.rosTopic.subscribe(this.processMessageBound); 51 | }; 52 | 53 | ROS3D.MarkerArrayClient.prototype.processMessage = function(arrayMessage){ 54 | arrayMessage.markers.forEach(function(message) { 55 | var key = message.ns + message.id; 56 | if(message.action === 0) { 57 | var updated = false; 58 | if(key in this.markers) { // "MODIFY" 59 | updated = this.markers[key].children[0].update(message); 60 | if(!updated) { // "REMOVE" 61 | this.removeMarker(key); 62 | } 63 | } 64 | if(!updated) { // "ADD" 65 | var newMarker = new ROS3D.Marker({ 66 | message : message, 67 | path : this.path, 68 | }); 69 | this.markers[key] = new ROS3D.SceneNode({ 70 | frameID : message.header.frame_id, 71 | tfClient : this.tfClient, 72 | object : newMarker 73 | }); 74 | this.rootObject.add(this.markers[key]); 75 | } 76 | } 77 | else if(message.action === 1) { // "DEPRECATED" 78 | console.warn('Received marker message with deprecated action identifier "1"'); 79 | } 80 | else if(message.action === 2) { // "DELETE" 81 | this.removeMarker(key); 82 | } 83 | else if(message.action === 3) { // "DELETE ALL" 84 | for (var m in this.markers){ 85 | this.removeMarker(m); 86 | } 87 | this.markers = {}; 88 | } 89 | else { 90 | console.warn('Received marker message with unknown action identifier "'+message.action+'"'); 91 | } 92 | }.bind(this)); 93 | 94 | this.emit('change'); 95 | }; 96 | 97 | ROS3D.MarkerArrayClient.prototype.unsubscribe = function(){ 98 | if(this.rosTopic){ 99 | this.rosTopic.unsubscribe(this.processMessageBound); 100 | } 101 | }; 102 | 103 | ROS3D.MarkerArrayClient.prototype.removeMarker = function(key) { 104 | var oldNode = this.markers[key]; 105 | if(!oldNode) { 106 | return; 107 | } 108 | oldNode.unsubscribeTf(); 109 | this.rootObject.remove(oldNode); 110 | oldNode.children.forEach(child => { 111 | child.dispose(); 112 | }); 113 | delete(this.markers[key]); 114 | }; 115 | -------------------------------------------------------------------------------- /src/markers/MarkerClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Russell Toris - rctoris@wpi.edu 4 | */ 5 | 6 | /** 7 | * A marker client that listens to a given marker topic. 8 | * 9 | * Emits the following events: 10 | * 11 | * * 'change' - there was an update or change in the marker 12 | * 13 | * @constructor 14 | * @param options - object with following keys: 15 | * 16 | * * ros - the ROSLIB.Ros connection handle 17 | * * topic - the marker topic to listen to 18 | * * tfClient - the TF client handle to use 19 | * * rootObject (optional) - the root object to add this marker to 20 | * * path (optional) - the base path to any meshes that will be loaded 21 | * * lifetime - the lifetime of marker 22 | */ 23 | ROS3D.MarkerClient = function(options) { 24 | options = options || {}; 25 | this.ros = options.ros; 26 | this.topicName = options.topic; 27 | this.tfClient = options.tfClient; 28 | this.rootObject = options.rootObject || new THREE.Object3D(); 29 | this.path = options.path || '/'; 30 | this.lifetime = options.lifetime || 0; 31 | 32 | // Markers that are displayed (Map ns+id--Marker) 33 | this.markers = {}; 34 | this.rosTopic = undefined; 35 | this.updatedTime = {}; 36 | 37 | this.processMessageBound = this.processMessage.bind(this); 38 | this.subscribe(); 39 | }; 40 | ROS3D.MarkerClient.prototype.__proto__ = EventEmitter3.prototype; 41 | 42 | ROS3D.MarkerClient.prototype.unsubscribe = function(){ 43 | if(this.rosTopic){ 44 | this.rosTopic.unsubscribe(this.processMessageBound); 45 | } 46 | }; 47 | 48 | ROS3D.MarkerClient.prototype.checkTime = function(name){ 49 | var curTime = new Date().getTime(); 50 | if (curTime - this.updatedTime[name] > this.lifetime) { 51 | this.removeMarker(name); 52 | this.emit('change'); 53 | } else { 54 | var that = this; 55 | setTimeout(function() {that.checkTime(name);}, 56 | 100); 57 | } 58 | }; 59 | 60 | ROS3D.MarkerClient.prototype.subscribe = function(){ 61 | this.unsubscribe(); 62 | 63 | // subscribe to the topic 64 | this.rosTopic = new ROSLIB.Topic({ 65 | ros : this.ros, 66 | name : this.topicName, 67 | messageType : 'visualization_msgs/Marker', 68 | compression : 'png' 69 | }); 70 | this.rosTopic.subscribe(this.processMessageBound); 71 | }; 72 | 73 | ROS3D.MarkerClient.prototype.processMessage = function(message){ 74 | // remove old marker from Three.Object3D children buffer 75 | var key = message.ns + message.id; 76 | var oldNode = this.markers[key]; 77 | this.updatedTime[key] = new Date().getTime(); 78 | if (oldNode) { 79 | this.removeMarker(key); 80 | 81 | } else if (this.lifetime) { 82 | this.checkTime(message.ns + message.id); 83 | } 84 | 85 | if (message.action === 0) { // "ADD" or "MODIFY" 86 | var newMarker = new ROS3D.Marker({ 87 | message : message, 88 | path : this.path, 89 | }); 90 | 91 | this.markers[key] = new ROS3D.SceneNode({ 92 | frameID : message.header.frame_id, 93 | tfClient : this.tfClient, 94 | object : newMarker 95 | }); 96 | this.rootObject.add(this.markers[key]); 97 | } 98 | 99 | this.emit('change'); 100 | }; 101 | 102 | ROS3D.MarkerClient.prototype.removeMarker = function(key) { 103 | var oldNode = this.markers[key]; 104 | if(!oldNode) { 105 | return; 106 | } 107 | oldNode.unsubscribeTf(); 108 | this.rootObject.remove(oldNode); 109 | oldNode.children.forEach(child => { 110 | child.dispose(); 111 | }); 112 | delete(this.markers[key]); 113 | }; 114 | -------------------------------------------------------------------------------- /src/models/Arrow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * A Arrow is a THREE object that can be used to display an arrow model. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * origin (optional) - the origin of the arrow 13 | * * direction (optional) - the direction vector of the arrow 14 | * * length (optional) - the length of the arrow 15 | * * headLength (optional) - the head length of the arrow 16 | * * shaftDiameter (optional) - the shaft diameter of the arrow 17 | * * headDiameter (optional) - the head diameter of the arrow 18 | * * material (optional) - the material to use for this arrow 19 | */ 20 | ROS3D.Arrow = function(options) { 21 | options = options || {}; 22 | var origin = options.origin || new THREE.Vector3(0, 0, 0); 23 | var direction = options.direction || new THREE.Vector3(1, 0, 0); 24 | var length = options.length || 1; 25 | var headLength = options.headLength || 0.2; 26 | var shaftDiameter = options.shaftDiameter || 0.05; 27 | var headDiameter = options.headDiameter || 0.1; 28 | var material = options.material || new THREE.MeshBasicMaterial(); 29 | 30 | var shaftLength = length - headLength; 31 | 32 | // create and merge geometry 33 | var geometry = new THREE.CylinderGeometry(shaftDiameter * 0.5, shaftDiameter * 0.5, shaftLength, 34 | 12, 1); 35 | var m = new THREE.Matrix4(); 36 | m.setPosition(new THREE.Vector3(0, shaftLength * 0.5, 0)); 37 | geometry.applyMatrix(m); 38 | 39 | // create the head 40 | var coneGeometry = new THREE.CylinderGeometry(0, headDiameter * 0.5, headLength, 12, 1); 41 | m.setPosition(new THREE.Vector3(0, shaftLength + (headLength * 0.5), 0)); 42 | coneGeometry.applyMatrix(m); 43 | 44 | // put the arrow together 45 | geometry.merge(coneGeometry); 46 | 47 | THREE.Mesh.call(this, geometry, material); 48 | 49 | this.position.copy(origin); 50 | this.setDirection(direction); 51 | }; 52 | ROS3D.Arrow.prototype.__proto__ = THREE.Mesh.prototype; 53 | 54 | /** 55 | * Set the direction of this arrow to that of the given vector. 56 | * 57 | * @param direction - the direction to set this arrow 58 | */ 59 | ROS3D.Arrow.prototype.setDirection = function(direction) { 60 | var axis = new THREE.Vector3(); 61 | if(direction.x === 0 && direction.z === 0){ 62 | axis.set(1, 0, 0); 63 | } else { 64 | axis.set(0, 1, 0).cross(direction); 65 | } 66 | var radians = Math.acos(new THREE.Vector3(0, 1, 0).dot(direction.clone().normalize())); 67 | this.matrix = new THREE.Matrix4().makeRotationAxis(axis.normalize(), radians); 68 | this.rotation.setFromRotationMatrix(this.matrix, this.rotation.order); 69 | }; 70 | 71 | /** 72 | * Set this arrow to be the given length. 73 | * 74 | * @param length - the new length of the arrow 75 | */ 76 | ROS3D.Arrow.prototype.setLength = function(length) { 77 | this.scale.set(length, length, length); 78 | }; 79 | 80 | /** 81 | * Set the color of this arrow to the given hex value. 82 | * 83 | * @param hex - the hex value of the color to use 84 | */ 85 | ROS3D.Arrow.prototype.setColor = function(hex) { 86 | this.material.color.setHex(hex); 87 | }; 88 | 89 | /* 90 | * Free memory of elements in this marker. 91 | */ 92 | ROS3D.Arrow.prototype.dispose = function() { 93 | if (this.geometry !== undefined) { 94 | this.geometry.dispose(); 95 | } 96 | if (this.material !== undefined) { 97 | this.material.dispose(); 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /src/models/Arrow2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Jihoon Lee - lee@magazino.eu 4 | */ 5 | 6 | /** 7 | * A Arrow is a THREE object that can be used to display an arrow model using ArrowHelper 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * origin (optional) - the origin of the arrow 13 | * * direction (optional) - the direction vector of the arrow 14 | * * length (optional) - the length of the arrow 15 | * * headLength (optional) - the head length of the arrow 16 | * * shaftDiameter (optional) - the shaft diameter of the arrow 17 | * * headDiameter (optional) - the head diameter of the arrow 18 | * * material (optional) - the material to use for this arrow 19 | */ 20 | ROS3D.Arrow2 = function(options) { 21 | options = options || {}; 22 | var origin = options.origin || new THREE.Vector3(0, 0, 0); 23 | var direction = options.direction || new THREE.Vector3(1, 0, 0); 24 | var length = options.length || 1; 25 | var headLength = options.headLength || 0.2; 26 | var shaftDiameter = options.shaftDiameter || 0.05; 27 | var headDiameter = options.headDiameter || 0.1; 28 | var material = options.material || new THREE.MeshBasicMaterial(); 29 | 30 | THREE.ArrowHelper.call(this, direction, origin, length, 0xff0000); 31 | 32 | }; 33 | 34 | ROS3D.Arrow2.prototype.__proto__ = THREE.ArrowHelper.prototype; 35 | 36 | /* 37 | * Free memory of elements in this object. 38 | */ 39 | ROS3D.Arrow2.prototype.dispose = function() { 40 | if (this.line !== undefined) { 41 | this.line.material.dispose(); 42 | this.line.geometry.dispose(); 43 | } 44 | if (this.cone!== undefined) { 45 | this.cone.material.dispose(); 46 | this.cone.geometry.dispose(); 47 | } 48 | }; 49 | 50 | /* 51 | ROS3D.Arrow2.prototype.setLength = function ( length, headLength, headWidth ) { 52 | if ( headLength === undefined ) { 53 | headLength = 0.2 * length; 54 | } 55 | if ( headWidth === undefined ) { 56 | headWidth = 0.2 * headLength; 57 | } 58 | 59 | this.line.scale.set( 1, Math.max( 0, length), 1 ); 60 | this.line.updateMatrix(); 61 | 62 | this.cone.scale.set( headWidth, headLength, headWidth ); 63 | this.cone.position.y = length; 64 | this.cone.updateMatrix(); 65 | 66 | }; 67 | */ 68 | -------------------------------------------------------------------------------- /src/models/Axes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * An Axes object can be used to display the axis of a particular coordinate frame. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * shaftRadius (optional) - the radius of the shaft to render 13 | * * headRadius (optional) - the radius of the head to render 14 | * * headLength (optional) - the length of the head to render 15 | * * scale (optional) - the scale of the frame (defaults to 1.0) 16 | * * lineType (optional) - the line type for the axes. Supported line types: 17 | * 'dashed' and 'full'. 18 | * * lineDashLength (optional) - the length of the dashes, relative to the length of the axis. 19 | * Maximum value is 1, which means the dash length is 20 | * equal to the length of the axis. Parameter only applies when 21 | * lineType is set to dashed. 22 | */ 23 | ROS3D.Axes = function(options) { 24 | THREE.Object3D.call(this); 25 | var that = this; 26 | options = options || {}; 27 | var shaftRadius = options.shaftRadius || 0.008; 28 | var headRadius = options.headRadius || 0.023; 29 | var headLength = options.headLength || 0.1; 30 | var scaleArg = options.scale || 1.0; 31 | var lineType = options.lineType || 'full'; 32 | var lineDashLength = options.lineDashLength || 0.1; 33 | 34 | 35 | this.scale.set(scaleArg, scaleArg, scaleArg); 36 | 37 | // create the cylinders for the objects 38 | this.lineGeom = new THREE.CylinderGeometry(shaftRadius, shaftRadius, 1.0 - headLength); 39 | this.headGeom = new THREE.CylinderGeometry(0, headRadius, headLength); 40 | 41 | /** 42 | * Adds an axis marker to this axes object. 43 | * 44 | * @param axis - the 3D vector representing the axis to add 45 | */ 46 | function addAxis(axis) { 47 | // set the color of the axis 48 | var color = new THREE.Color(); 49 | color.setRGB(axis.x, axis.y, axis.z); 50 | var material = new THREE.MeshBasicMaterial({ 51 | color : color.getHex() 52 | }); 53 | 54 | // setup the rotation information 55 | var rotAxis = new THREE.Vector3(); 56 | rotAxis.crossVectors(axis, new THREE.Vector3(0, -1, 0)); 57 | var rot = new THREE.Quaternion(); 58 | rot.setFromAxisAngle(rotAxis, 0.5 * Math.PI); 59 | 60 | // create the arrow 61 | var arrow = new THREE.Mesh(that.headGeom, material); 62 | arrow.position.copy(axis); 63 | arrow.position.multiplyScalar(0.95); 64 | arrow.quaternion.copy(rot); 65 | arrow.updateMatrix(); 66 | that.add(arrow); 67 | 68 | // create the line 69 | var line; 70 | if (lineType === 'dashed') { 71 | var l = lineDashLength; 72 | for (var i = 0; (l / 2 + 3 * l * i + l / 2) <= 1; ++i) { 73 | var geom = new THREE.CylinderGeometry(shaftRadius, shaftRadius, l); 74 | line = new THREE.Mesh(geom, material); 75 | line.position.copy(axis); 76 | // Make spacing between dashes equal to 1.5 times the dash length. 77 | line.position.multiplyScalar(l / 2 + 3 * l * i); 78 | line.quaternion.copy(rot); 79 | line.updateMatrix(); 80 | that.add(line); 81 | } 82 | } else if (lineType === 'full') { 83 | line = new THREE.Mesh(that.lineGeom, material); 84 | line.position.copy(axis); 85 | line.position.multiplyScalar(0.45); 86 | line.quaternion.copy(rot); 87 | line.updateMatrix(); 88 | that.add(line); 89 | } else { 90 | console.warn('[ROS3D.Axes]: Unsupported line type. Not drawing any axes.'); 91 | } 92 | } 93 | 94 | // add the three markers to the axes 95 | addAxis(new THREE.Vector3(1, 0, 0)); 96 | addAxis(new THREE.Vector3(0, 1, 0)); 97 | addAxis(new THREE.Vector3(0, 0, 1)); 98 | }; 99 | ROS3D.Axes.prototype.__proto__ = THREE.Object3D.prototype; 100 | -------------------------------------------------------------------------------- /src/models/Grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Russell Toris - rctoris@wpi.edu 4 | */ 5 | 6 | /** 7 | * Create a grid object. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * num_cells (optional) - The number of cells of the grid 13 | * * color (optional) - the line color of the grid, like '#cccccc' 14 | * * lineWidth (optional) - the width of the lines in the grid 15 | * * cellSize (optional) - The length, in meters, of the side of each cell 16 | */ 17 | ROS3D.Grid = function(options) { 18 | options = options || {}; 19 | var num_cells = options.num_cells || 10; 20 | var color = options.color || '#cccccc'; 21 | var lineWidth = options.lineWidth || 1; 22 | var cellSize = options.cellSize || 1; 23 | 24 | THREE.Object3D.call(this); 25 | 26 | var material = new THREE.LineBasicMaterial({ 27 | color: color, 28 | linewidth: lineWidth 29 | }); 30 | 31 | for (var i = 0; i <= num_cells; ++i) { 32 | var edge = cellSize * num_cells / 2; 33 | var position = edge - (i * cellSize); 34 | var geometryH = new THREE.Geometry(); 35 | geometryH.vertices.push( 36 | new THREE.Vector3( -edge, position, 0 ), 37 | new THREE.Vector3( edge, position, 0 ) 38 | ); 39 | var geometryV = new THREE.Geometry(); 40 | geometryV.vertices.push( 41 | new THREE.Vector3( position, -edge, 0 ), 42 | new THREE.Vector3( position, edge, 0 ) 43 | ); 44 | this.add(new THREE.Line(geometryH, material)); 45 | this.add(new THREE.Line(geometryV, material)); 46 | } 47 | }; 48 | 49 | ROS3D.Grid.prototype.__proto__ = THREE.Object3D.prototype; 50 | -------------------------------------------------------------------------------- /src/models/MeshLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Jose Rojas - jrojas@redlinesolutions.co 4 | */ 5 | 6 | /** 7 | * MeshLoader is a singleton factory class for using various helper classes to 8 | * load mesh files of different types. 9 | * 10 | * It consists of one dictionary property 'loaders'. The dictionary keys consist 11 | * of the file extension for each supported loader type. The dictionary values 12 | * are functions used to construct the loader objects. The functions have the 13 | * following parameters: 14 | * 15 | * * meshRes - the MeshResource that will contain the loaded mesh 16 | * * uri - the uri path to the mesh file 17 | * @returns loader object 18 | */ 19 | ROS3D.MeshLoader = { 20 | onError: function(error) { 21 | console.error(error); 22 | }, 23 | loaders: { 24 | 'dae': function(meshRes, uri, options) { 25 | const material = options.material; 26 | const loader = new THREE.ColladaLoader(options.loader); 27 | loader.log = function(message) { 28 | if (meshRes.warnings) { 29 | console.warn(message); 30 | } 31 | }; 32 | loader.load( 33 | uri, 34 | function colladaReady(collada) { 35 | // check for a scale factor in ColladaLoader2 36 | // add a texture to anything that is missing one 37 | if(material !== null) { 38 | collada.scene.traverse(function(child) { 39 | if(child instanceof THREE.Mesh) { 40 | if(child.material === undefined) { 41 | child.material = material; 42 | } 43 | } 44 | }); 45 | } 46 | 47 | meshRes.add(collada.scene); 48 | }, 49 | /*onProgress=*/null, 50 | ROS3D.MeshLoader.onError); 51 | return loader; 52 | }, 53 | 54 | 'obj': function(meshRes, uri, options) { 55 | const material = options.material; 56 | const loader = new THREE.OBJLoader(options.loader); 57 | loader.log = function(message) { 58 | if (meshRes.warnings) { 59 | console.warn(message); 60 | } 61 | }; 62 | 63 | //Reload the mesh again after materials have been loaded 64 | // @todo: this should be improved so that the file doesn't need to be 65 | // reloaded however that would involve more changes within the OBJLoader. 66 | function onMaterialsLoaded(loader, materials) { 67 | loader. 68 | setMaterials(materials). 69 | load( 70 | uri, 71 | function OBJMaterialsReady(obj) { 72 | // add the container group 73 | meshRes.add(obj); 74 | }, 75 | null, 76 | ROS3D.MeshLoader.onError); 77 | } 78 | 79 | loader.load( 80 | uri, 81 | function OBJFileReady(obj) { 82 | 83 | const baseUri = THREE.LoaderUtils.extractUrlBase( uri ); 84 | 85 | if (obj.materialLibraries.length) { 86 | // load the material libraries 87 | const materialUri = obj.materialLibraries[0]; 88 | new THREE.MTLLoader(options.loader).setPath(baseUri).load( 89 | materialUri, 90 | function(materials) { 91 | materials.preload(); 92 | onMaterialsLoaded(loader, materials); 93 | }, 94 | null, 95 | ROS3D.MeshLoader.onError 96 | ); 97 | } else { 98 | // add the container group 99 | meshRes.add(obj); 100 | } 101 | 102 | }, 103 | /*onProgress=*/null, 104 | ROS3D.MeshLoader.onError 105 | ); 106 | return loader; 107 | }, 108 | 109 | 'stl': function(meshRes, uri, options) { 110 | const material = options.material; 111 | const loader = new THREE.STLLoader(options.loader); 112 | { 113 | loader.load(uri, 114 | function ( geometry ) { 115 | geometry.computeFaceNormals(); 116 | var mesh; 117 | if(material !== null) { 118 | mesh = new THREE.Mesh( geometry, material ); 119 | } else { 120 | mesh = new THREE.Mesh( geometry, 121 | new THREE.MeshBasicMaterial( { color: 0x999999 } ) ); 122 | } 123 | meshRes.add(mesh); 124 | }, 125 | /*onProgress=*/null, 126 | ROS3D.MeshLoader.onError); 127 | } 128 | return loader; 129 | } 130 | 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /src/models/MeshResource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Jihoon Lee - jihoonlee.in@gmail.com 4 | * @author Russell Toris - rctoris@wpi.edu 5 | */ 6 | 7 | /** 8 | * A MeshResource is an THREE object that will load from a external mesh file. Currently loads 9 | * Collada files. 10 | * 11 | * @constructor 12 | * @param options - object with following keys: 13 | * 14 | * * path (optional) - the base path to the associated models that will be loaded 15 | * * resource - the resource file name to load 16 | * * material (optional) - the material to use for the object 17 | * * warnings (optional) - if warnings should be printed 18 | */ 19 | ROS3D.MeshResource = function(options) { 20 | THREE.Object3D.call(this); 21 | options = options || {}; 22 | var path = options.path || '/'; 23 | var resource = options.resource; 24 | var material = options.material || null; 25 | this.warnings = options.warnings; 26 | 27 | 28 | // check for a trailing '/' 29 | if (path.substr(path.length - 1) !== '/') { 30 | path += '/'; 31 | } 32 | 33 | var uri = path + resource; 34 | var fileType = uri.substr(-3).toLowerCase(); 35 | 36 | // check the type 37 | var loaderFunc = ROS3D.MeshLoader.loaders[fileType]; 38 | if (loaderFunc) { 39 | loaderFunc(this, uri, options); 40 | } else { 41 | console.warn('Unsupported loader for file type: \'' + fileType + '\''); 42 | } 43 | }; 44 | ROS3D.MeshResource.prototype.__proto__ = THREE.Object3D.prototype; 45 | -------------------------------------------------------------------------------- /src/models/TriangleList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David Gossow - dgossow@willowgarage.com 4 | */ 5 | 6 | /** 7 | * A TriangleList is a THREE object that can be used to display a list of triangles as a geometry. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * material (optional) - the material to use for the object 13 | * * vertices - the array of vertices to use 14 | * * colors - the associated array of colors to use 15 | */ 16 | ROS3D.TriangleList = function(options) { 17 | options = options || {}; 18 | var material = options.material || new THREE.MeshBasicMaterial(); 19 | var vertices = options.vertices; 20 | var colors = options.colors; 21 | 22 | THREE.Object3D.call(this); 23 | 24 | // set the material to be double sided 25 | material.side = THREE.DoubleSide; 26 | 27 | // construct the geometry 28 | var geometry = new THREE.Geometry(); 29 | for (i = 0; i < vertices.length; i++) { 30 | geometry.vertices.push(new THREE.Vector3(vertices[i].x, vertices[i].y, vertices[i].z)); 31 | } 32 | 33 | // set the colors 34 | var i, j; 35 | if (colors.length === vertices.length) { 36 | // use per-vertex color 37 | for (i = 0; i < vertices.length; i += 3) { 38 | var faceVert = new THREE.Face3(i, i + 1, i + 2); 39 | for (j = i * 3; j < i * 3 + 3; i++) { 40 | var color = new THREE.Color(); 41 | color.setRGB(colors[i].r, colors[i].g, colors[i].b); 42 | faceVert.vertexColors.push(color); 43 | } 44 | geometry.faces.push(faceVert); 45 | } 46 | material.vertexColors = THREE.VertexColors; 47 | } else if (colors.length === vertices.length / 3) { 48 | // use per-triangle color 49 | for (i = 0; i < vertices.length; i += 3) { 50 | var faceTri = new THREE.Face3(i, i + 1, i + 2); 51 | faceTri.color.setRGB(colors[i / 3].r, colors[i / 3].g, colors[i / 3].b); 52 | geometry.faces.push(faceTri); 53 | } 54 | material.vertexColors = THREE.FaceColors; 55 | } else { 56 | // use marker color 57 | for (i = 0; i < vertices.length; i += 3) { 58 | var face = new THREE.Face3(i, i + 1, i + 2); 59 | geometry.faces.push(face); 60 | } 61 | } 62 | 63 | geometry.computeBoundingBox(); 64 | geometry.computeBoundingSphere(); 65 | geometry.computeFaceNormals(); 66 | 67 | this.add(new THREE.Mesh(geometry, material)); 68 | }; 69 | ROS3D.TriangleList.prototype.__proto__ = THREE.Object3D.prototype; 70 | 71 | /** 72 | * Set the color of this object to the given hex value. 73 | * 74 | * @param hex - the hex value of the color to set 75 | */ 76 | ROS3D.TriangleList.prototype.setColor = function(hex) { 77 | this.mesh.material.color.setHex(hex); 78 | }; 79 | -------------------------------------------------------------------------------- /src/navigation/ColorOcTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Peter Sari - sari@photoneo.com 4 | */ 5 | 6 | ROS3D.ColorOcTree = function(options) { 7 | ROS3D.OcTree.call(this, options); 8 | this.useOwnColor = (typeof options.palette !== 'undefined') && options.colorMode === ROS3D.OcTreeColorMode.COLOR; 9 | }; 10 | 11 | ROS3D.ColorOcTree.prototype.__proto__ = ROS3D.OcTree.prototype; 12 | 13 | ROS3D.ColorOcTree.prototype._readNodeData = function (dataStream, node) { 14 | node.value = dataStream.readFloat32(); // occupancy 15 | node.color = { 16 | r: dataStream.readUint8(), // red 17 | g: dataStream.readUint8(), // green 18 | b: dataStream.readUint8(), // blue 19 | }; 20 | 21 | }; 22 | 23 | ROS3D.ColorOcTree.prototype._obtainColor = function (node) { 24 | if (!this.useOwnColor) { return ROS3D.OcTree.prototype._obtainColor.call(this, node); } 25 | return node.color; 26 | }; 27 | -------------------------------------------------------------------------------- /src/navigation/OcTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Peter Sari - sari@photoneo.com 4 | */ 5 | 6 | /** 7 | * Specilaization of BaseOcTree 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * * inherited from BaseOctree 12 | * * occupancyThreshold (optional) - threshold value that separates occupied and free voxels from each other. (Default: 0) 13 | * * colorMode (optional) - Coloring mode @see ROS3D.OcTreeColorMode. 14 | * * palette (optional) - Palette used for false-coloring (default: predefined palette) 15 | * * paletteSclae (optional) - Scale of palette to represent a wider range of values (default: 1.) 16 | */ 17 | 18 | ROS3D.OcTree = function(options) { 19 | ROS3D.OcTreeBase.call(this, options); 20 | 21 | this._defaultOccupiedValue = 1.; 22 | this._defaultFreeValue = -1.; 23 | 24 | this.occupancyThreshold = (typeof options.occupancyThreshold !== 'undefined') ? options.occupancyThreshold : 0.0000001; 25 | 26 | this.useFlatColoring = (typeof options.colorMode !== 'undefined') && options.colorMode === ROS3D.OcTreeColorMode.SOLID; 27 | 28 | this.palette = (typeof options.palette !== 'undefined') ? options.palette.map(color => new THREE.Color(color)) : 29 | [ 30 | { r: 0, g: 0, b: 128, }, // dark blue (low) 31 | { r: 0, g: 255, b: 0, }, // green 32 | { r: 255, g: 255, b: 0, }, // yellow (mid) 33 | { r: 255, g: 128, b: 0, }, // orange 34 | { r: 255, g: 0, b: 0, } // red (high) 35 | ]; 36 | 37 | this.paletteScale = (typeof options.paletteScale !== 'undefined') ? options.paletteScale : 1.; 38 | }; 39 | 40 | ROS3D.OcTree.prototype.__proto__ = ROS3D.OcTreeBase.prototype; 41 | 42 | ROS3D.OcTree.prototype._readNodeData = function (dataStream, node) { 43 | node.value = dataStream.readFloat32(); 44 | }; 45 | 46 | ROS3D.OcTree.prototype._obtainColor = function (node) { 47 | if (this.useFlatColoring) { 48 | return this.color; 49 | } 50 | 51 | // Use a simple sigmoid curve to fit values from -inf..inf into 0..1 range 52 | const value = 1. / (1. + Math.exp(-node.value * this.paletteScale)) * this.palette.length; // Normalize 53 | 54 | const intVal = Math.trunc(value); 55 | const fracVal = value - intVal; 56 | 57 | if (intVal < 0) { return this.palette[0]; } 58 | if (intVal >= this.palette.length - 1) { return this.palette[this.palette.length - 1]; } 59 | 60 | // Simple lerp 61 | return { 62 | r: fracVal * this.palette[intVal].r + (1. - fracVal) * this.palette[intVal + 1].r, 63 | g: fracVal * this.palette[intVal].g + (1. - fracVal) * this.palette[intVal + 1].g, 64 | b: fracVal * this.palette[intVal].b + (1. - fracVal) * this.palette[intVal + 1].b, 65 | }; 66 | 67 | }; 68 | 69 | ROS3D.OcTree.prototype._checkOccupied = function (node) { 70 | return node.value >= this.occupancyThreshold; 71 | }; 72 | -------------------------------------------------------------------------------- /src/navigation/OcTreeBaseNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Peter Sari - sari@photoneo.com 4 | */ 5 | 6 | /** 7 | * Base node type that represents one voxel as a node of the tree 8 | */ 9 | 10 | ROS3D.OcTreeBaseNode = function () { 11 | this._children = [null, null, null, null, null, null, null, null]; 12 | this.value = null; 13 | }; 14 | 15 | ROS3D.OcTreeBaseNode.prototype.createChildNodeAt = function (newNode, index) { 16 | this._children[index % 8] = newNode; 17 | }; 18 | 19 | ROS3D.OcTreeBaseNode.prototype.hasChildAt = function (index) { 20 | return this._children[index % 8] !== null; 21 | }; 22 | 23 | ROS3D.OcTreeBaseNode.prototype.getChildAt = function (index) { 24 | return this._children[index % 8]; 25 | }; 26 | 27 | ROS3D.OcTreeBaseNode.prototype.isLeafNode = function () { 28 | for (let i = 0; i < 8; ++i) { 29 | if (this._children[i] !== null) { return false; } 30 | } 31 | return true; 32 | }; 33 | 34 | ROS3D.OcTreeBaseNode.prototype.hasChildren = function () { 35 | for (let i = 0; i < 8; ++i) { 36 | if (this._children[i] !== null) { return true; } 37 | } 38 | return false; 39 | }; 40 | -------------------------------------------------------------------------------- /src/navigation/OcTreeClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Peter Sari - sari@photoneo.com 4 | */ 5 | 6 | /** 7 | * An OcTree client that listens to a given OcTree topic. 8 | * 9 | * Emits the following events: 10 | * 11 | * 'change' - there was an update or change in the marker 12 | * 13 | * @constructor 14 | * @param options - object with following keys: 15 | * 16 | * * ros - the ROSLIB.Ros connection handle 17 | * * topic (optional) - the map topic to listen to 18 | * * continuous (optional) - if the map should be continuously loaded (e.g., for SLAM) 19 | * * tfClient (optional) - the TF client handle to use for a scene node 20 | * * compression (optional) - message compression (default: 'cbor') 21 | * * rootObject (optional) - the root object to add this marker to 22 | * * offsetPose (optional) - offset pose of the mao visualization, e.g. for z-offset (ROSLIB.Pose type) 23 | * * colorMode (optional)) - colorization mode for each voxels @see RORS3D.OcTreeColorMode (default 'color') 24 | * * color (optional) - color of the visualized map (if solid coloring option was set). Can be any value accepted by THREE.Color 25 | * * opacity (optional) - opacity of the visualized grid (0.0 == fully transparent, 1.0 == opaque) 26 | * * palette (optional) - list of RGB colors to be used as palette (THREE.Color) 27 | * * paletteScale (optional) - scale favtor of palette to cover wider range of values. (default: 1) 28 | * * voxelRenderMode (optional)- toggle between rendering modes @see ROS3D.OcTreeVoxelRenderMode. (default `occupid`) 29 | * 30 | */ 31 | 32 | ROS3D.OcTreeClient = function(options) { 33 | EventEmitter3.call(this); 34 | options = options || {}; 35 | this.ros = options.ros; 36 | this.topicName = options.topic || '/octomap'; 37 | this.compression = options.compression || 'cbor'; 38 | this.continuous = options.continuous; 39 | this.tfClient = options.tfClient; 40 | this.rootObject = options.rootObject || new THREE.Object3D(); 41 | this.offsetPose = options.offsetPose || new ROSLIB.Pose(); 42 | 43 | // Options passed to converter 44 | this.options = {}; 45 | 46 | // Append only when it was set, otherwise defaults are provided by the underlying layer 47 | if (typeof options.color !== 'undefined') { this.options['color'] = options.color; } 48 | if (typeof options.opacity !== 'undefined') { this.options['opacity'] = options.opacity; } 49 | if (typeof options.colorMode !== 'undefined') { this.options['colorMode'] = options.colorMode; } 50 | if (typeof options.palette !== 'undefined') { this.options['palette'] = options.palette; } 51 | if (typeof options.paletteScale !== 'undefined') { this.options['paletteScale'] = options.palette; } 52 | if (typeof options.voxelRenderMode !== 'undefined') { this.options['voxelRenderMode'] = options.voxelRenderMode; } 53 | 54 | // current grid that is displayed 55 | this.currentMap = null; 56 | 57 | // subscribe to the topic 58 | this.rosTopic = undefined; 59 | this.processMessageBound = this.processMessage.bind(this); 60 | this.subscribe(); 61 | }; 62 | 63 | ROS3D.OcTreeClient.prototype.__proto__ = EventEmitter3.prototype; 64 | 65 | ROS3D.OcTreeClient.prototype.unsubscribe = function () { 66 | if (this.rosTopic) { 67 | this.rosTopic.unsubscribe(this.processMessageBound); 68 | } 69 | }; 70 | 71 | ROS3D.OcTreeClient.prototype.subscribe = function () { 72 | this.unsubscribe(); 73 | // subscribe to the topic 74 | this.rosTopic = new ROSLIB.Topic({ 75 | ros: this.ros, 76 | name: this.topicName, 77 | messageType: 'octomap_msgs/Octomap', 78 | queue_length: 1, 79 | compression: this.compression 80 | }); 81 | this.rosTopic.subscribe(this.processMessageBound); 82 | }; 83 | 84 | ROS3D.OcTreeClient.prototype.processMessage = function (message) { 85 | // check for an old map 86 | if (this.currentMap) { 87 | if (this.currentMap.tfClient) { 88 | this.currentMap.unsubscribeTf(); 89 | } 90 | } 91 | 92 | this._processMessagePrivate(message); 93 | 94 | if (!this.continuous) { 95 | this.rosTopic.unsubscribe(this.processMessageBound); 96 | } 97 | }; 98 | 99 | 100 | ROS3D.OcTreeClient.prototype._loadOcTree = function (message) { 101 | 102 | return new Promise( 103 | function (resolve, reject) { 104 | 105 | // 1. Create the corresponding octree object from message 106 | const options = Object.assign({ 107 | resolution: message.resolution, 108 | }, this.options); 109 | 110 | let newOcTree = null; 111 | { 112 | if (message.binary) { 113 | newOcTree = new ROS3D.OcTreeBase( 114 | options 115 | ); 116 | newOcTree.readBinary(message.data); 117 | } else { 118 | 119 | const ctorTable = { 120 | 'OcTree': ROS3D.OcTree, 121 | 'ColorOcTree': ROS3D.ColorOcTree, 122 | }; 123 | 124 | if (message.id in ctorTable) { 125 | console.log(message.id, ctorTable); 126 | 127 | newOcTree = new ctorTable[message.id]( 128 | options 129 | ); 130 | 131 | newOcTree.read(message.data); 132 | } 133 | 134 | } 135 | } 136 | 137 | { 138 | newOcTree.buildGeometry(); 139 | } 140 | 141 | resolve(newOcTree); 142 | }.bind(this)); 143 | 144 | }; 145 | 146 | ROS3D.OcTreeClient.prototype._processMessagePrivate = function (message) { 147 | let promise = this._loadOcTree(message); 148 | 149 | promise.then( 150 | // 3. Replace geometry 151 | function (newOcTree) { 152 | // check if we care about the scene 153 | const oldNode = this.sceneNode; 154 | if (this.tfClient) { 155 | this.currentMap = newOcTree; 156 | this.sceneNode = new ROS3D.SceneNode({ 157 | frameID: message.header.frame_id, 158 | tfClient: this.tfClient, 159 | object: newOcTree.object, 160 | pose: this.offsetPose 161 | }); 162 | } else { 163 | this.sceneNode = newOcTree.object; 164 | this.currentMap = newOcTree; 165 | } 166 | 167 | this.rootObject.remove(oldNode); 168 | this.rootObject.add(this.sceneNode); 169 | 170 | this.emit('change'); 171 | }.bind(this) 172 | ); 173 | }; 174 | -------------------------------------------------------------------------------- /src/navigation/OccupancyGrid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Russell Toris - rctoris@wpi.edu 4 | */ 5 | 6 | /** 7 | * An OccupancyGrid can convert a ROS occupancy grid message into a THREE object. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * message - the occupancy grid message 13 | * * color (optional) - color of the visualized grid 14 | * * opacity (optional) - opacity of the visualized grid (0.0 == fully transparent, 1.0 == opaque) 15 | */ 16 | ROS3D.OccupancyGrid = function(options) { 17 | options = options || {}; 18 | var message = options.message; 19 | var opacity = options.opacity || 1.0; 20 | var color = options.color || {r:255,g:255,b:255,a:255}; 21 | 22 | // create the geometry 23 | var info = message.info; 24 | var origin = info.origin; 25 | var width = info.width; 26 | var height = info.height; 27 | var geom = new THREE.PlaneBufferGeometry(width, height); 28 | 29 | // create the color material 30 | var imageData = new Uint8Array(width * height * 4); 31 | var texture = new THREE.DataTexture(imageData, width, height, THREE.RGBAFormat); 32 | texture.flipY = true; 33 | texture.minFilter = THREE.NearestFilter; 34 | texture.magFilter = THREE.NearestFilter; 35 | texture.needsUpdate = true; 36 | 37 | var material = new THREE.MeshBasicMaterial({ 38 | map : texture, 39 | transparent : opacity < 1.0, 40 | opacity : opacity 41 | }); 42 | material.side = THREE.DoubleSide; 43 | 44 | // create the mesh 45 | THREE.Mesh.call(this, geom, material); 46 | // move the map so the corner is at X, Y and correct orientation (informations from message.info) 47 | 48 | // assign options to this for subclasses 49 | Object.assign(this, options); 50 | 51 | this.quaternion.copy(new THREE.Quaternion( 52 | origin.orientation.x, 53 | origin.orientation.y, 54 | origin.orientation.z, 55 | origin.orientation.w 56 | )); 57 | this.position.x = (width * info.resolution) / 2 + origin.position.x; 58 | this.position.y = (height * info.resolution) / 2 + origin.position.y; 59 | this.position.z = origin.position.z; 60 | this.scale.x = info.resolution; 61 | this.scale.y = info.resolution; 62 | 63 | var data = message.data; 64 | // update the texture (after the the super call and this are accessible) 65 | this.color = color; 66 | this.material = material; 67 | this.texture = texture; 68 | 69 | for ( var row = 0; row < height; row++) { 70 | for ( var col = 0; col < width; col++) { 71 | 72 | // determine the index into the map data 73 | var invRow = (height - row - 1); 74 | var mapI = col + (invRow * width); 75 | // determine the value 76 | var val = this.getValue(mapI, invRow, col, data); 77 | 78 | // determine the color 79 | var color = this.getColor(mapI, invRow, col, val); 80 | 81 | // determine the index into the image data array 82 | var i = (col + (row * width)) * 4; 83 | 84 | // copy the color 85 | imageData.set(color, i); 86 | } 87 | } 88 | 89 | texture.needsUpdate = true; 90 | 91 | }; 92 | 93 | ROS3D.OccupancyGrid.prototype.dispose = function() { 94 | this.material.dispose(); 95 | this.texture.dispose(); 96 | }; 97 | 98 | /** 99 | * Returns the value for a given grid cell 100 | * @param {int} index the current index of the cell 101 | * @param {int} row the row of the cell 102 | * @param {int} col the column of the cell 103 | * @param {object} data the data buffer 104 | */ 105 | ROS3D.OccupancyGrid.prototype.getValue = function(index, row, col, data) { 106 | return data[index]; 107 | }; 108 | 109 | /** 110 | * Returns a color value given parameters of the position in the grid; the default implementation 111 | * scales the default color value by the grid value. Subclasses can extend this functionality 112 | * (e.g. lookup a color in a color map). 113 | * @param {int} index the current index of the cell 114 | * @param {int} row the row of the cell 115 | * @param {int} col the column of the cell 116 | * @param {float} value the value of the cell 117 | * @returns r,g,b,a array of values from 0 to 255 representing the color values for each channel 118 | */ 119 | ROS3D.OccupancyGrid.prototype.getColor = function(index, row, col, value) { 120 | return [ 121 | (value * this.color.r) / 255, 122 | (value * this.color.g) / 255, 123 | (value * this.color.b) / 255, 124 | 255 125 | ]; 126 | }; 127 | 128 | ROS3D.OccupancyGrid.prototype.__proto__ = THREE.Mesh.prototype; 129 | -------------------------------------------------------------------------------- /src/navigation/OccupancyGridClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Russell Toris - rctoris@wpi.edu 4 | */ 5 | 6 | /** 7 | * An occupancy grid client that listens to a given map topic. 8 | * 9 | * Emits the following events: 10 | * 11 | * * 'change' - there was an update or change in the marker 12 | * 13 | * @constructor 14 | * @param options - object with following keys: 15 | * 16 | * * ros - the ROSLIB.Ros connection handle 17 | * * topic (optional) - the map topic to listen to 18 | * * continuous (optional) - if the map should be continuously loaded (e.g., for SLAM) 19 | * * tfClient (optional) - the TF client handle to use for a scene node 20 | * * compression (optional) - message compression (default: 'cbor') 21 | * * rootObject (optional) - the root object to add this marker to 22 | * * offsetPose (optional) - offset pose of the grid visualization, e.g. for z-offset (ROSLIB.Pose type) 23 | * * color (optional) - color of the visualized grid 24 | * * opacity (optional) - opacity of the visualized grid (0.0 == fully transparent, 1.0 == opaque) 25 | */ 26 | ROS3D.OccupancyGridClient = function(options) { 27 | EventEmitter3.call(this); 28 | options = options || {}; 29 | this.ros = options.ros; 30 | this.topicName = options.topic || '/map'; 31 | this.compression = options.compression || 'cbor'; 32 | this.continuous = options.continuous; 33 | this.tfClient = options.tfClient; 34 | this.rootObject = options.rootObject || new THREE.Object3D(); 35 | this.offsetPose = options.offsetPose || new ROSLIB.Pose(); 36 | this.color = options.color || {r:255,g:255,b:255}; 37 | this.opacity = options.opacity || 1.0; 38 | 39 | // current grid that is displayed 40 | this.currentGrid = null; 41 | 42 | // subscribe to the topic 43 | this.rosTopic = undefined; 44 | this.processMessageBound = this.processMessage.bind(this); 45 | this.subscribe(); 46 | }; 47 | ROS3D.OccupancyGridClient.prototype.__proto__ = EventEmitter3.prototype; 48 | 49 | ROS3D.OccupancyGridClient.prototype.unsubscribe = function(){ 50 | if(this.rosTopic){ 51 | this.rosTopic.unsubscribe(this.processMessageBound); 52 | } 53 | }; 54 | 55 | ROS3D.OccupancyGridClient.prototype.subscribe = function(){ 56 | this.unsubscribe(); 57 | 58 | // subscribe to the topic 59 | this.rosTopic = new ROSLIB.Topic({ 60 | ros : this.ros, 61 | name : this.topicName, 62 | messageType : 'nav_msgs/OccupancyGrid', 63 | queue_length : 1, 64 | compression : this.compression 65 | }); 66 | this.sceneNode = null; 67 | this.rosTopic.subscribe(this.processMessageBound); 68 | }; 69 | 70 | ROS3D.OccupancyGridClient.prototype.processMessage = function(message){ 71 | // check for an old map 72 | if (this.currentGrid) { 73 | // check if it there is a tf client 74 | if (this.tfClient) { 75 | // grid is of type ROS3D.SceneNode 76 | this.sceneNode.unsubscribeTf(); 77 | this.sceneNode.remove(this.currentGrid); 78 | } else { 79 | this.rootObject.remove(this.currentGrid); 80 | } 81 | this.currentGrid.dispose(); 82 | } 83 | 84 | var newGrid = new ROS3D.OccupancyGrid({ 85 | message : message, 86 | color : this.color, 87 | opacity : this.opacity 88 | }); 89 | 90 | // check if we care about the scene 91 | if (this.tfClient) { 92 | this.currentGrid = newGrid; 93 | if (this.sceneNode === null) { 94 | this.sceneNode = new ROS3D.SceneNode({ 95 | frameID : message.header.frame_id, 96 | tfClient : this.tfClient, 97 | object : newGrid, 98 | pose : this.offsetPose 99 | }); 100 | this.rootObject.add(this.sceneNode); 101 | } else { 102 | this.sceneNode.add(this.currentGrid); 103 | } 104 | } else { 105 | this.sceneNode = this.currentGrid = newGrid; 106 | this.rootObject.add(this.currentGrid); 107 | } 108 | 109 | this.emit('change'); 110 | 111 | // check if we should unsubscribe 112 | if (!this.continuous) { 113 | this.rosTopic.unsubscribe(this.processMessageBound); 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /src/navigation/Odometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David V. Lu!! - davidvlu@gmail.com 4 | */ 5 | 6 | /** 7 | * An Odometry client 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * ros - the ROSLIB.Ros connection handle 13 | * * topic - the marker topic to listen to 14 | * * tfClient - the TF client handle to use 15 | * * rootObject (optional) - the root object to add this marker to 16 | * * keep (optional) - number of markers to keep around (default: 1) 17 | * * color (optional) - color for line (default: 0xcc00ff) 18 | * * length (optional) - the length of the arrow (default: 1.0) 19 | * * headLength (optional) - the head length of the arrow (default: 0.2) 20 | * * shaftDiameter (optional) - the shaft diameter of the arrow (default: 0.05) 21 | * * headDiameter (optional) - the head diameter of the arrow (default: 0.1) 22 | */ 23 | ROS3D.Odometry = function(options) { 24 | THREE.Object3D.call(this); 25 | this.options = options || {}; 26 | this.ros = options.ros; 27 | this.topicName = options.topic || '/particlecloud'; 28 | this.tfClient = options.tfClient; 29 | this.color = options.color || 0xcc00ff; 30 | this.length = options.length || 1.0; 31 | this.rootObject = options.rootObject || new THREE.Object3D(); 32 | this.keep = options.keep || 1; 33 | 34 | this.sns = []; 35 | 36 | this.rosTopic = undefined; 37 | this.processMessageBound = this.processMessage.bind(this); 38 | this.subscribe(); 39 | }; 40 | ROS3D.Odometry.prototype.__proto__ = THREE.Object3D.prototype; 41 | 42 | 43 | ROS3D.Odometry.prototype.unsubscribe = function(){ 44 | if(this.rosTopic){ 45 | this.rosTopic.unsubscribe(this.processMessageBound); 46 | } 47 | }; 48 | 49 | ROS3D.Odometry.prototype.subscribe = function(){ 50 | this.unsubscribe(); 51 | 52 | // subscribe to the topic 53 | this.rosTopic = new ROSLIB.Topic({ 54 | ros : this.ros, 55 | name : this.topicName, 56 | queue_length : 1, 57 | messageType : 'nav_msgs/Odometry' 58 | }); 59 | this.rosTopic.subscribe(this.processMessageBound); 60 | }; 61 | 62 | ROS3D.Odometry.prototype.processMessage = function(message){ 63 | if(this.sns.length >= this.keep) { 64 | this.sns[0].unsubscribeTf(); 65 | this.rootObject.remove(this.sns[0]); 66 | this.sns.shift(); 67 | } 68 | 69 | this.options.origin = new THREE.Vector3( message.pose.pose.position.x, message.pose.pose.position.y, 70 | message.pose.pose.position.z); 71 | 72 | var rot = new THREE.Quaternion(message.pose.pose.orientation.x, message.pose.pose.orientation.y, 73 | message.pose.pose.orientation.z, message.pose.pose.orientation.w); 74 | this.options.direction = new THREE.Vector3(1,0,0); 75 | this.options.direction.applyQuaternion(rot); 76 | this.options.material = new THREE.MeshBasicMaterial({color: this.color}); 77 | var arrow = new ROS3D.Arrow(this.options); 78 | 79 | this.sns.push(new ROS3D.SceneNode({ 80 | frameID : message.header.frame_id, 81 | tfClient : this.tfClient, 82 | object : arrow 83 | })); 84 | 85 | this.rootObject.add(this.sns[ this.sns.length - 1]); 86 | }; 87 | -------------------------------------------------------------------------------- /src/navigation/Path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David V. Lu!! - davidvlu@gmail.com 4 | */ 5 | 6 | /** 7 | * A Path client that listens to a given topic and displays a line connecting the poses. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * ros - the ROSLIB.Ros connection handle 13 | * * topic - the marker topic to listen to 14 | * * tfClient - the TF client handle to use 15 | * * rootObject (optional) - the root object to add this marker to 16 | * * color (optional) - color for line (default: 0xcc00ff) 17 | */ 18 | ROS3D.Path = function(options) { 19 | THREE.Object3D.call(this); 20 | options = options || {}; 21 | this.ros = options.ros; 22 | this.topicName = options.topic || '/path'; 23 | this.tfClient = options.tfClient; 24 | this.color = options.color || 0xcc00ff; 25 | this.rootObject = options.rootObject || new THREE.Object3D(); 26 | 27 | this.sn = null; 28 | this.line = null; 29 | 30 | this.rosTopic = undefined; 31 | this.processMessageBound = this.processMessage.bind(this); 32 | this.subscribe(); 33 | }; 34 | ROS3D.Path.prototype.__proto__ = THREE.Object3D.prototype; 35 | 36 | 37 | ROS3D.Path.prototype.unsubscribe = function(){ 38 | if(this.rosTopic){ 39 | this.rosTopic.unsubscribe(this.processMessageBound); 40 | } 41 | }; 42 | 43 | ROS3D.Path.prototype.subscribe = function(){ 44 | this.unsubscribe(); 45 | 46 | // subscribe to the topic 47 | this.rosTopic = new ROSLIB.Topic({ 48 | ros : this.ros, 49 | name : this.topicName, 50 | queue_length : 1, 51 | messageType : 'nav_msgs/Path' 52 | }); 53 | this.rosTopic.subscribe(this.processMessageBound); 54 | }; 55 | 56 | ROS3D.Path.prototype.processMessage = function(message){ 57 | if(this.sn!==null){ 58 | this.sn.unsubscribeTf(); 59 | this.rootObject.remove(this.sn); 60 | } 61 | 62 | var lineGeometry = new THREE.Geometry(); 63 | for(var i=0; i= message.range_min && range <= message.range_max){ 64 | var angle = message.angle_min + i * message.angle_increment; 65 | this.points.positions.array[j++] = range * Math.cos(angle); 66 | this.points.positions.array[j++] = range * Math.sin(angle); 67 | this.points.positions.array[j++] = 0.0; 68 | } 69 | } 70 | this.points.update(j/3); 71 | }; 72 | -------------------------------------------------------------------------------- /src/sensors/NavSatFix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Mathieu Bredif - mathieu.bredif@ign.fr 4 | */ 5 | 6 | /** 7 | * A NavSatFix client that listens to a given topic and displays a line connecting the gps fixes. 8 | * 9 | * @constructor 10 | * @param options - object with following keys: 11 | * 12 | * * ros - the ROSLIB.Ros connection handle 13 | * * topic - the NavSatFix topic to listen to 14 | * * rootObject (optional) - the root object to add the trajectory line and the gps marker to 15 | * * object3d (optional) - the object3d to be translated by the gps position 16 | * * material (optional) - THREE.js material or options passed to a THREE.LineBasicMaterial, such as : 17 | * * material.color (optional) - color for line 18 | * * material.linewidth (optional) - line width 19 | * * altitudeNaN (optional) - default altitude when the message altitude is NaN (default: 0) 20 | * * keep (optional) - number of gps fix points to keep (default: 100) 21 | * * convert (optional) - conversion function from lon/lat/alt to THREE.Vector3 (default: passthrough) 22 | */ 23 | 24 | ROS3D.NavSatFix = function(options) { 25 | options = options || {}; 26 | this.ros = options.ros; 27 | this.topicName = options.topic || '/gps/fix'; 28 | this.rootObject = options.rootObject || new THREE.Object3D(); 29 | this.object3d = options.object3d || new THREE.Object3D(); 30 | var material = options.material || {}; 31 | this.altitudeNaN = options.altitudeNaN || 0; 32 | this.keep = options.keep || 100; 33 | this.convert = options.convert || function(lon,lat,alt) { return new THREE.Vector3(lon,lat,alt); }; 34 | this.count = 0; 35 | this.next1 = 0; 36 | this.next2 = this.keep; 37 | 38 | this.geom = new THREE.BufferGeometry(); 39 | this.vertices = new THREE.BufferAttribute(new Float32Array( 6 * this.keep ), 3 ); 40 | this.geom.addAttribute( 'position', this.vertices); 41 | this.material = material.isMaterial ? material : new THREE.LineBasicMaterial( material ); 42 | this.line = new THREE.Line( this.geom, this.material ); 43 | this.rootObject.add(this.object3d); 44 | this.rootObject.add(this.line); 45 | 46 | this.rosTopic = undefined; 47 | 48 | this.processMessageBound = this.processMessage.bind(this); 49 | this.subscribe(); 50 | }; 51 | ROS3D.NavSatFix.prototype.__proto__ = THREE.Object3D.prototype; 52 | 53 | 54 | ROS3D.NavSatFix.prototype.unsubscribe = function(){ 55 | if(this.rosTopic){ 56 | this.rosTopic.unsubscribe(this.processMessageBound); 57 | } 58 | }; 59 | 60 | ROS3D.NavSatFix.prototype.subscribe = function(){ 61 | this.unsubscribe(); 62 | 63 | // subscribe to the topic 64 | this.rosTopic = new ROSLIB.Topic({ 65 | ros : this.ros, 66 | name : this.topicName, 67 | queue_length : 1, 68 | messageType : 'sensor_msgs/NavSatFix' 69 | }); 70 | 71 | this.rosTopic.subscribe(this.processMessageBound); 72 | }; 73 | 74 | ROS3D.NavSatFix.prototype.processMessage = function(message){ 75 | var altitude = isNaN(message.altitude) ? this.altitudeNaN : message.altitude; 76 | var p = this.convert(message.longitude, message.latitude, altitude); 77 | 78 | // move the object3d to the gps position 79 | this.object3d.position.copy(p); 80 | this.object3d.updateMatrixWorld(true); 81 | 82 | // copy the position twice in the circular buffer 83 | // the second half replicates the first to allow a single drawRange 84 | this.vertices.array[3*this.next1 ] = p.x; 85 | this.vertices.array[3*this.next1+1] = p.y; 86 | this.vertices.array[3*this.next1+2] = p.z; 87 | this.vertices.array[3*this.next2 ] = p.x; 88 | this.vertices.array[3*this.next2+1] = p.y; 89 | this.vertices.array[3*this.next2+2] = p.z; 90 | this.vertices.needsUpdate = true; 91 | 92 | this.next1 = (this.next1+1) % this.keep; 93 | this.next2 = this.next1 + this.keep; 94 | this.count = Math.min(this.count+1, this.keep); 95 | this.geom.setDrawRange(this.next2-this.count, this.count ); 96 | }; 97 | -------------------------------------------------------------------------------- /src/sensors/PointCloud2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David V. Lu!! - davidvlu@gmail.com 4 | * @author Mathieu Bredif - mathieu.bredif@ign.fr 5 | */ 6 | 7 | /** 8 | * Decodes the base64-encoded array 'inbytes' into the array 'outbytes' 9 | * until 'inbytes' is exhausted or 'outbytes' is filled. 10 | * if 'record_size' is specified, records of length 'record_size' bytes 11 | * are copied every other 'pointRatio' records. 12 | * returns the number of decoded records 13 | */ 14 | function decode64(inbytes, outbytes, record_size, pointRatio) { 15 | var x,b=0,l=0,j=0,L=inbytes.length,A=outbytes.length; 16 | record_size = record_size || A; // default copies everything (no skipping) 17 | pointRatio = pointRatio || 1; // default copies everything (no skipping) 18 | var bitskip = (pointRatio-1) * record_size * 8; 19 | for(x=0;x=8){ 23 | l-=8; 24 | outbytes[j++]=(b>>>l)&0xff; 25 | if((j % record_size) === 0) { // skip records 26 | // no optimization: for(var i=0;i=8) {l-=8;i+=8;}} 27 | // first optimization: for(;l0){b=decode64.e[inbytes.charAt(x)];} 32 | } 33 | } 34 | } 35 | return Math.floor(j/record_size); 36 | } 37 | // initialize decoder with static lookup table 'e' 38 | decode64.S='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 39 | decode64.e={}; 40 | for(var i=0;i<64;i++){decode64.e[decode64.S.charAt(i)]=i;} 41 | 42 | 43 | /** 44 | * A PointCloud2 client that listens to a given topic and displays the points. 45 | * 46 | * @constructor 47 | * @param options - object with following keys: 48 | * 49 | * * ros - the ROSLIB.Ros connection handle 50 | * * topic - the marker topic to listen to (default: '/points') 51 | * * tfClient - the TF client handle to use 52 | * * compression (optional) - message compression (default: 'cbor') 53 | * * rootObject (optional) - the root object to add this marker to use for the points. 54 | * * max_pts (optional) - number of points to draw (default: 10000) 55 | * * pointRatio (optional) - point subsampling ratio (default: 1, no subsampling) 56 | * * messageRatio (optional) - message subsampling ratio (default: 1, no subsampling) 57 | * * material (optional) - a material object or an option to construct a PointsMaterial. 58 | * * colorsrc (optional) - the field to be used for coloring (default: 'rgb') 59 | * * colormap (optional) - function that turns the colorsrc field value to a color 60 | */ 61 | ROS3D.PointCloud2 = function(options) { 62 | options = options || {}; 63 | this.ros = options.ros; 64 | this.topicName = options.topic || '/points'; 65 | this.throttle_rate = options.throttle_rate || null; 66 | this.compression = options.compression || 'cbor'; 67 | this.max_pts = options.max_pts || 10000; 68 | this.points = new ROS3D.Points(options); 69 | this.rosTopic = undefined; 70 | this.buffer = null; 71 | 72 | this.processMessageBound = this.processMessage.bind(this); 73 | this.subscribe(); 74 | }; 75 | ROS3D.PointCloud2.prototype.__proto__ = THREE.Object3D.prototype; 76 | 77 | 78 | ROS3D.PointCloud2.prototype.unsubscribe = function(){ 79 | if(this.rosTopic){ 80 | this.rosTopic.unsubscribe(this.processMessageBound); 81 | } 82 | }; 83 | 84 | ROS3D.PointCloud2.prototype.subscribe = function(){ 85 | this.unsubscribe(); 86 | 87 | // subscribe to the topic 88 | this.rosTopic = new ROSLIB.Topic({ 89 | ros : this.ros, 90 | name : this.topicName, 91 | messageType : 'sensor_msgs/PointCloud2', 92 | throttle_rate : this.throttle_rate, 93 | queue_length : 1, 94 | compression: this.compression 95 | }); 96 | this.rosTopic.subscribe(this.processMessageBound); 97 | }; 98 | 99 | ROS3D.PointCloud2.prototype.processMessage = function(msg){ 100 | if(!this.points.setup(msg.header.frame_id, msg.point_step, msg.fields)) { 101 | return; 102 | } 103 | 104 | var n, pointRatio = this.points.pointRatio; 105 | var bufSz = this.max_pts * msg.point_step; 106 | 107 | if (msg.data.buffer) { 108 | this.buffer = msg.data.slice(0, Math.min(msg.data.byteLength, bufSz)); 109 | n = Math.min(msg.height*msg.width / pointRatio, this.points.positions.array.length / 3); 110 | } else { 111 | if (!this.buffer || this.buffer.byteLength < bufSz) { 112 | this.buffer = new Uint8Array(bufSz); 113 | } 114 | n = decode64(msg.data, this.buffer, msg.point_step, pointRatio); 115 | pointRatio = 1; 116 | } 117 | 118 | var dv = new DataView(this.buffer.buffer); 119 | var littleEndian = !msg.is_bigendian; 120 | var x = this.points.fields.x.offset; 121 | var y = this.points.fields.y.offset; 122 | var z = this.points.fields.z.offset; 123 | var base, color; 124 | for(var i = 0; i < n; i++){ 125 | base = i * pointRatio * msg.point_step; 126 | this.points.positions.array[3*i ] = dv.getFloat32(base+x, littleEndian); 127 | this.points.positions.array[3*i + 1] = dv.getFloat32(base+y, littleEndian); 128 | this.points.positions.array[3*i + 2] = dv.getFloat32(base+z, littleEndian); 129 | 130 | if(this.points.colors){ 131 | color = this.points.colormap(this.points.getColor(dv,base,littleEndian)); 132 | this.points.colors.array[3*i ] = color.r; 133 | this.points.colors.array[3*i + 1] = color.g; 134 | this.points.colors.array[3*i + 2] = color.b; 135 | } 136 | } 137 | this.points.update(n); 138 | }; 139 | -------------------------------------------------------------------------------- /src/sensors/Points.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author David V. Lu!! - davidvlu@gmail.com 4 | * @author Mathieu Bredif - mathieu.bredif@ign.fr 5 | */ 6 | 7 | /** 8 | * A set of points. Used by PointCloud2 and LaserScan. 9 | * 10 | * @constructor 11 | * @param options - object with following keys: 12 | * 13 | * * tfClient - the TF client handle to use 14 | * * rootObject (optional) - the root object to add this marker to use for the points. 15 | * * max_pts (optional) - number of points to draw (default: 10000) 16 | * * pointRatio (optional) - point subsampling ratio (default: 1, no subsampling) 17 | * * messageRatio (optional) - message subsampling ratio (default: 1, no subsampling) 18 | * * material (optional) - a material object or an option to construct a PointsMaterial. 19 | * * colorsrc (optional) - the field to be used for coloring (default: 'rgb') 20 | * * colormap (optional) - function that turns the colorsrc field value to a color 21 | */ 22 | ROS3D.Points = function(options) { 23 | THREE.Object3D.call(this); 24 | options = options || {}; 25 | this.tfClient = options.tfClient; 26 | this.rootObject = options.rootObject || new THREE.Object3D(); 27 | this.max_pts = options.max_pts || 10000; 28 | this.pointRatio = options.pointRatio || 1; 29 | this.messageRatio = options.messageRatio || 1; 30 | this.messageCount = 0; 31 | this.material = options.material || {}; 32 | this.colorsrc = options.colorsrc; 33 | this.colormap = options.colormap; 34 | 35 | if(('color' in options) || ('size' in options) || ('texture' in options)) { 36 | console.warn( 37 | 'toplevel "color", "size" and "texture" options are deprecated.' + 38 | 'They should beprovided within a "material" option, e.g. : '+ 39 | ' { tfClient, material : { color: mycolor, size: mysize, map: mytexture }, ... }' 40 | ); 41 | } 42 | 43 | this.sn = null; 44 | }; 45 | 46 | ROS3D.Points.prototype.__proto__ = THREE.Object3D.prototype; 47 | 48 | ROS3D.Points.prototype.setup = function(frame, point_step, fields) 49 | { 50 | if(this.sn===null){ 51 | // turn fields to a map 52 | fields = fields || []; 53 | this.fields = {}; 54 | for(var i=0; i 0) { 125 | target = intersections[0].object; 126 | event3D.intersection = this.lastIntersection = intersections[0]; 127 | } else { 128 | target = this.fallbackTarget; 129 | } 130 | 131 | // if the mouse moves from one object to another (or from/to the 'null' object), notify both 132 | if (target !== this.lastTarget && domEvent.type.match(/mouse/)) { 133 | 134 | // Event Status. TODO: Make it as enum 135 | // 0: Accepted 136 | // 1: Failed 137 | // 2: Continued 138 | var eventStatus = this.notify(target, 'mouseover', event3D); 139 | if (eventStatus === 0) { 140 | this.notify(this.lastTarget, 'mouseout', event3D); 141 | } else if(eventStatus === 1) { 142 | // if target was null or no target has caught our event, fall back 143 | target = this.fallbackTarget; 144 | if (target !== this.lastTarget) { 145 | this.notify(target, 'mouseover', event3D); 146 | this.notify(this.lastTarget, 'mouseout', event3D); 147 | } 148 | } 149 | } 150 | 151 | // if the finger moves from one object to another (or from/to the 'null' object), notify both 152 | if (target !== this.lastTarget && domEvent.type.match(/touch/)) { 153 | var toucheventAccepted = this.notify(target, domEvent.type, event3D); 154 | if (toucheventAccepted) { 155 | this.notify(this.lastTarget, 'touchleave', event3D); 156 | this.notify(this.lastTarget, 'touchend', event3D); 157 | } else { 158 | // if target was null or no target has caught our event, fall back 159 | target = this.fallbackTarget; 160 | if (target !== this.lastTarget) { 161 | this.notify(this.lastTarget, 'touchmove', event3D); 162 | this.notify(this.lastTarget, 'touchend', event3D); 163 | } 164 | } 165 | } 166 | 167 | // pass through event 168 | this.notify(target, domEvent.type, event3D); 169 | if (domEvent.type === 'mousedown' || domEvent.type === 'touchstart' || domEvent.type === 'touchmove') { 170 | this.dragging = true; 171 | } 172 | this.lastTarget = target; 173 | }; 174 | 175 | /** 176 | * Notify the listener of the type of event that occurred. 177 | * 178 | * @param target - the target of the event 179 | * @param type - the type of event that occurred 180 | * @param event3D - the 3D mouse even information 181 | * @returns if an event was canceled 182 | */ 183 | ROS3D.MouseHandler.prototype.notify = function(target, type, event3D) { 184 | // ensure the type is set 185 | // 186 | event3D.type = type; 187 | 188 | // make the event cancelable 189 | event3D.cancelBubble = false; 190 | event3D.continueBubble = false; 191 | event3D.stopPropagation = function() { 192 | event3D.cancelBubble = true; 193 | }; 194 | 195 | // it hit the selectable object but don't highlight 196 | event3D.continuePropagation = function () { 197 | event3D.continueBubble = true; 198 | }; 199 | 200 | // walk up graph until event is canceled or root node has been reached 201 | event3D.currentTarget = target; 202 | 203 | while (event3D.currentTarget) { 204 | // try to fire event on object 205 | if (event3D.currentTarget.dispatchEvent 206 | && event3D.currentTarget.dispatchEvent instanceof Function) { 207 | event3D.currentTarget.dispatchEvent(event3D); 208 | if (event3D.cancelBubble) { 209 | this.dispatchEvent(event3D); 210 | return 0; // Event Accepted 211 | } 212 | else if(event3D.continueBubble) { 213 | return 2; // Event Continued 214 | } 215 | } 216 | // walk up 217 | event3D.currentTarget = event3D.currentTarget.parent; 218 | } 219 | 220 | return 1; // Event Failed 221 | }; 222 | 223 | Object.assign(ROS3D.MouseHandler.prototype, THREE.EventDispatcher.prototype); 224 | -------------------------------------------------------------------------------- /test/depthCloud/DepthCloud.test.js: -------------------------------------------------------------------------------- 1 | var assert = chai.assert; 2 | 3 | describe('depthCloud', function() { 4 | // Setup Kinect DepthCloud stream 5 | var depthCloud = new ROS3D.DepthCloud({ 6 | url : 'http://'+window.location.hostname+':9999/stream?topic=/depthcloud_encoded&type=vp8&bitrate=250000&quality=best', 7 | f : 525.0 8 | }); 9 | 10 | it('should return 525.0 for value of f', function() { 11 | assert.equal(525.0, depthCloud.f); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // Base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // Testing frameworks 10 | frameworks: ['mocha', 'chai'], 11 | 12 | // List of files / patterns to load in the browser 13 | files: [ 14 | '../node_modules/eventemitter3/dist/eventemitter3.umd.js', 15 | '../node_modules/lodash/lodash.js', 16 | '../node_modules/roslib/build/roslib.js', 17 | '../node_modules/three/build/three.js', 18 | '../build/ros3d.js', 19 | '**/*.test.js' 20 | ], 21 | 22 | client: { 23 | mocha: { 24 | timeout: 10000 25 | } 26 | }, 27 | 28 | // test results reporter to use 29 | // possible values: 'dots', 'progress', 'junit' 30 | reporters: ['progress'], 31 | 32 | 33 | // web server port 34 | port: 9876, 35 | 36 | 37 | // cli runner port 38 | runnerPort: 9200, 39 | 40 | 41 | // enable / disable colors in the output (reporters and logs) 42 | colors: true, 43 | 44 | 45 | // level of logging 46 | // possible values: ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, MARK, OFF 47 | logLevel: 'INFO', 48 | 49 | 50 | // enable / disable watching file and executing tests whenever any file changes 51 | autoWatch: true, 52 | 53 | 54 | // Start these browsers, currently available: 55 | // - Chrome 56 | // - ChromeCanary 57 | // - Firefox 58 | // - Opera 59 | // - Safari (only Mac) 60 | // - PhantomJS 61 | // - IE (only Windows) 62 | browsers: ['Firefox'], 63 | 64 | 65 | // If browser does not capture in given timeout [ms], kill it 66 | captureTimeout: 60000, 67 | 68 | 69 | // Continuous Integration mode 70 | // if true, it capture browsers, run tests and exit 71 | singleRun: true 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /test/markers/Marker.test.js: -------------------------------------------------------------------------------- 1 | var assert = chai.assert; 2 | 3 | describe('Marker', function() { 4 | var marker; 5 | afterEach(function () { 6 | if (marker !== undefined) { 7 | marker.dispose(); 8 | } 9 | }); 10 | describe('Arrow', function() { 11 | }) 12 | describe('Cube', function() { 13 | }) 14 | describe('Sphere', function() { 15 | }) 16 | describe('Cylinder', function() { 17 | }) 18 | describe('Line Strip', function() { 19 | var message = {}; 20 | message.type = ROS3D.MARKER_LINE_STRIP; 21 | message.color = {r: 0, g: 0, b: 0, a: 1}, 22 | message.pose = {}; 23 | message.pose.position = {x: 0, y: 0, z: 0}; 24 | message.pose.orientation = {x: 0, y: 0, z: 0, w: 1}; 25 | message.scale = {x: 1, y: 0, z: 0}; 26 | message.points = [ 27 | {x: 0, y: 0, z: 0}, 28 | {x: 1, y: 0, z: 0}, 29 | {x: 1, y: 1, z: 0}, 30 | {x: 0, y: 1, z: 0}, 31 | ] 32 | message.colors = [ 33 | {r: 1, g: 0, b: 0, a: 1}, 34 | {r: 0, g: 1, b: 0, a: 1}, 35 | {r: 0, g: 0, b: 1, a: 1}, 36 | {r: 1, g: 1, b: 1, a: 1}, 37 | ] 38 | var options = {}; 39 | options.message = message; 40 | marker = new ROS3D.Marker(options); 41 | it('Correct child object', function() { 42 | assert.equal(marker.children.length, 1); 43 | assert.equal(marker.children[0].type, 'Line'); 44 | }) 45 | }) 46 | describe('Line List', function() { 47 | }) 48 | describe('Cube List', function() { 49 | }) 50 | describe('Sphere List', function() { 51 | }) 52 | describe('Points', function() { 53 | }) 54 | describe('Text View Facing', function() { 55 | }) 56 | describe('Mesh Resource', function() { 57 | }) 58 | describe('Triangle List', function() { 59 | }) 60 | }); 61 | -------------------------------------------------------------------------------- /test/models/Arrow.test.js: -------------------------------------------------------------------------------- 1 | var assert = chai.assert; 2 | 3 | describe('Arrow', function() { 4 | var arrow = new ROS3D.Arrow(); 5 | 6 | it('matrix should be equal to the proper Matrix4', function() { 7 | var a = new THREE.Vector3(0, 1, 0); 8 | arrow.setDirection(a); 9 | var b = new THREE.Matrix4(); 10 | b.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 11 | assert.isTrue(_.isEqual(arrow.matrix.toArray(), b.toArray())); 12 | }); 13 | 14 | it('scale should be equal to THREE.Vector3(2, 2, 2)', function() { 15 | arrow.setLength(2); 16 | assert.isTrue(arrow.scale.equals(new THREE.Vector3(2, 2, 2))); 17 | }); 18 | 19 | it('material.color should be equal to THREE.Color(0xfff000)', function() { 20 | arrow.setColor(0xfff000); 21 | assert.equal(arrow.material.color.getHex(), new THREE.Color(0xfff000).getHex()); 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /test/models/Grid.test.js: -------------------------------------------------------------------------------- 1 | var assert = chai.assert; 2 | 3 | describe('Grid', function() { 4 | var grid = new ROS3D.Grid(); 5 | 6 | it('should default to 22 children', function() { 7 | assert.equal(grid.children.length, 22); 8 | }); 9 | 10 | it('each child\'s color is THREE.Color(\'#cccccc\') by default', function() { 11 | var sample = new THREE.Color('#cccccc').getHex(); 12 | function correctColor(element, index, array) { 13 | return element.material.color.getHex() === sample; 14 | } 15 | assert.isTrue(grid.children.every(correctColor)); 16 | }); 17 | 18 | it('each child\'s linewidth is 1 by default', function() { 19 | function isOne(element, index, array) { 20 | return element.material.linewidth === 1; 21 | } 22 | assert.isTrue(grid.children.every(isOne)); 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 | 9 |

ros3d.js Tests

10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, before, beforeEach, after, afterEach, chai, ROS3D, _ */ 2 | 3 | var assert = chai.assert; 4 | 5 | describe('Initialization', function() { 6 | 7 | 8 | describe('Arrow', function() { 9 | var arrow = new ROS3D.Arrow(); 10 | 11 | it('matrix should be equal to the proper Matrix4', function() { 12 | var a = new THREE.Vector3(0, 1, 0); 13 | arrow.setDirection(a); 14 | var b = new THREE.Matrix4(); 15 | b.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 16 | assert.isTrue(_.isEqual(arrow.matrix.toArray(), b.toArray())); 17 | }); 18 | 19 | it('scale should be equal to THREE.Vector3(2, 2, 2)', function() { 20 | arrow.setLength(2); 21 | assert.isTrue(arrow.scale.equals(new THREE.Vector3(2, 2, 2))); 22 | }); 23 | 24 | it('material.color should be equal to THREE.Color(0xfff000)', function() { 25 | arrow.setColor(0xfff000); 26 | assert.equal(arrow.material.color.getHex(), new THREE.Color(0xfff000).getHex()); 27 | }); 28 | 29 | }); 30 | 31 | describe('depthCloud', function() { 32 | // Setup Kinect DepthCloud stream 33 | var depthCloud = new ROS3D.DepthCloud({ 34 | url : 'http://'+window.location.hostname+':9999/stream?topic=/depthcloud_encoded&type=vp8&bitrate=250000&quality=best', 35 | f : 525.0 36 | }); 37 | 38 | it('should return 525.0 for value of f', function() { 39 | assert.equal(525.0, depthCloud.f); 40 | }); 41 | }); 42 | 43 | describe('Grid', function() { 44 | var grid = new ROS3D.Grid(); 45 | 46 | it('should default to 22 children', function() { 47 | assert.equal(grid.children.length, 22); 48 | }); 49 | 50 | it('each child\'s color is THREE.Color(\'#cccccc\') by default', function() { 51 | var sample = new THREE.Color('#cccccc').getHex(); 52 | function correctColor(element, index, array) { 53 | return element.material.color.getHex() === sample; 54 | } 55 | assert.isTrue(grid.children.every(correctColor)); 56 | }); 57 | 58 | it('each child\'s linewidth is 1 by default', function() { 59 | function isOne(element, index, array) { 60 | return element.material.linewidth === 1; 61 | } 62 | assert.isTrue(grid.children.every(isOne)); 63 | }); 64 | 65 | }); 66 | 67 | describe('Marker', function() { 68 | var marker; 69 | afterEach(function () { 70 | if (marker !== undefined) { 71 | marker.dispose(); 72 | } 73 | }); 74 | describe('Arrow', function() { 75 | }); 76 | describe('Cube', function() { 77 | }); 78 | describe('Sphere', function() { 79 | }); 80 | describe('Cylinder', function() { 81 | }); 82 | describe('Line Strip', function() { 83 | var message = {}; 84 | message.type = ROS3D.MARKER_LINE_STRIP; 85 | message.color = {r: 0, g: 0, b: 0, a: 1}, 86 | message.pose = {}; 87 | message.pose.position = {x: 0, y: 0, z: 0}; 88 | message.pose.orientation = {x: 0, y: 0, z: 0, w: 1}; 89 | message.scale = {x: 1, y: 0, z: 0}; 90 | message.points = [ 91 | {x: 0, y: 0, z: 0}, 92 | {x: 1, y: 0, z: 0}, 93 | {x: 1, y: 1, z: 0}, 94 | {x: 0, y: 1, z: 0}, 95 | ]; 96 | message.colors = [ 97 | {r: 1, g: 0, b: 0, a: 1}, 98 | {r: 0, g: 1, b: 0, a: 1}, 99 | {r: 0, g: 0, b: 1, a: 1}, 100 | {r: 1, g: 1, b: 1, a: 1}, 101 | ]; 102 | var options = {}; 103 | options.message = message; 104 | marker = new ROS3D.Marker(options); 105 | it('Correct child object', function() { 106 | assert.equal(marker.children.length, 1); 107 | assert.equal(marker.children[0].type, 'Line'); 108 | }); 109 | }); 110 | describe('Line List', function() { 111 | }); 112 | describe('Cube List', function() { 113 | }); 114 | describe('Sphere List', function() { 115 | }); 116 | describe('Points', function() { 117 | }); 118 | describe('Text View Facing', function() { 119 | }); 120 | describe('Mesh Resource', function() { 121 | }); 122 | describe('Triangle List', function() { 123 | }); 124 | }); 125 | 126 | }); 127 | --------------------------------------------------------------------------------