├── .gitmodules ├── .gitignore ├── test ├── env_256_cornell_box.hdr ├── env_256_cornell_box.png ├── meshfile_object_gltf_optimized.bin ├── meshfile_object_gltf_optimized.ktx2 ├── Readme.md ├── empty_scene.html ├── camera_control.html ├── transparent_ellipsoid.html ├── meshfile_geometry_obj.html ├── set_color.html ├── meshfile_geometry_stl.html ├── meshfile_object_obj.html ├── controls.html ├── texture_text.html ├── orthographic_camera.html ├── render_callback.html ├── animation2.html ├── chained_properties.html ├── meshfile_object_gltf_optimized.html ├── webxr.html ├── meshfile_object_dae.html ├── meshfile_object_gltf.html ├── meshfile_geometry_dae.html ├── opacity.html ├── background.html ├── animation.html └── fatlines.html ├── data └── HeadTextureMultisense.png ├── rebuild ├── Dockerfile └── rebuild.sh ├── package.json ├── webpack.config.js ├── dist ├── index.html └── main.min.js.THIRD_PARTY_LICENSES.json ├── LICENSE └── Readme.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/main.js 3 | dist/main.min.js.LICENSE.txt 4 | yarn-error.log 5 | -------------------------------------------------------------------------------- /test/env_256_cornell_box.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshcat-dev/meshcat/master/test/env_256_cornell_box.hdr -------------------------------------------------------------------------------- /test/env_256_cornell_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshcat-dev/meshcat/master/test/env_256_cornell_box.png -------------------------------------------------------------------------------- /data/HeadTextureMultisense.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshcat-dev/meshcat/master/data/HeadTextureMultisense.png -------------------------------------------------------------------------------- /test/meshfile_object_gltf_optimized.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshcat-dev/meshcat/master/test/meshfile_object_gltf_optimized.bin -------------------------------------------------------------------------------- /test/meshfile_object_gltf_optimized.ktx2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshcat-dev/meshcat/master/test/meshfile_object_gltf_optimized.ktx2 -------------------------------------------------------------------------------- /rebuild/Dockerfile: -------------------------------------------------------------------------------- 1 | # This is used by rebuild.sh to snapshot the node environment. 2 | FROM node:20.11.0 AS build 3 | WORKDIR /usr/src/app 4 | COPY tmp/package.json ./ 5 | COPY tmp/yarn.lock ./ 6 | COPY tmp/webpack.config.js ./ 7 | ENV YARN_CACHE_FOLDER=/root/.yarn 8 | ENV NODE_OPTIONS=--openssl-legacy-provider 9 | RUN \ 10 | --mount=type=cache,target=/root/.yarn \ 11 | --mount=type=cache,target=./node_modules/.cache/webpack \ 12 | yarn install 13 | ENTRYPOINT npm run build 14 | -------------------------------------------------------------------------------- /test/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Testing MeshCat 3 | 4 | This directory contains various HTML test files that each exercise features of MeshCat. To view the tests, open a local web server: 5 | 6 | python3 -m http.server 7000 7 | 8 | After running that command, open http://127.0.0.1:7000/ and browse through the index of tests. Use Ctrl-C to exit the server. 9 | 10 | Some (but not all) test cases will also work correctly when viewed directly as file:// URLs, but we do not recommend this. 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meshcat", 3 | "version": "0.0.1", 4 | "main": "src/index.js", 5 | "repository": "https://github.com/rdeits/meshcat", 6 | "author": "Robin Deits", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "webpack": "^5.89.0", 10 | "webpack-cli": "^5.1.4", 11 | "webpack-license-plugin": "^4.4.2" 12 | }, 13 | "dependencies": { 14 | "@msgpack/msgpack": "^2.8.0", 15 | "ccapture.js": "aceslowman/ccapture.js#npm-fix", 16 | "dat.gui": "^0.7.9", 17 | "three": "^0.176.0", 18 | "three-wtm": "^1.0", 19 | "wwobjloader2": "^6.2.1", 20 | "yarn": "^1.22.21" 21 | }, 22 | "scripts": { 23 | "build": "webpack", 24 | "watch": "webpack --watch" 25 | }, 26 | "resolutions": { 27 | "three": "^0.176.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const LicensePlugin = require('webpack-license-plugin') 2 | 3 | module.exports = [{ 4 | entry: './src/index.js', 5 | output: { 6 | library: "MeshCat", 7 | libraryTarget: 'umd' 8 | }, 9 | mode: "development", 10 | devtool: "eval-cheap-source-map" 11 | }, { 12 | entry: './src/index.js', 13 | output: { 14 | filename: "main.min.js", 15 | library: "MeshCat", 16 | libraryTarget: 'umd' 17 | }, 18 | mode: "production", 19 | module: { 20 | rules: [ 21 | { 22 | test: /\/libs\/(basis|draco)\//, 23 | type: 'asset/inline' 24 | } 25 | ] 26 | }, 27 | plugins: [ 28 | new LicensePlugin({ 29 | outputFilename: "main.min.js.THIRD_PARTY_LICENSES.json", 30 | licenseOverrides: { 31 | 'wwobjloader2@6.2.1': 'MIT', 32 | } 33 | }) 34 | ], 35 | }]; 36 | -------------------------------------------------------------------------------- /test/empty_scene.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |Viewer.handle_command(cmd)cmd should be a JS object with at least the field type. Available command types are:
28 | set_objectdelete command first (see below).
32 | Internally, we append a final path segment, <object>, to the provided path before creating the object. This is done because clients may disagree about what the "intrinsic" transform of a particular geometry is (for example, is a "Box" centered on the origin, or does it have one corner at the origin?). Clients can use the matrix field of the JSON object to store that intrinsic transform, and that matrix will be preserved by attaching it to the <object> path. Generally, you shouldn't need to worry about this: if you set an object at the path /meshcat/foo, then you can set the transform at /meshcat/foo and everything will just work.
33 |
Additional fields:
34 |path"/"-separated string indicating the object's path in the scene tree. An object at path "/foo/bar" is a child of an object at path "/foo", so setting the transform of (or deleting) "/foo" will also affect its children.
37 | objecttoJSON() method of a Three.js Object3D).
39 | Beyond the nominal format, Meshcat also offers a few extensions for convenience: 40 |
geometries stanza, the type field can be set to "_meshfile_geometry" to parse using a mesh file format. In this case, the geometry must also have a field named format set to one of "obj" or "dae" or "stl" and a field named "data" with the string contents of the file.
42 | geometries stanza, the type field can be set to "LineGeometry" to create lines with configurable width (works in all modern browsers). See the Line2 example below for details.
43 | materials stanza, the type field can be set to "_text" to use a string as the texture (i.e., a font rendered onto an image). In this case, the material must also have fields named font_size (in pixels), font_face (a string), and text (the words to render into a texture).
44 | materials stanza, the type field can be set to "LineMaterial" to create materials for Line2 objects with configurable line width. See the Line2 example below for details.
45 | object stanza (i.e., the object with a uuid, not the object argument to set_object), the type field can be set to "_meshfile_object" to parse using a mesh file format. In this case, the geometries and materials and geometry: {uuid} and material: {uuid} are all ignored, and the object must have a field named format set to one of "obj" or "dae" or "stl" and a field named "data" with the string contents of the file. When the format is obj, the object may also have a field named mtl_library with the string contents of the associated mtl file.
46 | object stanza, the type field can be set to "Line2" to render lines with configurable width using LineGeometry and LineMaterial. See the Line2 example below for details.
47 | Example (nominal format): 50 |
51 | {
52 | type: "set_object",
53 | path: "/meshcat/boxes/box1",
54 | object: {
55 | metadata: {version: 4.5, type: "Object"},
56 | geometries: [
57 | {
58 | uuid: "cef79e52-526d-4263-b595-04fa2705974e",
59 | type: "BoxGeometry",
60 | width: 1,
61 | height: 1,
62 | depth:1
63 | }
64 | ],
65 | materials: [
66 | {
67 | uuid: "0767ae32-eb34-450c-b65f-3ae57a1102c3",
68 | type: "MeshLambertMaterial",
69 | color: 16777215,
70 | emissive: 0,
71 | side: 2,
72 | depthFunc: 3,
73 | depthTest: true,
74 | depthWrite: true
75 | }
76 | ],
77 | object: {
78 | uuid: "00c2baef-9600-4c6b-b88d-7e82c40e004f",
79 | type: "Mesh",
80 | geometry: "cef79e52-526d-4263-b595-04fa2705974e",
81 | material: "0767ae32-eb34-450c-b65f-3ae57a1102c3"
82 | }
83 | }
84 | }
85 |
86 | Note the somewhat indirect way in which geometries and materials are specified. Each Three.js serialized object has a list of geometries and a list of materials, each with a UUID. The actual geometry and material for a given object are simply references to those existing UUIDs. This enables easy re-use of geometries between objects in Three.js, although we don't really rely on that in MeshCat. Some information about the JSON object format can be found on the Three.js wiki.
87 | 88 |
Example (_meshfile_geometry):
89 |
90 | {
91 | type: "set_object",
92 | path: "/some/file/geometry",
93 | object: {
94 | metadata: {
95 | version: 4.5,
96 | type: "Object"
97 | },
98 | geometries: [
99 | {
100 | type: "_meshfile_geometry",
101 | uuid: "4a08da6b-bbc6-11ee-b7a2-4b79088b524d",
102 | format: "obj",
103 | data: "v -0.06470900 ..."
104 | }
105 | ],
106 | images: [
107 | {
108 | uuid: "c448fc3a-bbc6-11ee-b7a2-4b79088b524d",
109 | url: "data:image/png;base64,iVBORw0KGgoAAA=="
110 | }
111 | ],
112 | textures: [
113 | {
114 | uuid: "d442ea92-bbc6-11ee-b7a2-4b79088b524d",
115 | wrap: [1001, 1001],
116 | repeat: [1, 1],
117 | image: "c448fc3a-bbc6-11ee-b7a2-4b79088b524d"
118 | }
119 | ],
120 | materials: [
121 | {
122 | uuid: "4a08da6e-bbc6-11ee-b7a2-4b79088b524d",
123 | type: "MeshLambertMaterial",
124 | color: 16777215,
125 | reflectivity: 0.5,
126 | map: "d442ea92-bbc6-11ee-b7a2-4b79088b524d"
127 | }
128 | ],
129 | object: {
130 | uuid: "4a08da6f-bbc6-11ee-b7a2-4b79088b524d",
131 | type: "Mesh",
132 | geometry: "4a08da6b-bbc6-11ee-b7a2-4b79088b524d",
133 | material: "4a08da6e-bbc6-11ee-b7a2-4b79088b524d"
134 | }
135 | }
136 | }
137 |
138 | Example (_text):
139 |
140 | {
141 | type: "set_object",
142 | path: "/meshcat/text",
143 | object: {
144 | metadata: {
145 | version: 4.5,
146 | type: "Object"
147 | },
148 | geometries: [
149 | {
150 | uuid: "6fe70119-bba7-11ee-b7a2-4b79088b524d",
151 | type: "PlaneGeometry",
152 | width: 8,
153 | height: 8,
154 | widthSegments: 1,
155 | heightSegments: 1
156 | }
157 | ],
158 | textures: [
159 | {
160 | uuid: "0c8c99a8-bba8-11ee-b7a2-4b79088b524d",
161 | type: "_text",
162 | text: "Hello, world!",
163 | font_size: 300,
164 | font_face: "sans-serif"
165 | }
166 | ],
167 | materials: [
168 | {
169 | uuid: "6fe7011b-bba7-11ee-b7a2-4b79088b524d",
170 | type: "MeshPhongMaterial",
171 | transparent: true,
172 | map: "0c8c99a8-bba8-11ee-b7a2-4b79088b524d",
173 | }
174 | ],
175 | object: {
176 | uuid: "6fe7011c-bba7-11ee-b7a2-4b79088b524d",
177 | type: "Mesh",
178 | geometry: "6fe70119-bba7-11ee-b7a2-4b79088b524d",
179 | material: "6fe7011b-bba7-11ee-b7a2-4b79088b524d",
180 | }
181 | }
182 | }
183 |
184 | Example (_meshfile_object):
185 |
186 | {
187 | type: "set_object",
188 | path: "/meshcat/wavefront_file",
189 | object: {
190 | metadata: {version: 4.5, type: "Object"},
191 | object: {
192 | uuid: "00c2baef-9600-4c6b-b88d-7e82c40e004f",
193 | type: "_meshfile_object",
194 | format: "obj",
195 | data: "mtllib ./cube.mtl\nusemtl material_0\nv 0.0 0.0 0.0 ...",
196 | mtl_library: "newmtl material_0\nKa 0.2 0.2 0.2\n ...",
197 | resources: {"cube.png": "data:image/png;base64,iV ..."}
198 | }
199 | }
200 | }
201 |
202 | Check test/meshfile_object_obj.html for the full demo.
Example (Line2 with LineGeometry and LineMaterial):
Modern browsers don't support the GL_LINEWIDTH parameter, so standard Line objects won't show varying line widths. MeshCat supports Line2 objects with LineGeometry and LineMaterial for lines with configurable properties.
205 |
206 | {
207 | type: "set_object",
208 | path: "/meshcat/fatline",
209 | object: {
210 | metadata: {version: 4.5, type: "Object"},
211 | geometries: [
212 | {
213 | uuid: "6fe70119-bba7-11ee-b7a2-4b79088b524d",
214 | type: "LineGeometry",
215 | position: {
216 | array: new Float32Array([0, 0, 0, 1, 0, 0, 1, 1, 0])
217 | },
218 | color: {
219 | array: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1])
220 | }
221 | }
222 | ],
223 | materials: [
224 | {
225 | uuid: "6fe7011b-bba7-11ee-b7a2-4b79088b524d",
226 | type: "LineMaterial",
227 | color: 16777215,
228 | linewidth: 5.0,
229 | vertexColors: true,
230 | dashed: false,
231 | worldUnits: false
232 | }
233 | ],
234 | object: {
235 | uuid: "6fe7011c-bba7-11ee-b7a2-4b79088b524d",
236 | type: "Line2",
237 | geometry: "6fe70119-bba7-11ee-b7a2-4b79088b524d",
238 | material: "6fe7011b-bba7-11ee-b7a2-4b79088b524d"
239 | }
240 | }
241 | }
242 |
243 | LineGeometry supports these attributes:
244 |position: Float32Array of vertex positions (x, y, z, x, y, z, ...)
246 | color: (optional) Float32Array of per-vertex RGB colors (r, g, b, r, g, b, ...)
247 | LineMaterial supports the exact same attributes as documented in the Three.js LineMaterial docs.
249 |Check test/fatlines.html for a full demo.
set_transform"/foo" will move the objects at "/foo/box1" and "/foo/robots/HAL9000".
254 | Additional fields:
255 |path"/"-separated string indicating the object's path in the scene tree. An object at path "/foo/bar" is a child of an object at path "/foo", so setting the transform of (or deleting) "/foo" will also affect its children.
258 | matrixFloat32Array in column-major order.
261 |
265 | {
266 | type: "set_transform",
267 | path: "/meshcat/boxes",
268 | matrix: new Float32Array([1, 0, 0, 0,
269 | 0, 1, 0, 0,
270 | 0, 0, 1, 0,
271 | 0.5, 0.0, 0.5, 1])
272 | }
273 |
274 | deleteAdditional fields:
279 |path"/"-separated string indicating the object's path in the scene tree. An object at path "/foo/bar" is a child of an object at path "/foo", so setting the transform of (or deleting) "/foo" will also affect its children.
282 |
285 | {
286 | type: "delete",
287 | path: "/meshcat/boxes"
288 | }
289 |
290 | set_propertyNote: as we append an extra path element with the name <object> to every item created with set_object, if you want to modify a property of the object itself, rather than the group containing it, you should ensure that your path is of the form /meshcat/foo/<object>
Additional fields:
296 |propertyvisible: bool
302 | position: number[3]
303 | quaternion: number[4]
304 | scale: number[3]
305 | color: number[4]
306 | opacity: number (this is the same as the 4th element of color)
307 | modulated_opacity: number
308 | top_color: number[3] (only for the Background)
309 | bottom_color: number[3] (only for the Background)
310 | THREE.Object3D object. This provides a powerful capability to customize the scene, but should be considered an advanced usage -- you're on your own to avoid any unwanted side-effects.
312 | 313 | Properties can be *chained*. For example, for an object with a phong material (MeshPhongMaterial), we may want to tweak its shininess, making it duller. Shininess is not a property of the object itself, but the object's material. There is no *path* to that material, but the property name can include a property name chain, e.g., `material.shininess`. While setting the property, the chained properties will be evaluated in sequence, such that the final name in the chain is the property that receives the `value`. 314 |
315 | As noted, specifying a `path` that doesn't exist creates that path. However, specifying a property that doesn't exist does *not* create that property. If a name in the property chain is missing, an error message will be printed to the console and no value will be assigned. This is not a no-op per se. If the `path` led to the implicit creation of a new folder and object, that pair will still be in place. 316 |
317 | More subtly, if the property name chain has an interior name (e.g., the `foo` in `material.foo.color`) that exists but is not an object and does not have properties (such as if `foo` were a `Number`), then, again, an error gets written to the console and no value will be assigned. 318 |
319 | Finally, property chains can include arrays, such as `"children[1].material.specular"`. The index will be evaluated as a property (with all of the potential consequences as outlined above). In error messages, it may be reported as `children.1` instead of `children[1]`. 320 |
value
326 | {
327 | type: "set_property",
328 | path: "/Cameras/default/rotated/<object>",
329 | property: "zoom",
330 | value: 2.0
331 | }
332 |
333 | Example 2:
334 |
335 | {
336 | type: "set_property",
337 | path: "/Lights/DirectionalLight/<object>",
338 | property: "intensity",
339 | value: 1.0
340 | }
341 |
342 | Example 3 (chained properties):
343 |
344 | {
345 | type: "set_property",
346 | path: "/Lights/SpotLight/<object>",
347 | property: "shadow.radius",
348 | value: 1.0
349 | }
350 |
351 | set_animationAdditional fields:
356 |animationspathset_property above, you will need to append <object> to the path to set an object's intrinsic property.
364 | clipAnimationClip in JSON form. The clip in turn has the following fields:
368 | fpsnametracksname field, with a single "." before that property name to signify that it applies to exactly the object given by the path above.
380 | Each track has the following fields:
381 |name"." (e.g. ".position")
385 | type"vector3" for the position property)
389 | keystime (in frames) and value indicating the value of the animated property at that time.
393 | optionsplayrepetitions
413 | {
414 | type: "set_animation",
415 | animations: [{
416 | path: "/Cameras/default",
417 | clip: {
418 | fps: 30,
419 | name: "default",
420 | tracks: [{
421 | name: ".position"
422 | type: "vector3",
423 | keys: [{
424 | time: 0,
425 | value: [0, 1, .3]
426 | },{
427 | time: 80,
428 | value: [0, 1, 2]
429 | }],
430 | }]
431 | }
432 | },{
433 | path: "/meshcat/boxes",
434 | clip: {
435 | fps: 30,
436 | name: "default",
437 | tracks: [{
438 | name: ".position"
439 | type: "vector3",
440 | keys: [{
441 | time: 0,
442 | value: [0, 1, 0]
443 | },{
444 | time: 80,
445 | value: [0, -1, 0]
446 | }],
447 | }]
448 | }
449 | }],
450 | options: {
451 | play: true,
452 | repetitions: 1
453 | }
454 | }
455 |
456 | set_targetExample:
461 |
462 | {
463 | "type": "set_target",
464 | value: [0., 1., 0.]
465 | }
466 |
467 | This sets the camera target to `(0, 1, 0)`
468 | capture_image
473 | {
474 | "type": "capture_image",
475 | "xres": 1920,
476 | "yres": 1080
477 | }
478 |
479 | This sets the camera target to `(0, 1, 0)`
480 | set_render_callback
497 | {
498 | "type": "set_render_callback",
499 | "callback": `() => {
500 | if (this.is_perspective()) {
501 | if (this.connection.readyState == 1 /* OPEN */) {
502 | this.connection.send(msgpack.encode({
503 | 'type': 'camera_pose',
504 | 'camera_pose': this.camera.matrixWorld.elements
505 | }));
506 | }
507 | }
508 | }`
509 | }
510 |
511 | This dispatches the camera's pose in the world to the websocket
512 | connection.
513 | Viewer.connect(url)msgpack.decode() from msgpack-javascript and the resulting object will be passed directly to Viewer.handle_command() as documented above.
524 |
525 | Note that we do support the MsgPack extension types listed in msgpack-javascript#extension-types, with additional support for the Float32Array type which is particularly useful for efficiently sending point data and for Uint32Array, Uint8Array, and Int32Array.
526 |
534 | {
535 | type: "set_property",
536 | path: "/path/to/my/geometry",
537 | property: "opacity",
538 | value: 0.5
539 | }
540 |
541 |
542 | This would assign the opacity value 0.5 to *all* of the materials found rooted
543 | at `"/path/to/my/geometry"`. (That means using the path `"/path"` could affect
544 | many geometries.) However, this will overwrite whatever opacity the geometry had
545 | inherently (i.e., from the material defined in the mesh file); a transparent
546 | object could become *more* opaque.
547 |
548 | Meshcat offers a pseudo property "modulated_opacity". Meshcat remembers an
549 | object's inherent opacity and, by setting *this* value, sets the rendered
550 | opacity to be the product of the inherent and modulated opacity value. The
551 | corresponding command would be:
552 |
553 |
554 | {
555 | type: "set_property",
556 | path: "/path/to/my/geometry",
557 | property: "modulated_opacity",
558 | value: 0.5
559 | }
560 |
561 |
562 | Setting `"modulated_opacity"` to 1 will restore the geometry's original
563 | opacity. This does *not* introduce a queryable `modulated_opacity` property on
564 | any material. This is what makes it a "pseudo" property. The same tree-based
565 | scope of effect applies to `"modulated_opacity"` as with `"opacity"` (and also
566 | the `"color"` property).
567 |
568 | Meshcat *always* remembers the inherent opacity value. So, if you've overwritten
569 | the value (via setting `"opacity"` or `"color"`), you can restore it by setting
570 | the `"modulated_opacity"` value to 1.0.
571 |
572 | ### Useful Paths
573 |
574 | The default MeshCat scene comes with a few objects at pre-set paths. You can replace, delete, or transform these objects just like anything else in the scene.
575 |
576 | /Lights/DirectionalLight/Lights/AmbientLight/Grid/Axes/Cameras/Background/Render Settings/set_transform commands like any other object. You can also use set_target to change the camera's target. Please note that replacing the camera with set_object is not currently supported (but we expect to implement this in the future).
596 |
597 | Controlling the camera is slightly more complicated than moving a single object because the camera actually has two important poses: the origin about which the camera orbits when you click-and-drag with the mouse, and the position of the camera itself. In addition, cameras and controls in Three.js assume a coordinate system in which the Y axis is upward. In robotics, we typically have the Z axis pointing up, and that's what's done in MeshCat. To account for this, the actual camera lives inside a few path elements:
598 |
599 | `/Cameras/default/rotated/