├── .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 | [](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 | roscore
46 | (method of choice to publish to /map)
47 | roslaunch rosbridge_server rosbridge_websocket.launch
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 | roscore
61 | roslaunch rosbridge_server rosbridge_websocket.launch
62 | rosrun tf2_web_republisher tf2_web_republisher
63 | roslaunch openni_launch openni.launch depth_registration:=true
64 | rosrun web_video_server web_video_server _port:=9999
65 | rosrun depthcloud_encoder depthcloud_encoder_node _depth:=/camera/depth_registered/image_float _rgb:=/camera/rgb/image_rect_color
66 | rosrun nodelet nodelet standalone depth_image_proc/convert_metric image_raw:=/camera/depth_registered/image_raw image:=/camera/depth_registered/image_float
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 | roscore
52 | rosrun visualization_marker_tutorials basic_shapes
53 | rosrun tf2_web_republisher tf2_web_republisher
54 | roslaunch rosbridge_server rosbridge_websocket.launch
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 | ``,
13 | ...Object.entries(examples).map(([example, link]) => {
14 | return `${example} `
15 | }),
16 | ` `,
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 | roscore
56 | rosrun interactive_marker_tutorials basic_controls
57 | rosrun interactive_marker_proxy proxy topic_ns:=/basic_controls
58 | target_frame:=/rotating_frame
59 | rosrun tf2_web_republisher tf2_web_republisher
60 | roslaunch rosbridge_server rosbridge_websocket.launch
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 | roslaunch ros3djs.launch
62 | rosparam set use_sim_time true
63 | rosbag play -l --clock kitti_2011_09_26_drive_0002_synced.bag
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 | roscore
47 | rosrun map_server map_server /opt/ros/groovy/share/rail_maps/maps/ilab.pgm
48 | 0.05
49 | roslaunch rosbridge_server rosbridge_websocket.launch
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 | roscore
55 | rosrun visualization_marker_tutorials basic_shapes
56 | rosrun tf2_web_republisher tf2_web_republisher
57 | roslaunch rosbridge_server rosbridge_websocket.launch
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 | roslaunch ros3djs.launch
75 | rosparam set use_sim_time true
76 | rosbag play -l --clock kitti_2011_09_26_drive_0002_synced.bag
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 | roscore
49 | rosrun octomap_server map_server ./src/octomap_server/tests/teapot.32.bt
50 | roslaunch rosbridge_server rosbridge_websocket.launch
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 | roscore
54 | roslaunch rosbridge_server rosbridge_websocket.launch
55 | rosrun tf2_web_republisher tf2_web_republisher
56 | roslaunch openni_launch openni.launch depth_registration:=true
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 | roslaunch pr2_description upload_pr2.launch
59 | rosrun robot_state_publisher robot_state_publisher
60 | rosparam set use_gui true
61 | rosrun joint_state_publisher joint_state_publisher
62 | rosrun tf2_web_republisher tf2_web_republisher
63 | roslaunch rosbridge_server rosbridge_websocket.launch
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 |
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 |
--------------------------------------------------------------------------------