debugging-javascript.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. Title: Three.js Debugging JavaScript
  2. Description: How to debug JavaScript with THREE.js
  3. TOC: Debugging JavaScript
  4. Most of this article is not directly about THREE.js but is
  5. rather about debugging JavaScript in general. It seemed important in
  6. that many people just starting with THREE.js are also just
  7. starting with JavaScript so I hope this can help them more easily
  8. solve any issues they run into.
  9. Debugging is a big topic and I probably can't begin to cover
  10. everything there is to know but if you're new to JavaScript
  11. then here's an attempt to give a few pointers. I strongly
  12. suggest you take some time to learn them. They'll help you
  13. enormously in your learning.
  14. ## Learn your Browser's Developer Tools
  15. All browsers have developer tools.
  16. [Chrome](https://developers.google.com/web/tools/chrome-devtools/),
  17. [Firefox](https://developer.mozilla.org/en-US/docs/Tools),
  18. [Safari](https://developer.apple.com/safari/tools/),
  19. [Edge](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide).
  20. In Chrome you can click the the `⋮` icon, pick More Tools->Developer Tools
  21. to get to the developer tools. A keyboard shortcut is also shown there.
  22. <div class="threejs_center"><img class="border" src="resources/images/devtools-chrome.jpg" style="width: 789px;"></div>
  23. In Firefox you click the `☰` icon, pick "Web Developer", then pick
  24. "Toggle Tools"
  25. <div class="threejs_center"><img class="border" src="resources/images/devtools-firefox.jpg" style="width: 786px;"></div>
  26. In Safari you first have to enable the Develop menu from the
  27. Advanced Safari Preferences.
  28. <div class="threejs_center"><img class="border" src="resources/images/devtools-enable-safari.jpg" style="width: 775px;"></div>
  29. Then in the Develop menu you can pick "Show/Connect Web Inspector".
  30. <div class="threejs_center"><img class="border" src="resources/images/devtools-safari.jpg" style="width: 777px;"></div>
  31. With Chrome you can also [use Chrome on your computer to debug webpages running on Chrome on your Android phone or tablet](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).
  32. Similarly with Safari you can
  33. [use your computer to debug webpages running on Safari on iPhones and iPads](https://www.google.com/search?q=safari+remote+debugging+ios).
  34. I'm most familiar with Chrome so this guide will be using Chrome
  35. as an example when referring to tools but most browsers have similar
  36. features so it should be easy to apply anything here to all browsers.
  37. ## Turn off the cache
  38. Browsers try to reuse data they've already downloaded. This is great
  39. for users so if you visit a website a second time many of the files
  40. used to display the site will not have be downloaded again.
  41. On the other hand this can be bad for web development. You change
  42. a file on your computer, reload the page, and you don't see the changes
  43. because the browser uses the version it got last time.
  44. One solution during web development is to turn off the cache. This
  45. way the browser will always get the newest versions of your files.
  46. First pick settings from the corner menu
  47. <div class="threejs_center"><img class="border" src="resources/images/devtools-chrome-settings.jpg" style="width: 778px"></div>
  48. Then pick "Disable Cache (while DevTools is open)".
  49. <div class="threejs_center"><img class="border" src="resources/images/devtools-chrome-disable-cache.jpg" style="width: 779px"></div>
  50. ## Use the JavaScript console
  51. Inside all devtools is a *console*. It shows warnings and error messages.
  52. ** READ THE MESSAGES!! **
  53. Typically there should be only 1 or 2 messages.
  54. <div class="threejs_center"><img class="border" src="resources/images/devtools-no-errors.jpg" style="width: 779px"></div>
  55. If you see any others **READ THEM**. For example:
  56. <div class="threejs_center"><img class="border" src="resources/images/devtools-errors.jpg" style="width: 779px"></div>
  57. I mis-spelled "three" as "threee"
  58. You can also print your own info to the console with with `console.log` as in
  59. ```js
  60. console.log(someObject.position.x, someObject.position.y, someObject.position.z);
  61. ```
  62. Even cooler, if you log an object you can inspect it. For example if we log
  63. the root scene object from [the gLTF article](threejs-load-gltf.html)
  64. ```js
  65. {
  66. const gltfLoader = new GLTFLoader();
  67. gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
  68. const root = gltf.scene;
  69. scene.add(root);
  70. + console.log(root);
  71. ```
  72. Then we can expand that object in the JavaScript console
  73. <div class="threejs_center"><img class="border" src="resources/images/devtools-console-object.gif"></div>
  74. You can also use `console.error` which reports the message in red
  75. in includes a stack trace.
  76. ## Put data on screen
  77. Another obvious but often overlooked way is to add `<div>` or `<pre>` tags
  78. and put data in them.
  79. The most obvious way is to make some HTML elements
  80. ```html
  81. <canvas id="c"></canvas>
  82. +<div id="debug">
  83. + <div>x:<span id="x"></span></div>
  84. + <div>y:<span id="y"></span></div>
  85. + <div>z:<span id="z"></span></div>
  86. +</div>
  87. ```
  88. Style them so they stay on top of the canvas. (assuming your canvas
  89. fills the page)
  90. ```html
  91. <style>
  92. #debug {
  93. position: absolute;
  94. left: 1em;
  95. top: 1em;
  96. padding: 1em;
  97. background: rgba(0, 0, 0, 0.8);
  98. color: white;
  99. font-family: monospace;
  100. }
  101. </style>
  102. ```
  103. And then looking the elements up and setting their content.
  104. ```js
  105. // at init time
  106. const xElem = document.querySelector('#x');
  107. const yElem = document.querySelector('#y');
  108. const zElem = document.querySelector('#z');
  109. // at render or update time
  110. xElem.textContent = someObject.position.x.toFixed(3);
  111. yElem.textContent = someObject.position.y.toFixed(3);
  112. zElem.textContent = someObject.position.z.toFixed(3);
  113. ```
  114. This is more useful for real time values
  115. {{{example url="../threejs-debug-js-html-elements.html" }}}
  116. Another way to put data on the screen is to make a clearing logger.
  117. I just made that term up but lots of games I've worked on have used this solution. The idea
  118. is you have a buffer that displays messages for only one frame.
  119. Any part of your code that wants to display data calls some function
  120. to add data to that buffer every frame. This is much less work
  121. than making an element per piece of data above.
  122. For example let's change the HTML from above to just this
  123. ```html
  124. <canvas id="c"></canvas>
  125. <div id="debug">
  126. <pre></pre>
  127. </div>
  128. ```
  129. And let's make simple class to manage this *clear back buffer*.
  130. ```js
  131. class ClearingLogger {
  132. constructor(elem) {
  133. this.elem = elem;
  134. this.lines = [];
  135. }
  136. log(...args) {
  137. this.lines.push([...args].join(' '));
  138. }
  139. render() {
  140. this.elem.textContent = this.lines.join('\n');
  141. this.lines = [];
  142. }
  143. }
  144. ```
  145. Then let's make a simple example that every time we click the mouse makes a mesh
  146. that moves in a random direction for 2 seconds. We'll start with one of the
  147. examples from the article on [making things responsive](threejs-responsive.html)
  148. Here's the code that adds a new `Mesh` every time we click the mouse
  149. ```js
  150. const geometry = new THREE.SphereGeometry();
  151. const material = new THREE.MeshBasicMaterial({color: 'red'});
  152. const things = [];
  153. function rand(min, max) {
  154. if (max === undefined) {
  155. max = min;
  156. min = 0;
  157. }
  158. return Math.random() * (max - min) + min;
  159. }
  160. function createThing() {
  161. const mesh = new THREE.Mesh(geometry, material);
  162. scene.add(mesh);
  163. things.push({
  164. mesh,
  165. timer: 2,
  166. velocity: new THREE.Vector3(rand(-5, 5), rand(-5, 5), rand(-5, 5)),
  167. });
  168. }
  169. canvas.addEventListener('click', createThing);
  170. ```
  171. And here's the code that moves the meshes we created, logs them,
  172. and removes them when their timer has run out
  173. ```js
  174. const logger = new ClearingLogger(document.querySelector('#debug pre'));
  175. let then = 0;
  176. function render(now) {
  177. now *= 0.001; // convert to seconds
  178. const deltaTime = now - then;
  179. then = now;
  180. ...
  181. logger.log('fps:', (1 / deltaTime).toFixed(1));
  182. logger.log('num things:', things.length);
  183. for (let i = 0; i < things.length;) {
  184. const thing = things[i];
  185. const mesh = thing.mesh;
  186. const pos = mesh.position;
  187. logger.log(
  188. 'timer:', thing.timer.toFixed(3),
  189. 'pos:', pos.x.toFixed(3), pos.y.toFixed(3), pos.z.toFixed(3));
  190. thing.timer -= deltaTime;
  191. if (thing.timer <= 0) {
  192. // remove this thing. Note we don't advance `i`
  193. things.splice(i, 1);
  194. scene.remove(mesh);
  195. } else {
  196. mesh.position.addScaledVector(thing.velocity, deltaTime);
  197. ++i;
  198. }
  199. }
  200. renderer.render(scene, camera);
  201. logger.render();
  202. requestAnimationFrame(render);
  203. }
  204. ```
  205. Now click the mouse a bunch in the example below
  206. {{{example url="../threejs-debug-js-clearing-logger.html" }}}
  207. ## Query Parameters
  208. Another thing to remember is that webpages can have data passed
  209. into them either via query parameters or the anchor, sometimes called
  210. the search and the hash.
  211. https://domain/path/?query#anchor
  212. You can use this to make features optional or pass in parameters.
  213. For example let's take the previous example and make it so
  214. the debug stuff only shows up if we put `?debug=true` in the URL.
  215. First we need some code to parse the query string
  216. ```js
  217. /**
  218. * Returns the query parameters as a key/value object.
  219. * Example: If the query parameters are
  220. *
  221. * abc=123&def=456&name=gman
  222. *
  223. * Then `getQuery()` will return an object like
  224. *
  225. * {
  226. * abc: '123',
  227. * def: '456',
  228. * name: 'gman',
  229. * }
  230. */
  231. function getQuery() {
  232. return Object.fromEntries(new URLSearchParams(window.location.search).entries());
  233. }
  234. ```
  235. Then we might make the debug element not show by default
  236. ```html
  237. <canvas id="c"></canvas>
  238. +<div id="debug" style="display: none;">
  239. <pre></pre>
  240. </div>
  241. ```
  242. Then in the code we read the params and choose to un-hide the
  243. debug info if and only if `?debug=true` is passed in
  244. ```js
  245. const query = getQuery();
  246. const debug = query.debug === 'true';
  247. const logger = debug
  248. ? new ClearingLogger(document.querySelector('#debug pre'))
  249. : new DummyLogger();
  250. if (debug) {
  251. document.querySelector('#debug').style.display = '';
  252. }
  253. ```
  254. We also made a `DummyLogger` that does nothing and chose to use it if `?debug=true` has not been passed in.
  255. ```js
  256. class DummyLogger {
  257. log() {}
  258. render() {}
  259. }
  260. ```
  261. You can see if we use this url:
  262. <a target="_blank" href="../threejs-debug-js-params.html">threejs-debug-js-params.html</a>
  263. there is no debug info but if we use this url:
  264. <a target="_blank" href="../threejs-debug-js-params.html?debug=true">threejs-debug-js-params.html?debug=true</a>
  265. there is debug info.
  266. Multiple parameters can be passed in by separating with '&' as in `somepage.html?someparam=somevalue&someotherparam=someothervalue`.
  267. Using parameters like this we can pass in all kinds of options. Maybe `speed=0.01` to slow down our app for making it easier to understand something or `showHelpers=true` for whether or not to add helpers
  268. that show the lights, shadow, or camera frustum seen in other lessons.
  269. ## Learn to use the Debugger
  270. Every browser has a debugger where you can pause your program
  271. step through line by line and inspect all the variables.
  272. Teaching you how to use a debugger is too big a topic for this
  273. article but here's a few links
  274. * [Get Started with Debugging JavaScript in Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/javascript/)
  275. * [Debugging in Chrome](https://javascript.info/debugging-chrome)
  276. * [Tips and Tricks for Debugging in Chrome Developer Tools](https://hackernoon.com/tips-and-tricks-for-debugging-in-chrome-developer-tools-458ade27c7ab)
  277. ## Check for `NaN` in the debugger or elsewhere
  278. `NaN` is short for Not A Number. It's what JavaScript will assign
  279. as a value when you do something that doesn't make sense mathwise.
  280. As a simple example
  281. <div class="threejs_center"><img class="border" src="resources/images/nan-banana.png" style="width: 180px;"></div>
  282. Often when I'm making something and nothing appears on the screen
  283. I'll check some values and if I see `NaN` I will instantly have a
  284. place to start looking.
  285. As an example when I first started making the path for the
  286. [article about loading gLTF files](threejs-load-gltf.html) I made
  287. a curve using the `SplineCurve` class which makes a 2D curve.
  288. I then used that curve to move the cars like this
  289. ```js
  290. curve.getPointAt(zeroToOnePointOnCurve, car.position);
  291. ```
  292. Internally `curve.getPointAt` calls the `set` function
  293. on the object passed as the second argument. In this case that
  294. second argument is `car.position` which is a `Vector3`. `Vector3`'s
  295. `set` function requires 3 arguments, x, y, and z but `SplineCurve` is a 2D curve
  296. and so it calls `car.position.set` with just x and y.
  297. The result is that `car.position.set` sets x to x, y to y, and z to `undefined`.
  298. A quick glance in the debugger looking at the car's `matrixWorld`
  299. showed a bunch of `NaN` values.
  300. <div class="threejs_center"><img class="border" src="resources/images/debugging-nan.gif" style="width: 476px;"></div>
  301. Seeing the matrix had `NaN`s in it suggested something like `position`,
  302. `rotation`, `scale` or some other function that affects that matrix had bad
  303. data. Working backward from their it was easy to track down the issue.
  304. In top of `NaN` there's also `Infinity` which is a similar sign there
  305. is a math bug somewhere.
  306. ## Look In the Code!
  307. THREE.js is Open Source. Don't be afraid to look inside the code!
  308. You can look inside on [github](https://github.com/mrdoob/three.js).
  309. You can also look inside by stepping into functions in the debugger.
  310. ## Put `requestAnimationFrame` at bottom of your render function.
  311. I see this pattern often
  312. ```js
  313. function render() {
  314. requestAnimationFrame(render);
  315. // -- do stuff --
  316. renderer.render(scene, camera);
  317. }
  318. requestAnimationFrame(render);
  319. ```
  320. I'd suggest that putting the call to `requestAnimationFrame` at
  321. the bottom as in
  322. ```js
  323. function render() {
  324. // -- do stuff --
  325. renderer.render(scene, camera);
  326. requestAnimationFrame(render);
  327. }
  328. requestAnimationFrame(render);
  329. ```
  330. The biggest reason is it means your code will stop if you have an error. Putting
  331. `requestAnimationFrame` at the top means your code will keep running even if you
  332. have an error since you already requested another frame. IMO it's better to find
  333. those errors than to ignore them. They could easily be the reason something is
  334. not appearing as you expect it to but unless your code stops you might not even
  335. notice.
  336. ## Check your units!
  337. This basically means knowing for example when to use degrees vs
  338. when to use radians. It's unfortunate that THREE.js does not
  339. consistently use the same units everywhere. Off the top of my head
  340. the camera's field of view is in degrees. All other angles are in
  341. radians.
  342. The other place to look out is your world unit size. Until
  343. recently 3D apps could choose any unit size they wanted. One app might choose
  344. 1 unit = 1cm. Another might choose 1 unit = 1 foot. It's actually still
  345. true that you can chose any units you want for certain applications.
  346. That said, THREE.js assumes 1 unit = 1 meter. This is important for
  347. things like physically based rendering which uses meters to compute
  348. lighting effects. It's also important for AR and VR which need to
  349. deal with real world units like where your phone is or where the VR
  350. controllers are.
  351. ## Making a *Minimal, Complete, Verifiable, Example* for Stack Overflow
  352. If you decide to ask a question about THREE.js it's almost always
  353. required for you to provide an MCVE which stands for Minimal, Complete,
  354. Verifiable, Example.
  355. The **Minimal** part is important. Let's say you where having an issue with the
  356. path movement in the last example of the [loading a gLTF
  357. article](threejs-load-gltf.html). That example has many parts. Listing them out
  358. it has
  359. 1. A bunch of HTML
  360. 2. Some CSS
  361. 3. Lights
  362. 4. Shadows
  363. 5. DAT.gui code to manipulate shadows
  364. 6. Code to load a .GLTF file
  365. 7. Code to resize the canvas.
  366. 8. Code to move the cars along paths
  367. That's pretty huge. If your question is only about the path following part you
  368. can remove most of the HTML as you only need a `<canvas>` and a `<script>` tag
  369. for THREE.js. You can remove the CSS and the resizing code. You can remove .GLTF
  370. code because you only care about the path. You can remove the lights and the
  371. shadows by using a `MeshBasicMaterial`. You can certainly remove the DAT.gui
  372. code. The code makes a ground plane with a texture. It would be easier to use a
  373. `GridHelper`. Finally if our question is about moving things on a path we could
  374. just use cubes on the path instead of loaded car models.
  375. Here's a more minimal example taking all the above into account. It
  376. shrunk from 271 lines to 135. We might consider shrinking it even
  377. more by simplifying our path. Maybe a path with 3 or 4 points would
  378. work just as well as our path with 21 points.
  379. {{{example url="../threejs-debugging-mcve.html" }}}
  380. I kept the `OrbitController` just because it's useful for others
  381. to move the camera and figure out what's going on but depending
  382. on your issue you might be able to remove that as well.
  383. The best thing about making an MCVE is we'll often solve our own
  384. problem. The process of removing everything that's not needed and
  385. making the smallest example we can that reproduces the issue more
  386. often than not leads us to our bug.
  387. On top of that it's respectful of all the people's time who you are
  388. asking to look at your code on Stack Overflow. By making the minimal
  389. example you make it much easier for them to help you. You'll also
  390. learn in the process.
  391. Also important, when you go to Stack Overflow to post your question **put your
  392. code [in a snippet](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/).**
  393. Of course you are welcome to use JSFiddle or Codepen or similar site to test out
  394. your MCVE but once you actually get to posting your question on Stack Overflow
  395. you're required to put the code to reproduce your issue **in the question itself**.
  396. By making a snippet you satisfy that requirement.
  397. Also note all the live examples on this site should run as snippets.
  398. Just copy the HTML, CSS, and JavaScript parts to their respective
  399. parts of the [snippet editor](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/).
  400. Just remember to try to remove the parts that are not relevant to
  401. your issue and try to make your code the minimal amount needed.
  402. Follow these suggestions and you're far more likely to get help
  403. with your issue.
  404. ## Use a `MeshBasicMaterial`
  405. Because the `MeshBasicMaterial` uses no lights this is one way to
  406. remove reasons something might not be showing up. If your objects
  407. show up using `MeshBasicMaterial` but not with whatever materials
  408. you were using then you know the issue is likely with the materials
  409. or the lights and not some other part of the code.
  410. ## Check your `near` and `far` settings for your camera
  411. A `PerspectiveCamera` has `near` and `far` settings which are covered in the
  412. [article on cameras](threejs-cameras.html). Make sure they are set to fit the
  413. space that contains your objects. Maybe even just **temporarily** set them to
  414. something large like `near` = 0.001 and `far` = 1000000. You will likely run
  415. into depth resolution issues but you'll at least be able to see your objects
  416. provided they are in front of the camera.
  417. ## Check your scene is in front of the camera
  418. Sometimes things don't appear because they are not in front of the camera. If
  419. your camera is not controllable try adding camera control like the
  420. `OrbitController` so you can look around and find your scene. Or, try framing
  421. the scene using code which is covered in [this article](threejs-load-obj.html).
  422. That code finds the size of part of the scene and then moves the camera and
  423. adjusts the `near` and `far` settings to make it visible. You can then look in
  424. the debugger or add some `console.log` messages to print the size and center of
  425. the scene.
  426. ## Put something in front of the camera
  427. This is just another way of saying if all else fails start with
  428. something that works and then slowly add stuff back in. If you get
  429. a screen with nothing on it then try putting something directly in
  430. front of the camera. Make a sphere or box, give it a simple material
  431. like the `MeshBasicMaterial` and make sure you can get that on the screen.
  432. Then start adding things back a little at time and testing. Eventually
  433. you'll either reproduce your bug or you'll find it on the way.
  434. ---
  435. These were a few tips for debugging JavaScript. Let's also go
  436. over [some tips for debugging GLSL](threejs-debugging-glsl.html).