Quellcode durchsuchen

Merge pull request #22807 from greggman/test

Merge Threejsfundamentals into three.js
Mr.doob vor 3 Jahren
Ursprung
Commit
e0f59762be
100 geänderte Dateien mit 9350 neuen und 0 gelöschten Zeilen
  1. BIN
      manual/3rdparty/monaco-editor/min/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf
  2. 85 0
      manual/3rdparty/monaco-editor/min/vs/base/worker/workerMain.js
  3. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/abap/abap.js
  4. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/apex/apex.js
  5. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/azcli/azcli.js
  6. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/bat/bat.js
  7. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/cameligo/cameligo.js
  8. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/clojure/clojure.js
  9. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/coffee/coffee.js
  10. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/cpp/cpp.js
  11. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/csharp/csharp.js
  12. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/csp/csp.js
  13. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/css/css.js
  14. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/dockerfile/dockerfile.js
  15. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/fsharp/fsharp.js
  16. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/go/go.js
  17. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/graphql/graphql.js
  18. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/handlebars/handlebars.js
  19. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/html/html.js
  20. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/ini/ini.js
  21. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/java/java.js
  22. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/javascript/javascript.js
  23. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/kotlin/kotlin.js
  24. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/less/less.js
  25. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/lua/lua.js
  26. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/markdown/markdown.js
  27. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/mips/mips.js
  28. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/msdax/msdax.js
  29. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/mysql/mysql.js
  30. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/objective-c/objective-c.js
  31. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/pascal/pascal.js
  32. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/pascaligo/pascaligo.js
  33. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/perl/perl.js
  34. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/pgsql/pgsql.js
  35. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/php/php.js
  36. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/postiats/postiats.js
  37. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/powerquery/powerquery.js
  38. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/powershell/powershell.js
  39. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/pug/pug.js
  40. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/python/python.js
  41. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/r/r.js
  42. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/razor/razor.js
  43. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/redis/redis.js
  44. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/redshift/redshift.js
  45. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/restructuredtext/restructuredtext.js
  46. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/ruby/ruby.js
  47. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/rust/rust.js
  48. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/sb/sb.js
  49. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/scheme/scheme.js
  50. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/scss/scss.js
  51. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/shell/shell.js
  52. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/solidity/solidity.js
  53. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/sophia/sophia.js
  54. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/sql/sql.js
  55. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/st/st.js
  56. 9 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/swift/swift.js
  57. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/tcl/tcl.js
  58. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/twig/twig.js
  59. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/typescript/typescript.js
  60. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/vb/vb.js
  61. 7 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/xml/xml.js
  62. 6 0
      manual/3rdparty/monaco-editor/min/vs/basic-languages/yaml/yaml.js
  63. 5 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.css
  64. 7 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.js
  65. 10 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.de.js
  66. 10 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.es.js
  67. 10 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.fr.js
  68. 10 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.it.js
  69. 9 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.ja.js
  70. 10 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.js
  71. 9 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.ko.js
  72. 9 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.ru.js
  73. 9 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.zh-cn.js
  74. 9 0
      manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.zh-tw.js
  75. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/css/cssMode.js
  76. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/css/cssWorker.js
  77. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/html/htmlMode.js
  78. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/html/htmlWorker.js
  79. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/json/jsonMode.js
  80. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/json/jsonWorker.js
  81. 6 0
      manual/3rdparty/monaco-editor/min/vs/language/typescript/tsMode.js
  82. 20 0
      manual/3rdparty/monaco-editor/min/vs/language/typescript/tsWorker.js
  83. 37 0
      manual/3rdparty/monaco-editor/min/vs/loader.js
  84. 1 0
      manual/3rdparty/split.min.js
  85. 719 0
      manual/en/align-html-elements-to-3d.html
  86. 256 0
      manual/en/backgrounds.html
  87. 314 0
      manual/en/billboards.html
  88. 555 0
      manual/en/cameras.html
  89. 418 0
      manual/en/canvas-textures.html
  90. 437 0
      manual/en/cleanup.html
  91. 444 0
      manual/en/custom-buffergeometry.html
  92. 459 0
      manual/en/custom-geometry.html
  93. 113 0
      manual/en/debugging-glsl.html
  94. 494 0
      manual/en/debugging-javascript.html
  95. 274 0
      manual/en/fog.html
  96. 437 0
      manual/en/fundamentals.html
  97. 1925 0
      manual/en/game.html
  98. 627 0
      manual/en/indexed-textures.html
  99. 515 0
      manual/en/lights.html
  100. 699 0
      manual/en/load-gltf.html

BIN
manual/3rdparty/monaco-editor/min/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf


Datei-Diff unterdrückt, da er zu groß ist
+ 85 - 0
manual/3rdparty/monaco-editor/min/vs/base/worker/workerMain.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/abap/abap.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/apex/apex.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/azcli/azcli.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/azcli/azcli",["require","exports"],(function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.conf={comments:{lineComment:"#"}},t.language={defaultToken:"keyword",ignoreCase:!0,tokenPostfix:".azcli",str:/[^#\s]/,tokenizer:{root:[{include:"@comment"},[/\s-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}],[/^-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}]],type:[{include:"@comment"},[/-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":"key.identifier"}}],[/@str+\s*/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}]],comment:[[/#.*$/,{cases:{"@eos":{token:"comment",next:"@popall"}}}]]}}}));

+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/bat/bat.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/bat/bat",["require","exports"],(function(e,s){"use strict";Object.defineProperty(s,"__esModule",{value:!0}),s.conf={comments:{lineComment:"REM"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],surroundingPairs:[{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],folding:{markers:{start:new RegExp("^\\s*(::\\s*|REM\\s+)#region"),end:new RegExp("^\\s*(::\\s*|REM\\s+)#endregion")}}},s.language={defaultToken:"",ignoreCase:!0,tokenPostfix:".bat",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.parenthesis",open:"(",close:")"},{token:"delimiter.square",open:"[",close:"]"}],keywords:/call|defined|echo|errorlevel|exist|for|goto|if|pause|set|shift|start|title|not|pushd|popd/,symbols:/[=><!~?&|+\-*\/\^;\.,]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^(\s*)(rem(?:\s.*|))$/,["","comment"]],[/(\@?)(@keywords)(?!\w)/,[{token:"keyword"},{token:"keyword.$2"}]],[/[ \t\r\n]+/,""],[/setlocal(?!\w)/,"keyword.tag-setlocal"],[/endlocal(?!\w)/,"keyword.tag-setlocal"],[/[a-zA-Z_]\w*/,""],[/:\w*/,"metatag"],[/%[^%]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],string:[[/[^\\"'%]+/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/%[\w ]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/$/,"string","@popall"]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/cameligo/cameligo.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/clojure/clojure.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/coffee/coffee.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/cpp/cpp.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/csharp/csharp.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/csp/csp.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/csp/csp",["require","exports"],(function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.conf={brackets:[],autoClosingPairs:[],surroundingPairs:[]},e.language={keywords:[],typeKeywords:[],tokenPostfix:".csp",operators:[],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/child-src/,"string.quote"],[/connect-src/,"string.quote"],[/default-src/,"string.quote"],[/font-src/,"string.quote"],[/frame-src/,"string.quote"],[/img-src/,"string.quote"],[/manifest-src/,"string.quote"],[/media-src/,"string.quote"],[/object-src/,"string.quote"],[/script-src/,"string.quote"],[/style-src/,"string.quote"],[/worker-src/,"string.quote"],[/base-uri/,"string.quote"],[/plugin-types/,"string.quote"],[/sandbox/,"string.quote"],[/disown-opener/,"string.quote"],[/form-action/,"string.quote"],[/frame-ancestors/,"string.quote"],[/report-uri/,"string.quote"],[/report-to/,"string.quote"],[/upgrade-insecure-requests/,"string.quote"],[/block-all-mixed-content/,"string.quote"],[/require-sri-for/,"string.quote"],[/reflected-xss/,"string.quote"],[/referrer/,"string.quote"],[/policy-uri/,"string.quote"],[/'self'/,"string.quote"],[/'unsafe-inline'/,"string.quote"],[/'unsafe-eval'/,"string.quote"],[/'strict-dynamic'/,"string.quote"],[/'unsafe-hashed-attributes'/,"string.quote"]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/css/css.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/dockerfile/dockerfile.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/dockerfile/dockerfile",["require","exports"],(function(e,s){"use strict";Object.defineProperty(s,"__esModule",{value:!0}),s.conf={brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}]},s.language={defaultToken:"",tokenPostfix:".dockerfile",variable:/\${?[\w]+}?/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/(ONBUILD)(\s+)/,["keyword",""]],[/(ENV)(\s+)([\w]+)/,["keyword","",{token:"variable",next:"@arguments"}]],[/(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|ARG|VOLUME|LABEL|USER|WORKDIR|COPY|CMD|STOPSIGNAL|SHELL|HEALTHCHECK|ENTRYPOINT)/,{token:"keyword",next:"@arguments"}]],arguments:[{include:"@whitespace"},{include:"@strings"},[/(@variable)/,{cases:{"@eos":{token:"variable",next:"@popall"},"@default":"variable"}}],[/\\/,{cases:{"@eos":"","@default":""}}],[/./,{cases:{"@eos":{token:"",next:"@popall"},"@default":""}}]],whitespace:[[/\s+/,{cases:{"@eos":{token:"",next:"@popall"},"@default":""}}]],comment:[[/(^#.*$)/,"comment","@popall"]],strings:[[/'$/,"string","@popall"],[/'/,"string","@stringBody"],[/"$/,"string","@popall"],[/"/,"string","@dblStringBody"]],stringBody:[[/[^\\\$']/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/\\./,"string.escape"],[/'$/,"string","@popall"],[/'/,"string","@pop"],[/(@variable)/,"variable"],[/\\$/,"string"],[/$/,"string","@popall"]],dblStringBody:[[/[^\\\$"]/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/\\./,"string.escape"],[/"$/,"string","@popall"],[/"/,"string","@pop"],[/(@variable)/,"variable"],[/\\$/,"string"],[/$/,"string","@popall"]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/fsharp/fsharp.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/go/go.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/graphql/graphql.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/handlebars/handlebars.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/html/html.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/ini/ini.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/ini/ini",["require","exports"],(function(e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.conf={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}]},n.language={defaultToken:"",tokenPostfix:".ini",escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^\[[^\]]*\]/,"metatag"],[/(^\w+)(\s*)(\=)/,["key","","delimiter"]],{include:"@whitespace"},[/\d+/,"number"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],whitespace:[[/[ \t\r\n]+/,""],[/^\s*[#;].*$/,"comment"]],string:[[/[^\\"']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/java/java.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/javascript/javascript.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/kotlin/kotlin.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/less/less.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/lua/lua.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/markdown/markdown.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/mips/mips.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/msdax/msdax.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/mysql/mysql.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/objective-c/objective-c.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/pascal/pascal.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/pascaligo/pascaligo.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/pascaligo/pascaligo",["require","exports"],(function(e,o){"use strict";Object.defineProperty(o,"__esModule",{value:!0}),o.conf={comments:{lineComment:"//",blockComment:["(*","*)"]},brackets:[["{","}"],["[","]"],["(",")"],["<",">"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:"<",close:">"},{open:"'",close:"'"}]},o.language={defaultToken:"",tokenPostfix:".pascaligo",ignoreCase:!0,brackets:[{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"},{open:"(",close:")",token:"delimiter.parenthesis"},{open:"<",close:">",token:"delimiter.angle"}],keywords:["begin","block","case","const","else","end","fail","for","from","function","if","is","nil","of","remove","return","skip","then","type","var","while","with","option","None","transaction"],typeKeywords:["bool","int","list","map","nat","record","string","unit","address","map","mtz","xtz"],operators:["=",">","<","<=",">=","<>",":",":=","and","mod","or","+","-","*","/","@","&","^","%"],symbols:/[=><:@\^&|+\-*\/\^%]+/,tokenizer:{root:[[/[a-zA-Z_][\w]*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"identifier"}}],{include:"@whitespace"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/\$[0-9a-fA-F]{1,16}/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'/,"string","@string"],[/'[^\\']'/,"string"],[/'/,"string.invalid"],[/\#\d+/,"string"]],comment:[[/[^\(\*]+/,"comment"],[/\*\)/,"comment","@pop"],[/\(\*/,"comment"]],string:[[/[^\\']+/,"string"],[/\\./,"string.escape.invalid"],[/'/,{token:"string.quote",bracket:"@close",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,"white"],[/\(\*/,"comment","@comment"],[/\/\/.*$/,"comment"]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/perl/perl.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/pgsql/pgsql.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/php/php.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/postiats/postiats.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/powerquery/powerquery.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/powershell/powershell.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/pug/pug.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/python/python.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/r/r.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/razor/razor.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/redis/redis.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/redshift/redshift.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/restructuredtext/restructuredtext.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/ruby/ruby.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/rust/rust.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/sb/sb.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/sb/sb",["require","exports"],(function(e,o){"use strict";Object.defineProperty(o,"__esModule",{value:!0}),o.conf={comments:{lineComment:"'"},brackets:[["(",")"],["[","]"],["If","EndIf"],["While","EndWhile"],["For","EndFor"],["Sub","EndSub"]],autoClosingPairs:[{open:'"',close:'"',notIn:["string","comment"]},{open:"(",close:")",notIn:["string","comment"]},{open:"[",close:"]",notIn:["string","comment"]}]},o.language={defaultToken:"",tokenPostfix:".sb",ignoreCase:!0,brackets:[{token:"delimiter.array",open:"[",close:"]"},{token:"delimiter.parenthesis",open:"(",close:")"},{token:"keyword.tag-if",open:"If",close:"EndIf"},{token:"keyword.tag-while",open:"While",close:"EndWhile"},{token:"keyword.tag-for",open:"For",close:"EndFor"},{token:"keyword.tag-sub",open:"Sub",close:"EndSub"}],keywords:["Else","ElseIf","EndFor","EndIf","EndSub","EndWhile","For","Goto","If","Step","Sub","Then","To","While"],tagwords:["If","Sub","While","For"],operators:[">","<","<>","<=",">=","And","Or","+","-","*","/","="],identifier:/[a-zA-Z_][\w]*/,symbols:/[=><:+\-*\/%\.,]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[{include:"@whitespace"},[/(@identifier)(?=[.])/,"type"],[/@identifier/,{cases:{"@keywords":{token:"keyword.$0"},"@operators":"operator","@default":"variable.name"}}],[/([.])(@identifier)/,{cases:{$2:["delimiter","type.member"],"@default":""}}],[/\d*\.\d+/,"number.float"],[/\d+/,"number"],[/[()\[\]]/,"@brackets"],[/@symbols/,{cases:{"@operators":"operator","@default":"delimiter"}}],[/"([^"\\]|\\.)*$/,"string.invalid"],[/"/,"string","@string"]],whitespace:[[/[ \t\r\n]+/,""],[/(\').*$/,"comment"]],string:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"C?/,"string","@pop"]]}}}));

+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/scheme/scheme.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/scheme/scheme",["require","exports"],(function(e,o){"use strict";Object.defineProperty(o,"__esModule",{value:!0}),o.conf={comments:{lineComment:";",blockComment:["#|","|#"]},brackets:[["(",")"],["{","}"],["[","]"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}]},o.language={defaultToken:"",ignoreCase:!0,tokenPostfix:".scheme",brackets:[{open:"(",close:")",token:"delimiter.parenthesis"},{open:"{",close:"}",token:"delimiter.curly"},{open:"[",close:"]",token:"delimiter.square"}],keywords:["case","do","let","loop","if","else","when","cons","car","cdr","cond","lambda","lambda*","syntax-rules","format","set!","quote","eval","append","list","list?","member?","load"],constants:["#t","#f"],operators:["eq?","eqv?","equal?","and","or","not","null?"],tokenizer:{root:[[/#[xXoObB][0-9a-fA-F]+/,"number.hex"],[/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?/,"number.float"],[/(?:\b(?:(define|define-syntax|define-macro))\b)(\s+)((?:\w|\-|\!|\?)*)/,["keyword","white","variable"]],{include:"@whitespace"},{include:"@strings"},[/[a-zA-Z_#][a-zA-Z0-9_\-\?\!\*]*/,{cases:{"@keywords":"keyword","@constants":"constant","@operators":"operators","@default":"identifier"}}]],comment:[[/[^\|#]+/,"comment"],[/#\|/,"comment","@push"],[/\|#/,"comment","@pop"],[/[\|#]/,"comment"]],whitespace:[[/[ \t\r\n]+/,"white"],[/#\|/,"comment","@comment"],[/;.*$/,"comment"]],strings:[[/"$/,"string","@popall"],[/"(?=.)/,"string","@multiLineString"]],multiLineString:[[/[^\\"]+$/,"string","@popall"],[/[^\\"]+/,"string"],[/\\./,"string.escape"],[/"/,"string","@popall"],[/\\$/,"string"]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/scss/scss.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/shell/shell.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/solidity/solidity.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/sophia/sophia.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/sql/sql.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/st/st.js


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/swift/swift.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/tcl/tcl.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/twig/twig.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/typescript/typescript.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/vb/vb.js


+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/xml/xml.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 1.10.0(1b4729c63bdb0d1e06d4e637e5c3977ddeb714dd)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/xml/xml",["require","exports"],(function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.conf={comments:{blockComment:["\x3c!--","--\x3e"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}]},t.language={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}}}));

Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/basic-languages/yaml/yaml.js


Datei-Diff unterdrückt, da er zu groß ist
+ 5 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.css


Datei-Diff unterdrückt, da er zu groß ist
+ 7 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.js


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.de.js


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.es.js


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.fr.js


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.it.js


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.ja.js


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.js


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.ko.js


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.ru.js


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.zh-cn.js


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
manual/3rdparty/monaco-editor/min/vs/editor/editor.main.nls.zh-tw.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/css/cssMode.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/css/cssWorker.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/html/htmlMode.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/html/htmlWorker.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/json/jsonMode.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/json/jsonWorker.js


Datei-Diff unterdrückt, da er zu groß ist
+ 6 - 0
manual/3rdparty/monaco-editor/min/vs/language/typescript/tsMode.js


Datei-Diff unterdrückt, da er zu groß ist
+ 20 - 0
manual/3rdparty/monaco-editor/min/vs/language/typescript/tsWorker.js


+ 37 - 0
manual/3rdparty/monaco-editor/min/vs/loader.js

@@ -0,0 +1,37 @@
+/*!-----------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Version: 0.20.0(6363745c0a33c27b149b89342a7b96d354fb554c)
+ * Released under the MIT license
+ * https://github.com/Microsoft/vscode/blob/master/LICENSE.txt
+ *-----------------------------------------------------------*/
+"use strict";var define,AMDLoader,_amdLoaderGlobal=this,_commonjsGlobal="object"==typeof global?global:{};!function(e){e.global=_amdLoaderGlobal;var t=function(){function t(){this._detected=!1,this._isWindows=!1,this._isNode=!1,this._isElectronRenderer=!1,this._isWebWorker=!1}return Object.defineProperty(t.prototype,"isWindows",{get:function(){return this._detect(),this._isWindows},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isNode",{get:function(){return this._detect(),this._isNode},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isElectronRenderer",{get:function(){return this._detect(),this._isElectronRenderer},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isWebWorker",{get:function(){return this._detect(),this._isWebWorker},enumerable:!0,configurable:!0}),t.prototype._detect=function(){this._detected||(this._detected=!0,this._isWindows=t._isWindows(),this._isNode="undefined"!=typeof module&&!!module.exports,
+this._isElectronRenderer="undefined"!=typeof process&&void 0!==process.versions&&void 0!==process.versions.electron&&"renderer"===process.type,this._isWebWorker="function"==typeof e.global.importScripts)},t._isWindows=function(){return!!("undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.indexOf("Windows")>=0)||"undefined"!=typeof process&&"win32"===process.platform},t}();e.Environment=t}(AMDLoader||(AMDLoader={})),function(e){var t=function(e,t,r){this.type=e,this.detail=t,this.timestamp=r};e.LoaderEvent=t;var r=function(){function r(e){this._events=[new t(1,"",e)]}return r.prototype.record=function(r,n){this._events.push(new t(r,n,e.Utilities.getHighPerformanceTimestamp()))},r.prototype.getEvents=function(){return this._events},r}();e.LoaderEventRecorder=r;var n=function(){function e(){}return e.prototype.record=function(e,t){},e.prototype.getEvents=function(){return[]},e.INSTANCE=new e,e}();e.NullLoaderEventRecorder=n}(AMDLoader||(AMDLoader={})),function(e){var t=function(){
+function t(){}return t.fileUriToFilePath=function(e,t){if(t=decodeURI(t).replace(/%23/g,"#"),e){if(/^file:\/\/\//.test(t))return t.substr(8);if(/^file:\/\//.test(t))return t.substr(5)}else if(/^file:\/\//.test(t))return t.substr(7);return t},t.startsWith=function(e,t){return e.length>=t.length&&e.substr(0,t.length)===t},t.endsWith=function(e,t){return e.length>=t.length&&e.substr(e.length-t.length)===t},t.containsQueryString=function(e){return/^[^\#]*\?/gi.test(e)},t.isAbsolutePath=function(e){return/^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(e)},t.forEachProperty=function(e,t){if(e){var r=void 0;for(r in e)e.hasOwnProperty(r)&&t(r,e[r])}},t.isEmpty=function(e){var r=!0;return t.forEachProperty(e,(function(){r=!1})),r},t.recursiveClone=function(e){if(!e||"object"!=typeof e)return e;var r=Array.isArray(e)?[]:{};return t.forEachProperty(e,(function(e,n){r[e]=n&&"object"==typeof n?t.recursiveClone(n):n})),r},t.generateAnonymousModule=function(){return"===anonymous"+t.NEXT_ANONYMOUS_ID+++"==="},
+t.isAnonymousModule=function(e){return t.startsWith(e,"===anonymous")},t.getHighPerformanceTimestamp=function(){return this.PERFORMANCE_NOW_PROBED||(this.PERFORMANCE_NOW_PROBED=!0,this.HAS_PERFORMANCE_NOW=e.global.performance&&"function"==typeof e.global.performance.now),this.HAS_PERFORMANCE_NOW?e.global.performance.now():Date.now()},t.NEXT_ANONYMOUS_ID=1,t.PERFORMANCE_NOW_PROBED=!1,t.HAS_PERFORMANCE_NOW=!1,t}();e.Utilities=t}(AMDLoader||(AMDLoader={})),function(e){function t(e){if(e instanceof Error)return e;var t=new Error(e.message||String(e)||"Unknown Error");return e.stack&&(t.stack=e.stack),t}e.ensureError=t;var r=function(){function r(){}return r.validateConfigurationOptions=function(r){if("string"!=typeof(r=r||{}).baseUrl&&(r.baseUrl=""),"boolean"!=typeof r.isBuild&&(r.isBuild=!1),"object"!=typeof r.paths&&(r.paths={}),"object"!=typeof r.config&&(r.config={}),void 0===r.catchError&&(r.catchError=!1),void 0===r.recordStats&&(r.recordStats=!1),"string"!=typeof r.urlArgs&&(r.urlArgs=""),
+"function"!=typeof r.onError&&(r.onError=function(e){return"loading"===e.phase?(console.error('Loading "'+e.moduleId+'" failed'),console.error(e),console.error("Here are the modules that depend on it:"),void console.error(e.neededBy)):"factory"===e.phase?(console.error('The factory method of "'+e.moduleId+'" has thrown an exception'),void console.error(e)):void 0}),Array.isArray(r.ignoreDuplicateModules)||(r.ignoreDuplicateModules=[]),r.baseUrl.length>0&&(e.Utilities.endsWith(r.baseUrl,"/")||(r.baseUrl+="/")),"string"!=typeof r.cspNonce&&(r.cspNonce=""),Array.isArray(r.nodeModules)||(r.nodeModules=[]),r.nodeCachedData&&"object"==typeof r.nodeCachedData&&("string"!=typeof r.nodeCachedData.seed&&(r.nodeCachedData.seed="seed"),("number"!=typeof r.nodeCachedData.writeDelay||r.nodeCachedData.writeDelay<0)&&(r.nodeCachedData.writeDelay=7e3),!r.nodeCachedData.path||"string"!=typeof r.nodeCachedData.path)){var n=t(new Error("INVALID cached data configuration, 'path' MUST be set"));n.phase="configuration",
+r.onError(n),r.nodeCachedData=void 0}return r},r.mergeConfigurationOptions=function(t,n){void 0===t&&(t=null),void 0===n&&(n=null);var o=e.Utilities.recursiveClone(n||{});return e.Utilities.forEachProperty(t,(function(t,r){"ignoreDuplicateModules"===t&&void 0!==o.ignoreDuplicateModules?o.ignoreDuplicateModules=o.ignoreDuplicateModules.concat(r):"paths"===t&&void 0!==o.paths?e.Utilities.forEachProperty(r,(function(e,t){return o.paths[e]=t})):"config"===t&&void 0!==o.config?e.Utilities.forEachProperty(r,(function(e,t){return o.config[e]=t})):o[t]=e.Utilities.recursiveClone(r)})),r.validateConfigurationOptions(o)},r}();e.ConfigurationOptionsUtil=r;var n=function(){function t(e,t){if(this._env=e,this.options=r.mergeConfigurationOptions(t),this._createIgnoreDuplicateModulesMap(),this._createNodeModulesMap(),this._createSortedPathsRules(),""===this.options.baseUrl){if(this.options.nodeRequire&&this.options.nodeRequire.main&&this.options.nodeRequire.main.filename&&this._env.isNode){
+var n=this.options.nodeRequire.main.filename,o=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\"));this.options.baseUrl=n.substring(0,o+1)}if(this.options.nodeMain&&this._env.isNode){n=this.options.nodeMain,o=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\"));this.options.baseUrl=n.substring(0,o+1)}}}return t.prototype._createIgnoreDuplicateModulesMap=function(){this.ignoreDuplicateModulesMap={};for(var e=0;e<this.options.ignoreDuplicateModules.length;e++)this.ignoreDuplicateModulesMap[this.options.ignoreDuplicateModules[e]]=!0},t.prototype._createNodeModulesMap=function(){this.nodeModulesMap=Object.create(null);for(var e=0,t=this.options.nodeModules;e<t.length;e++){var r=t[e];this.nodeModulesMap[r]=!0}},t.prototype._createSortedPathsRules=function(){var t=this;this.sortedPathsRules=[],e.Utilities.forEachProperty(this.options.paths,(function(e,r){Array.isArray(r)?t.sortedPathsRules.push({from:e,to:r}):t.sortedPathsRules.push({from:e,to:[r]})})),this.sortedPathsRules.sort((function(e,t){
+return t.from.length-e.from.length}))},t.prototype.cloneAndMerge=function(e){return new t(this._env,r.mergeConfigurationOptions(e,this.options))},t.prototype.getOptionsLiteral=function(){return this.options},t.prototype._applyPaths=function(t){for(var r,n=0,o=this.sortedPathsRules.length;n<o;n++)if(r=this.sortedPathsRules[n],e.Utilities.startsWith(t,r.from)){for(var i=[],s=0,d=r.to.length;s<d;s++)i.push(r.to[s]+t.substr(r.from.length));return i}return[t]},t.prototype._addUrlArgsToUrl=function(t){return e.Utilities.containsQueryString(t)?t+"&"+this.options.urlArgs:t+"?"+this.options.urlArgs},t.prototype._addUrlArgsIfNecessaryToUrl=function(e){return this.options.urlArgs?this._addUrlArgsToUrl(e):e},t.prototype._addUrlArgsIfNecessaryToUrls=function(e){if(this.options.urlArgs)for(var t=0,r=e.length;t<r;t++)e[t]=this._addUrlArgsToUrl(e[t]);return e},t.prototype.moduleIdToPaths=function(t){if(!0===this.nodeModulesMap[t])return this.isBuild()?["empty:"]:["node|"+t];var r,n=t
+;if(e.Utilities.endsWith(n,".js")||e.Utilities.isAbsolutePath(n))e.Utilities.endsWith(n,".js")||e.Utilities.containsQueryString(n)||(n+=".js"),r=[n];else for(var o=0,i=(r=this._applyPaths(n)).length;o<i;o++)this.isBuild()&&"empty:"===r[o]||(e.Utilities.isAbsolutePath(r[o])||(r[o]=this.options.baseUrl+r[o]),e.Utilities.endsWith(r[o],".js")||e.Utilities.containsQueryString(r[o])||(r[o]=r[o]+".js"));return this._addUrlArgsIfNecessaryToUrls(r)},t.prototype.requireToUrl=function(t){var r=t;return e.Utilities.isAbsolutePath(r)||(r=this._applyPaths(r)[0],e.Utilities.isAbsolutePath(r)||(r=this.options.baseUrl+r)),this._addUrlArgsIfNecessaryToUrl(r)},t.prototype.isBuild=function(){return this.options.isBuild},t.prototype.isDuplicateMessageIgnoredFor=function(e){return this.ignoreDuplicateModulesMap.hasOwnProperty(e)},t.prototype.getConfigForModule=function(e){if(this.options.config)return this.options.config[e]},t.prototype.shouldCatchError=function(){return this.options.catchError},
+t.prototype.shouldRecordStats=function(){return this.options.recordStats},t.prototype.onError=function(e){this.options.onError(e)},t}();e.Configuration=n}(AMDLoader||(AMDLoader={})),function(e){var t=function(){function e(e){this._env=e,this._scriptLoader=null,this._callbackMap={}}return e.prototype.load=function(e,t,i,s){var d=this;this._scriptLoader||(this._scriptLoader=this._env.isWebWorker?new n:this._env.isNode?new o(this._env):new r);var a={callback:i,errorback:s};this._callbackMap.hasOwnProperty(t)?this._callbackMap[t].push(a):(this._callbackMap[t]=[a],this._scriptLoader.load(e,t,(function(){return d.triggerCallback(t)}),(function(e){return d.triggerErrorback(t,e)})))},e.prototype.triggerCallback=function(e){var t=this._callbackMap[e];delete this._callbackMap[e];for(var r=0;r<t.length;r++)t[r].callback()},e.prototype.triggerErrorback=function(e,t){var r=this._callbackMap[e];delete this._callbackMap[e];for(var n=0;n<r.length;n++)r[n].errorback(t)},e}(),r=function(){function e(){}
+return e.prototype.attachListeners=function(e,t,r){var n=function(){e.removeEventListener("load",o),e.removeEventListener("error",i)},o=function(e){n(),t()},i=function(e){n(),r(e)};e.addEventListener("load",o),e.addEventListener("error",i)},e.prototype.load=function(e,t,r,n){var o=document.createElement("script");o.setAttribute("async","async"),o.setAttribute("type","text/javascript"),this.attachListeners(o,r,n),o.setAttribute("src",t);var i=e.getConfig().getOptionsLiteral().cspNonce;i&&o.setAttribute("nonce",i),document.getElementsByTagName("head")[0].appendChild(o)},e}(),n=function(){function e(){}return e.prototype.load=function(e,t,r,n){try{importScripts(t),r()}catch(e){n(e)}},e}(),o=function(){function t(e){this._env=e,this._didInitialize=!1,this._didPatchNodeRequire=!1}return t.prototype._init=function(e){this._didInitialize||(this._didInitialize=!0,this._fs=e("fs"),this._vm=e("vm"),this._path=e("path"),this._crypto=e("crypto"))},t.prototype._initNodeRequire=function(e,t){
+var r=t.getConfig().getOptionsLiteral().nodeCachedData;if(r&&!this._didPatchNodeRequire){this._didPatchNodeRequire=!0;var n=this,o=e("module");o.prototype._compile=function(e,i){var s,d=o.wrap(e.replace(/^#!.*/,"")),a=t.getRecorder(),u=n._getCachedDataPath(r,i),l={filename:i};try{var c=n._fs.readFileSync(u);s=c.slice(0,16),l.cachedData=c.slice(16),a.record(60,u)}catch(e){a.record(61,u)}var h=new n._vm.Script(d,l),f=h.runInThisContext(l),p=n._path.dirname(i),_=function(e){var t=e.constructor,r=function(t){try{return e.require(t)}finally{}};return r.resolve=function(r){return t._resolveFilename(r,e)},r.main=process.mainModule,r.extensions=t._extensions,r.cache=t._cache,r}(this),g=[this.exports,_,this,i,p,process,_commonjsGlobal,Buffer],v=f.apply(this.exports,g);return n._handleCachedData(h,d,u,!l.cachedData,t),n._verifyCachedData(h,d,u,s,t),v}}},t.prototype.load=function(r,n,o,i){var s=this,d=r.getConfig().getOptionsLiteral(),a=d.nodeRequire||e.global.nodeRequire,u=d.nodeInstrumenter||function(e){return e}
+;this._init(a),this._initNodeRequire(a,r);var l=r.getRecorder();if(/^node\|/.test(n)){var c=n.split("|"),h=null;try{h=a(c[1])}catch(e){return void i(e)}r.enqueueDefineAnonymousModule([],(function(){return h})),o()}else{n=e.Utilities.fileUriToFilePath(this._env.isWindows,n);var f=this._path.normalize(n),p=this._getElectronRendererScriptPathOrUri(f),_=Boolean(d.nodeCachedData),g=_?this._getCachedDataPath(d.nodeCachedData,n):void 0;this._readSourceAndCachedData(f,g,l,(function(e,n,d,a){if(e)i(e);else{var l;l=n.charCodeAt(0)===t._BOM?t._PREFIX+n.substring(1)+t._SUFFIX:t._PREFIX+n+t._SUFFIX,l=u(l,f);var c={filename:p,cachedData:d},h=s._createAndEvalScript(r,l,c,o,i);s._handleCachedData(h,l,g,_&&!d,r),s._verifyCachedData(h,l,g,a,r)}}))}},t.prototype._createAndEvalScript=function(t,r,n,o,i){var s=t.getRecorder();s.record(31,n.filename);var d=new this._vm.Script(r,n),a=d.runInThisContext(n),u=t.getGlobalAMDDefineFunc(),l=!1,c=function(){return l=!0,u.apply(null,arguments)};return c.amd=u.amd,
+a.call(e.global,t.getGlobalAMDRequireFunc(),c,n.filename,this._path.dirname(n.filename)),s.record(32,n.filename),l?o():i(new Error("Didn't receive define call in "+n.filename+"!")),d},t.prototype._getElectronRendererScriptPathOrUri=function(e){if(!this._env.isElectronRenderer)return e;var t=e.match(/^([a-z])\:(.*)/i);return t?"file:///"+(t[1].toUpperCase()+":"+t[2]).replace(/\\/g,"/"):"file://"+e},t.prototype._getCachedDataPath=function(e,t){var r=this._crypto.createHash("md5").update(t,"utf8").update(e.seed,"utf8").digest("hex"),n=this._path.basename(t).replace(/\.js$/,"");return this._path.join(e.path,n+"-"+r+".code")},t.prototype._handleCachedData=function(e,t,r,n,o){var i=this;e.cachedDataRejected?this._fs.unlink(r,(function(n){o.getRecorder().record(62,r),i._createAndWriteCachedData(e,t,r,o),n&&o.getConfig().onError(n)})):n&&this._createAndWriteCachedData(e,t,r,o)},t.prototype._createAndWriteCachedData=function(e,t,r,n){
+var o=this,i=Math.ceil(n.getConfig().getOptionsLiteral().nodeCachedData.writeDelay*(1+Math.random())),s=-1,d=0,a=void 0,u=function(){setTimeout((function(){a||(a=o._crypto.createHash("md5").update(t,"utf8").digest());var i=e.createCachedData();0===i.length||i.length===s||d>=5||(s=i.length,o._fs.writeFile(r,Buffer.concat([a,i]),(function(e){e&&n.getConfig().onError(e),n.getRecorder().record(63,r),u()})))}),i*Math.pow(4,d++))};u()},t.prototype._readSourceAndCachedData=function(e,t,r,n){if(t){var o=void 0,i=void 0,s=void 0,d=2,a=function(e){e?n(e):0==--d&&n(void 0,o,i,s)};this._fs.readFile(e,{encoding:"utf8"},(function(e,t){o=t,a(e)})),this._fs.readFile(t,(function(e,n){!e&&n&&n.length>0?(s=n.slice(0,16),i=n.slice(16),r.record(60,t)):r.record(61,t),a()}))}else this._fs.readFile(e,{encoding:"utf8"},n)},t.prototype._verifyCachedData=function(e,t,r,n,o){var i=this;n&&(e.cachedDataRejected||setTimeout((function(){var e=i._crypto.createHash("md5").update(t,"utf8").digest()
+;n.equals(e)||(o.getConfig().onError(new Error("FAILED TO VERIFY CACHED DATA, deleting stale '"+r+"' now, but a RESTART IS REQUIRED")),i._fs.unlink(r,(function(e){return o.getConfig().onError(e)})))}),Math.ceil(5e3*(1+Math.random()))))},t._BOM=65279,t._PREFIX="(function (require, define, __filename, __dirname) { ",t._SUFFIX="\n});",t}();e.createScriptLoader=function(e){return new t(e)}}(AMDLoader||(AMDLoader={})),function(e){var t=function(){function t(e){var t=e.lastIndexOf("/");this.fromModulePath=-1!==t?e.substr(0,t+1):""}return t._normalizeModuleId=function(e){var t,r=e;for(t=/\/\.\//;t.test(r);)r=r.replace(t,"/");for(r=r.replace(/^\.\//g,""),t=/\/(([^\/])|([^\/][^\/\.])|([^\/\.][^\/])|([^\/][^\/][^\/]+))\/\.\.\//;t.test(r);)r=r.replace(t,"/");return r=r.replace(/^(([^\/])|([^\/][^\/\.])|([^\/\.][^\/])|([^\/][^\/][^\/]+))\/\.\.\//,"")},t.prototype.resolveModule=function(r){var n=r
+;return e.Utilities.isAbsolutePath(n)||(e.Utilities.startsWith(n,"./")||e.Utilities.startsWith(n,"../"))&&(n=t._normalizeModuleId(this.fromModulePath+n)),n},t.ROOT=new t(""),t}();e.ModuleIdResolver=t;var r=function(){function t(e,t,r,n,o,i){this.id=e,this.strId=t,this.dependencies=r,this._callback=n,this._errorback=o,this.moduleIdResolver=i,this.exports={},this.error=null,this.exportsPassedIn=!1,this.unresolvedDependenciesCount=this.dependencies.length,this._isComplete=!1}return t._safeInvokeFunction=function(t,r){try{return{returnedValue:t.apply(e.global,r),producedError:null}}catch(e){return{returnedValue:null,producedError:e}}},t._invokeFactory=function(t,r,n,o){return t.isBuild()&&!e.Utilities.isAnonymousModule(r)?{returnedValue:null,producedError:null}:t.shouldCatchError()?this._safeInvokeFunction(n,o):{returnedValue:n.apply(e.global,o),producedError:null}},t.prototype.complete=function(r,n,o){this._isComplete=!0;var i=null;if(this._callback)if("function"==typeof this._callback){r.record(21,this.strId)
+;var s=t._invokeFactory(n,this.strId,this._callback,o);i=s.producedError,r.record(22,this.strId),i||void 0===s.returnedValue||this.exportsPassedIn&&!e.Utilities.isEmpty(this.exports)||(this.exports=s.returnedValue)}else this.exports=this._callback;if(i){var d=e.ensureError(i);d.phase="factory",d.moduleId=this.strId,this.error=d,n.onError(d)}this.dependencies=null,this._callback=null,this._errorback=null,this.moduleIdResolver=null},t.prototype.onDependencyError=function(e){return this._isComplete=!0,this.error=e,!!this._errorback&&(this._errorback(e),!0)},t.prototype.isComplete=function(){return this._isComplete},t}();e.Module=r;var n=function(){function e(){this._nextId=0,this._strModuleIdToIntModuleId=new Map,this._intModuleIdToStrModuleId=[],this.getModuleId("exports"),this.getModuleId("module"),this.getModuleId("require")}return e.prototype.getMaxModuleId=function(){return this._nextId},e.prototype.getModuleId=function(e){var t=this._strModuleIdToIntModuleId.get(e);return void 0===t&&(t=this._nextId++,
+this._strModuleIdToIntModuleId.set(e,t),this._intModuleIdToStrModuleId[t]=e),t},e.prototype.getStrModuleId=function(e){return this._intModuleIdToStrModuleId[e]},e}(),o=function(){function e(e){this.id=e}return e.EXPORTS=new e(0),e.MODULE=new e(1),e.REQUIRE=new e(2),e}();e.RegularDependency=o;var i=function(e,t,r){this.id=e,this.pluginId=t,this.pluginParam=r};e.PluginDependency=i;var s=function(){function s(t,r,o,i,s){void 0===s&&(s=0),this._env=t,this._scriptLoader=r,this._loaderAvailableTimestamp=s,this._defineFunc=o,this._requireFunc=i,this._moduleIdProvider=new n,this._config=new e.Configuration(this._env),this._modules2=[],this._knownModules2=[],this._inverseDependencies2=[],this._inversePluginDependencies2=new Map,this._currentAnnonymousDefineCall=null,this._recorder=null,this._buildInfoPath=[],this._buildInfoDefineStack=[],this._buildInfoDependencies=[]}return s.prototype.reset=function(){return new s(this._env,this._scriptLoader,this._defineFunc,this._requireFunc,this._loaderAvailableTimestamp)},
+s.prototype.getGlobalAMDDefineFunc=function(){return this._defineFunc},s.prototype.getGlobalAMDRequireFunc=function(){return this._requireFunc},s._findRelevantLocationInStack=function(e,t){for(var r=function(e){return e.replace(/\\/g,"/")},n=r(e),o=t.split(/\n/),i=0;i<o.length;i++){var s=o[i].match(/(.*):(\d+):(\d+)\)?$/);if(s){var d=s[1],a=s[2],u=s[3],l=Math.max(d.lastIndexOf(" ")+1,d.lastIndexOf("(")+1);if((d=r(d=d.substr(l)))===n){var c={line:parseInt(a,10),col:parseInt(u,10)};return 1===c.line&&(c.col-="(function (require, define, __filename, __dirname) { ".length),c}}}throw new Error("Could not correlate define call site for needle "+e)},s.prototype.getBuildInfo=function(){if(!this._config.isBuild())return null;for(var e=[],t=0,r=0,n=this._modules2.length;r<n;r++){var o=this._modules2[r];if(o){var i=this._buildInfoPath[o.id]||null,d=this._buildInfoDefineStack[o.id]||null,a=this._buildInfoDependencies[o.id];e[t++]={id:o.strId,path:i,defineLocation:i&&d?s._findRelevantLocationInStack(i,d):null,
+dependencies:a,shim:null,exports:o.exports}}}return e},s.prototype.getRecorder=function(){return this._recorder||(this._config.shouldRecordStats()?this._recorder=new e.LoaderEventRecorder(this._loaderAvailableTimestamp):this._recorder=e.NullLoaderEventRecorder.INSTANCE),this._recorder},s.prototype.getLoaderEvents=function(){return this.getRecorder().getEvents()},s.prototype.enqueueDefineAnonymousModule=function(e,t){if(null!==this._currentAnnonymousDefineCall)throw new Error("Can only have one anonymous define call per script file");var r=null;this._config.isBuild()&&(r=new Error("StackLocation").stack||null),this._currentAnnonymousDefineCall={stack:r,dependencies:e,callback:t}},s.prototype.defineModule=function(e,n,o,i,s,d){var a=this;void 0===d&&(d=new t(e));var u=this._moduleIdProvider.getModuleId(e);if(this._modules2[u])this._config.isDuplicateMessageIgnoredFor(e)||console.warn("Duplicate definition of module '"+e+"'");else{var l=new r(u,e,this._normalizeDependencies(n,d),o,i,d);this._modules2[u]=l,
+this._config.isBuild()&&(this._buildInfoDefineStack[u]=s,this._buildInfoDependencies[u]=(l.dependencies||[]).map((function(e){return a._moduleIdProvider.getStrModuleId(e.id)}))),this._resolve(l)}},s.prototype._normalizeDependency=function(e,t){if("exports"===e)return o.EXPORTS;if("module"===e)return o.MODULE;if("require"===e)return o.REQUIRE;var r=e.indexOf("!");if(r>=0){var n=t.resolveModule(e.substr(0,r)),s=t.resolveModule(e.substr(r+1)),d=this._moduleIdProvider.getModuleId(n+"!"+s),a=this._moduleIdProvider.getModuleId(n);return new i(d,a,s)}return new o(this._moduleIdProvider.getModuleId(t.resolveModule(e)))},s.prototype._normalizeDependencies=function(e,t){for(var r=[],n=0,o=0,i=e.length;o<i;o++)r[n++]=this._normalizeDependency(e[o],t);return r},s.prototype._relativeRequire=function(t,r,n,o){if("string"==typeof r)return this.synchronousRequire(r,t);this.defineModule(e.Utilities.generateAnonymousModule(),r,n,o,null,t)},s.prototype.synchronousRequire=function(e,r){void 0===r&&(r=new t(e))
+;var n=this._normalizeDependency(e,r),o=this._modules2[n.id];if(!o)throw new Error("Check dependency list! Synchronous require cannot resolve module '"+e+"'. This is the first mention of this module!");if(!o.isComplete())throw new Error("Check dependency list! Synchronous require cannot resolve module '"+e+"'. This module has not been resolved completely yet.");if(o.error)throw o.error;return o.exports},s.prototype.configure=function(t,r){var n=this._config.shouldRecordStats();this._config=r?new e.Configuration(this._env,t):this._config.cloneAndMerge(t),this._config.shouldRecordStats()&&!n&&(this._recorder=null)},s.prototype.getConfig=function(){return this._config},s.prototype._onLoad=function(e){if(null!==this._currentAnnonymousDefineCall){var t=this._currentAnnonymousDefineCall;this._currentAnnonymousDefineCall=null,this.defineModule(this._moduleIdProvider.getStrModuleId(e),t.dependencies,t.callback,null,t.stack)}},s.prototype._createLoadError=function(t,r){
+var n=this,o=this._moduleIdProvider.getStrModuleId(t),i=(this._inverseDependencies2[t]||[]).map((function(e){return n._moduleIdProvider.getStrModuleId(e)})),s=e.ensureError(r);return s.phase="loading",s.moduleId=o,s.neededBy=i,s},s.prototype._onLoadError=function(e,t){var n=this._createLoadError(e,t);this._modules2[e]||(this._modules2[e]=new r(e,this._moduleIdProvider.getStrModuleId(e),[],(function(){}),(function(){}),null));for(var o=[],i=0,s=this._moduleIdProvider.getMaxModuleId();i<s;i++)o[i]=!1;var d=!1,a=[];for(a.push(e),o[e]=!0;a.length>0;){var u=a.shift(),l=this._modules2[u];l&&(d=l.onDependencyError(n)||d);var c=this._inverseDependencies2[u];if(c)for(i=0,s=c.length;i<s;i++){var h=c[i];o[h]||(a.push(h),o[h]=!0)}}d||this._config.onError(n)},s.prototype._hasDependencyPath=function(e,t){var r=this._modules2[e];if(!r)return!1;for(var n=[],o=0,i=this._moduleIdProvider.getMaxModuleId();o<i;o++)n[o]=!1;var s=[];for(s.push(r),n[e]=!0;s.length>0;){var d=s.shift().dependencies;if(d)for(o=0,i=d.length;o<i;o++){
+var a=d[o];if(a.id===t)return!0;var u=this._modules2[a.id];u&&!n[a.id]&&(n[a.id]=!0,s.push(u))}}return!1},s.prototype._findCyclePath=function(e,t,r){if(e===t||50===r)return[e];var n=this._modules2[e];if(!n)return null;var o=n.dependencies;if(o)for(var i=0,s=o.length;i<s;i++){var d=this._findCyclePath(o[i].id,t,r+1);if(null!==d)return d.push(e),d}return null},s.prototype._createRequire=function(t){var r=this,n=function(e,n,o){return r._relativeRequire(t,e,n,o)};return n.toUrl=function(e){return r._config.requireToUrl(t.resolveModule(e))},n.getStats=function(){return r.getLoaderEvents()},n.__$__nodeRequire=e.global.nodeRequire,n},s.prototype._loadModule=function(e){var t=this;if(!this._modules2[e]&&!this._knownModules2[e]){this._knownModules2[e]=!0;var r=this._moduleIdProvider.getStrModuleId(e),n=this._config.moduleIdToPaths(r);this._env.isNode&&(-1===r.indexOf("/")||/^@[^\/]+\/[^\/]+$/.test(r))&&n.push("node|"+r);var o=-1,i=function(r){if(++o>=n.length)t._onLoadError(e,r);else{var s=n[o],d=t.getRecorder()
+;if(t._config.isBuild()&&"empty:"===s)return t._buildInfoPath[e]=s,t.defineModule(t._moduleIdProvider.getStrModuleId(e),[],null,null,null),void t._onLoad(e);d.record(10,s),t._scriptLoader.load(t,s,(function(){t._config.isBuild()&&(t._buildInfoPath[e]=s),d.record(11,s),t._onLoad(e)}),(function(e){d.record(12,s),i(e)}))}};i(null)}},s.prototype._loadPluginDependency=function(e,r){var n=this;if(!this._modules2[r.id]&&!this._knownModules2[r.id]){this._knownModules2[r.id]=!0;var o=function(e){n.defineModule(n._moduleIdProvider.getStrModuleId(r.id),[],e,null,null)};o.error=function(e){n._config.onError(n._createLoadError(r.id,e))},e.load(r.pluginParam,this._createRequire(t.ROOT),o,this._config.getOptionsLiteral())}},s.prototype._resolve=function(e){var t=this,r=e.dependencies;if(r)for(var n=0,s=r.length;n<s;n++){var d=r[n];if(d!==o.EXPORTS)if(d!==o.MODULE)if(d!==o.REQUIRE){var a=this._modules2[d.id];if(a&&a.isComplete()){if(a.error)return void e.onDependencyError(a.error);e.unresolvedDependenciesCount--
+}else if(this._hasDependencyPath(d.id,e.id)){console.warn("There is a dependency cycle between '"+this._moduleIdProvider.getStrModuleId(d.id)+"' and '"+this._moduleIdProvider.getStrModuleId(e.id)+"'. The cyclic path follows:");var u=this._findCyclePath(d.id,e.id,0)||[];u.reverse(),u.push(d.id),console.warn(u.map((function(e){return t._moduleIdProvider.getStrModuleId(e)})).join(" => \n")),e.unresolvedDependenciesCount--}else if(this._inverseDependencies2[d.id]=this._inverseDependencies2[d.id]||[],this._inverseDependencies2[d.id].push(e.id),d instanceof i){var l=this._modules2[d.pluginId];if(l&&l.isComplete()){this._loadPluginDependency(l.exports,d);continue}var c=this._inversePluginDependencies2.get(d.pluginId);c||(c=[],this._inversePluginDependencies2.set(d.pluginId,c)),c.push(d),this._loadModule(d.pluginId)}else this._loadModule(d.id)}else e.unresolvedDependenciesCount--;else e.unresolvedDependenciesCount--;else e.exportsPassedIn=!0,e.unresolvedDependenciesCount--}
+0===e.unresolvedDependenciesCount&&this._onModuleComplete(e)},s.prototype._onModuleComplete=function(e){var t=this,r=this.getRecorder();if(!e.isComplete()){var n=e.dependencies,i=[];if(n)for(var s=0,d=n.length;s<d;s++){var a=n[s];if(a!==o.EXPORTS)if(a!==o.MODULE)if(a!==o.REQUIRE){var u=this._modules2[a.id];i[s]=u?u.exports:null}else i[s]=this._createRequire(e.moduleIdResolver);else i[s]={id:e.strId,config:function(){return t._config.getConfigForModule(e.strId)}};else i[s]=e.exports}e.complete(r,this._config,i);var l=this._inverseDependencies2[e.id];if(this._inverseDependencies2[e.id]=null,l)for(s=0,d=l.length;s<d;s++){var c=l[s],h=this._modules2[c];h.unresolvedDependenciesCount--,0===h.unresolvedDependenciesCount&&this._onModuleComplete(h)}var f=this._inversePluginDependencies2.get(e.id);if(f){this._inversePluginDependencies2.delete(e.id);for(s=0,d=f.length;s<d;s++)this._loadPluginDependency(e.exports,f[s])}}},s}();e.ModuleManager=s}(AMDLoader||(AMDLoader={})),function(e){
+var t=new e.Environment,r=null,n=function(e,t,n){"string"!=typeof e&&(n=t,t=e,e=null),"object"==typeof t&&Array.isArray(t)||(n=t,t=null),t||(t=["require","exports","module"]),e?r.defineModule(e,t,n,null,null):r.enqueueDefineAnonymousModule(t,n)};n.amd={jQuery:!0};var o=function(e,t){void 0===t&&(t=!1),r.configure(e,t)},i=function(){if(1===arguments.length){if(arguments[0]instanceof Object&&!Array.isArray(arguments[0]))return void o(arguments[0]);if("string"==typeof arguments[0])return r.synchronousRequire(arguments[0])}if(2!==arguments.length&&3!==arguments.length||!Array.isArray(arguments[0]))throw new Error("Unrecognized require call");r.defineModule(e.Utilities.generateAnonymousModule(),arguments[0],arguments[1],arguments[2],null)};function s(){if(void 0!==e.global.require||"undefined"!=typeof require){var o=e.global.require||require;if("function"==typeof o&&"function"==typeof o.resolve){var s=function(e){r.getRecorder().record(33,e);try{return o(e)}finally{r.getRecorder().record(34,e)}}
+;e.global.nodeRequire=s,i.nodeRequire=s,i.__$__nodeRequire=s}}t.isNode&&!t.isElectronRenderer?(module.exports=i,require=i):(t.isElectronRenderer||(e.global.define=n),e.global.require=i)}i.config=o,i.getConfig=function(){return r.getConfig().getOptionsLiteral()},i.reset=function(){r=r.reset()},i.getBuildInfo=function(){return r.getBuildInfo()},i.getStats=function(){return r.getLoaderEvents()},i.define=function(){return n.apply(null,arguments)},e.init=s,"function"==typeof e.global.define&&e.global.define.amd||(r=new e.ModuleManager(t,e.createScriptLoader(t),n,i,e.Utilities.getHighPerformanceTimestamp()),void 0!==e.global.require&&"function"!=typeof e.global.require&&i.config(e.global.require),(define=function(){return n.apply(null,arguments)}).amd=n.amd,"undefined"==typeof doNotInitLoader&&s())}(AMDLoader||(AMDLoader={}));
+//# sourceMappingURL=../../min-maps/vs/loader.js.map

Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 0
manual/3rdparty/split.min.js


+ 719 - 0
manual/en/align-html-elements-to-3d.html

@@ -0,0 +1,719 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Aligning HTML Elements to 3D</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Aligning HTML Elements to 3D">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Aligning HTML Elements to 3D</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This article is part of a series of articles about three.js. The first article
+is <a href="fundamentals.html">three.js fundamentals</a>. If you haven't read that
+yet and you're new to three.js you might want to consider starting there. </p>
+<p>Sometimes you'd like to display some text in your 3D scene. You have many options
+each with pluses and minuses.</p>
+<ul>
+<li><p>Use 3D text</p>
+<p>If you look at the <a href="primitives.html">primitives article</a> you'll see <a href="/docs/#api/en/geometries/TextGeometry"><code class="notranslate" translate="no">TextGeometry</code></a> which
+makes 3D text. This might be useful for flying logos but probably not so useful for stats, info,
+or labelling lots of things.</p>
+</li>
+<li><p>Use a texture with 2D text drawn into it.</p>
+<p>The article on <a href="canvas-textures.html">using a Canvas as a texture</a> shows using
+a canvas as a texture. You can draw text into a canvas and <a href="billboards.html">display it as a billboard</a>.
+The plus here might be that the text is integrated into the 3D scene. For something like a computer terminal
+shown in a 3D scene this might be perfect.</p>
+</li>
+<li><p>Use HTML Elements and position them to match the 3D</p>
+<p>The benefits to this approach is you can use all of HTML. Your HTML can have multiple elements. It can
+by styled with CSS. It can also be selected by the user as it is actual text. </p>
+</li>
+</ul>
+<p>This article will cover this last approach.</p>
+<p>Let's start simple. We'll make a 3D scene with a few primitives and then add a label to each primitive. We'll start
+with an example from <a href="responsive.html">the article on responsive pages</a> </p>
+<p>We'll add some <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> like we did in <a href="lights.html">the article on lighting</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
++import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+</pre>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const controls = new OrbitControls(camera, canvas);
+controls.target.set(0, 0, 0);
+controls.update();
+</pre>
+<p>We need to provide an HTML element to contain our label elements</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
+-  &lt;canvas id="c"&gt;&lt;/canvas&gt;
++  &lt;div id="container"&gt;
++    &lt;canvas id="c"&gt;&lt;/canvas&gt;
++    &lt;div id="labels"&gt;&lt;/div&gt;
++  &lt;/div&gt;
+&lt;/body&gt;
+</pre>
+<p>By putting both the canvas and the <code class="notranslate" translate="no">&lt;div id="labels"&gt;</code> inside a
+parent container we can make them overlap with this CSS</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#c {
+-    width: 100%;
+-    height: 100%;
++    width: 100%;  /* let our container decide our size */
++    height: 100%;
+    display: block;
+}
++#container {
++  position: relative;  /* makes this the origin of its children */
++  width: 100%;
++  height: 100%;
++  overflow: hidden;
++}
++#labels {
++  position: absolute;  /* let us position ourself inside the container */
++  left: 0;             /* make our position the top left of the container */
++  top: 0;
++  color: white;
++}
+</pre>
+<p>let's also add some CSS for the labels themselves</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#labels&gt;div {
+  position: absolute;  /* let us position them inside the container */
+  left: 0;             /* make their default position the top left of the container */
+  top: 0;
+  cursor: pointer;     /* change the cursor to a hand when over us */
+  font-size: large;
+  user-select: none;   /* don't let the text get selected */
+  text-shadow:         /* create a black outline */
+    -1px -1px 0 #000,
+     0   -1px 0 #000,
+     1px -1px 0 #000,
+     1px  0   0 #000,
+     1px  1px 0 #000,
+     0    1px 0 #000,
+    -1px  1px 0 #000,
+    -1px  0   0 #000;
+}
+#labels&gt;div:hover {
+  color: red;
+}
+</pre>
+<p>Now into our code we don't have to add too much. We had a function
+<code class="notranslate" translate="no">makeInstance</code> that we used to generate cubes. Let's make it
+so it also adds a label element.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const labelContainerElem = document.querySelector('#labels');
+
+-function makeInstance(geometry, color, x) {
++function makeInstance(geometry, color, x, name) {
+  const material = new THREE.MeshPhongMaterial({color});
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+
+  cube.position.x = x;
+
++  const elem = document.createElement('div');
++  elem.textContent = name;
++  labelContainerElem.appendChild(elem);
+
+-  return cube;
++  return {cube, elem};
+}
+</pre>
+<p>As you can see we're adding a <code class="notranslate" translate="no">&lt;div&gt;</code> to the container, one for each cube. We're
+also returning an object with both the <code class="notranslate" translate="no">cube</code> and the <code class="notranslate" translate="no">elem</code> for the label.</p>
+<p>Calling it we need to provide a name for each</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cubes = [
+-  makeInstance(geometry, 0x44aa88,  0),
+-  makeInstance(geometry, 0x8844aa, -2),
+-  makeInstance(geometry, 0xaa8844,  2),
++  makeInstance(geometry, 0x44aa88,  0, 'Aqua'),
++  makeInstance(geometry, 0x8844aa, -2, 'Purple'),
++  makeInstance(geometry, 0xaa8844,  2, 'Gold'),
+];
+</pre>
+<p>What remains is positioning the label elements at render time</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const tempV = new THREE.Vector3();
+
+...
+
+-cubes.forEach((cube, ndx) =&gt; {
++cubes.forEach((cubeInfo, ndx) =&gt; {
++  const {cube, elem} = cubeInfo;
+  const speed = 1 + ndx * .1;
+  const rot = time * speed;
+  cube.rotation.x = rot;
+  cube.rotation.y = rot;
+
++  // get the position of the center of the cube
++  cube.updateWorldMatrix(true, false);
++  cube.getWorldPosition(tempV);
++
++  // get the normalized screen coordinate of that position
++  // x and y will be in the -1 to +1 range with x = -1 being
++  // on the left and y = -1 being on the bottom
++  tempV.project(camera);
++
++  // convert the normalized position to CSS coordinates
++  const x = (tempV.x *  .5 + .5) * canvas.clientWidth;
++  const y = (tempV.y * -.5 + .5) * canvas.clientHeight;
++
++  // move the elem to that position
++  elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
+});
+</pre>
+<p>And with that we have labels aligned to their corresponding objects.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/align-html-to-3d.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/align-html-to-3d.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>There are a couple of issues we probably want to deal with.</p>
+<p>One is that if we rotate the objects so they overlap all the labels
+overlap as well.</p>
+<div class="threejs_center"><img src="../resources/images/overlapping-labels.png" style="width: 307px;"></div>
+
+<p>Another is that if we zoom way out so that the objects go outside
+the frustum the labels will still appear.</p>
+<p>A possible solution to the problem of overlapping objects is to use
+the <a href="picking.html">picking code from the article on picking</a>.
+We'll pass in the position of the object on the screen and then
+ask the <code class="notranslate" translate="no">RayCaster</code> to tell us which objects were intersected.
+If our object is not the first one then we are not in the front.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const tempV = new THREE.Vector3();
++const raycaster = new THREE.Raycaster();
+
+...
+
+cubes.forEach((cubeInfo, ndx) =&gt; {
+  const {cube, elem} = cubeInfo;
+  const speed = 1 + ndx * .1;
+  const rot = time * speed;
+  cube.rotation.x = rot;
+  cube.rotation.y = rot;
+
+  // get the position of the center of the cube
+  cube.updateWorldMatrix(true, false);
+  cube.getWorldPosition(tempV);
+
+  // get the normalized screen coordinate of that position
+  // x and y will be in the -1 to +1 range with x = -1 being
+  // on the left and y = -1 being on the bottom
+  tempV.project(camera);
+
++  // ask the raycaster for all the objects that intersect
++  // from the eye toward this object's position
++  raycaster.setFromCamera(tempV, camera);
++  const intersectedObjects = raycaster.intersectObjects(scene.children);
++  // We're visible if the first intersection is this object.
++  const show = intersectedObjects.length &amp;&amp; cube === intersectedObjects[0].object;
++
++  if (!show) {
++    // hide the label
++    elem.style.display = 'none';
++  } else {
++    // un-hide the label
++    elem.style.display = '';
+
+    // convert the normalized position to CSS coordinates
+    const x = (tempV.x *  .5 + .5) * canvas.clientWidth;
+    const y = (tempV.y * -.5 + .5) * canvas.clientHeight;
+
+    // move the elem to that position
+    elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
++  }
+});
+</pre>
+<p>This handles overlapping.</p>
+<p>To handle going outside the frustum we can add this check if the origin of
+the object is outside the frustum by checking <code class="notranslate" translate="no">tempV.z</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-  if (!show) {
++  if (!show || Math.abs(tempV.z) &gt; 1) {
+    // hide the label
+    elem.style.display = 'none';
+</pre>
+<p>This <em>kind of</em> works because the normalized coordinates we computed include a <code class="notranslate" translate="no">z</code>
+value that goes from -1 when at the <code class="notranslate" translate="no">near</code> part of our camera frustum to +1 when
+at the <code class="notranslate" translate="no">far</code> part of our camera frustum.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/align-html-to-3d-w-hiding.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/align-html-to-3d-w-hiding.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>For the frustum check, the solution above fails as we're only checking the origin of the object. For a large
+object. That origin might go outside the frustum but half of the object might still be in the frustum.</p>
+<p>A more correct solution would be to check if the object itself is in the frustum
+or not. Unfortunate that check is slow. For 3 cubes it will not be a problem
+but for many objects it might be.</p>
+<p>Three.js provides some functions to check if an object's bounding sphere is
+in a frustum</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// at init time
+const frustum = new THREE.Frustum();
+const viewProjection = new THREE.Matrix4();
+
+...
+
+// before checking
+camera.updateMatrix();
+camera.updateMatrixWorld();
+camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
+
+...
+
+// then for each mesh
+someMesh.updateMatrix();
+someMesh.updateMatrixWorld();
+
+viewProjection.multiplyMatrices(
+    camera.projectionMatrix, camera.matrixWorldInverse);
+frustum.setFromProjectionMatrix(viewProjection);
+const inFrustum = frustum.contains(someMesh));
+</pre>
+<p>Our current overlapping solution has similar issues. Picking is slow. We could
+use gpu based picking like we covered in the <a href="picking.html">picking
+article</a> but that is also not free. Which solution you
+chose depends on your needs.</p>
+<p>Another issue is the order the labels appear. If we change the code to have
+longer labels</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cubes = [
+-  makeInstance(geometry, 0x44aa88,  0, 'Aqua'),
+-  makeInstance(geometry, 0x8844aa, -2, 'Purple'),
+-  makeInstance(geometry, 0xaa8844,  2, 'Gold'),
++  makeInstance(geometry, 0x44aa88,  0, 'Aqua Colored Box'),
++  makeInstance(geometry, 0x8844aa, -2, 'Purple Colored Box'),
++  makeInstance(geometry, 0xaa8844,  2, 'Gold Colored Box'),
+];
+</pre>
+<p>and set the CSS so these don't wrap</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#labels&gt;div {
++  white-space: nowrap;
+</pre>
+<p>Then we can run into this issue</p>
+<div class="threejs_center"><img src="../resources/images/label-sorting-issue.png" style="width: 401px;"></div>
+
+<p>You can see above the purple box is in the back but its label is in front of the aqua box.</p>
+<p>We can fix this by setting the <code class="notranslate" translate="no">zIndex</code> of each element. The projected position has a <code class="notranslate" translate="no">z</code> value
+that goes from -1 in front to positive 1 in back. <code class="notranslate" translate="no">zIndex</code> is required to be an integer and goes the
+opposite direction meaning for <code class="notranslate" translate="no">zIndex</code> greater values are in front so the following code should work.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// convert the normalized position to CSS coordinates
+const x = (tempV.x *  .5 + .5) * canvas.clientWidth;
+const y = (tempV.y * -.5 + .5) * canvas.clientHeight;
+
+// move the elem to that position
+elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
+
++// set the zIndex for sorting
++elem.style.zIndex = (-tempV.z * .5 + .5) * 100000 | 0;
+</pre>
+<p>Because of the way the projected z value works we need to pick a large number to spread out the values
+otherwise many will have the same value. To make sure the labels don't overlap with other parts of
+the page we can tell the browser to create a new <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">stacking context</a>
+by setting the <code class="notranslate" translate="no">z-index</code> of the container of the labels</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#labels {
+  position: absolute;  /* let us position ourself inside the container */
++  z-index: 0;          /* make a new stacking context so children don't sort with rest of page */
+  left: 0;             /* make our position the top left of the container */
+  top: 0;
+  color: white;
+  z-index: 0;
+}
+</pre>
+<p>and now the labels should always be in the correct order.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/align-html-to-3d-w-sorting.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/align-html-to-3d-w-sorting.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>While we're at it let's do one more example to show one more issue.
+Let's draw a globe like Google Maps and label the countries.</p>
+<p>I found <a href="http://thematicmapping.org/downloads/world_borders.php">this data</a>
+which contains the borders of countries. It's licensed as
+<a href="http://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>.</p>
+<p>I <a href="https://github.com/mrdoob/three.js/blob/master/manual/resources/tools/geo-picking/">wrote some code</a>
+to load the data, and generate country outlines and some JSON data with the names
+of the countries and their locations.</p>
+<div class="threejs_center"><img src="../examples/resources/data/world/country-outlines-4k.png" style="background: black; width: 700px"></div>
+
+<p>The JSON data is an array of entries something like this</p>
+<pre class="prettyprint showlinemods notranslate lang-json" translate="no">[
+  {
+    "name": "Algeria",
+    "min": [
+      -8.667223,
+      18.976387
+    ],
+    "max": [
+      11.986475,
+      37.091385
+    ],
+    "area": 238174,
+    "lat": 28.163,
+    "lon": 2.632,
+    "population": {
+      "2005": 32854159
+    }
+  },
+  ...
+</pre>
+<p>where min, max, lat, lon, are all in latitude and longitude degrees.</p>
+<p>Let's load it up. The code is based on the examples from <a href="optimize-lots-of-objects.html">optimizing lots of
+objects</a> though we are not drawing lots
+of objects we'll be using the same solutions for <a href="rendering-on-demand.html">rendering on
+demand</a>.</p>
+<p>The first thing is to make a sphere and use the outline texture.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const loader = new THREE.TextureLoader();
+  const texture = loader.load('resources/data/world/country-outlines-4k.png', render);
+  const geometry = new THREE.SphereGeometry(1, 64, 32);
+  const material = new THREE.MeshBasicMaterial({map: texture});
+  scene.add(new THREE.Mesh(geometry, material));
+}
+</pre>
+<p>Then let's load the JSON file by first making a loader</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadJSON(url) {
+  const req = await fetch(url);
+  return req.json();
+}
+</pre>
+<p>and then calling it</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">let countryInfos;
+async function loadCountryData() {
+  countryInfos = await loadJSON('resources/data/world/country-info.json');
+     ...
+  }
+  requestRenderIfNotRequested();
+}
+loadCountryData();
+</pre>
+<p>Now let's use that data to generate and place the labels.</p>
+<p>In the article on <a href="optimize-lots-of-objects.html">optimizing lots of objects</a>
+we had setup a small scene graph of helper objects to make it easy to 
+compute latitude and longitude positions on our globe. See that article 
+for an explanation of how they work.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const lonFudge = Math.PI * 1.5;
+const latFudge = Math.PI;
+// these helpers will make it easy to position the boxes
+// We can rotate the lon helper on its Y axis to the longitude
+const lonHelper = new THREE.Object3D();
+// We rotate the latHelper on its X axis to the latitude
+const latHelper = new THREE.Object3D();
+lonHelper.add(latHelper);
+// The position helper moves the object to the edge of the sphere
+const positionHelper = new THREE.Object3D();
+positionHelper.position.z = 1;
+latHelper.add(positionHelper);
+</pre>
+<p>We'll use that to compute a position for each label</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const labelParentElem = document.querySelector('#labels');
+for (const countryInfo of countryInfos) {
+  const {lat, lon, name} = countryInfo;
+
+  // adjust the helpers to point to the latitude and longitude
+  lonHelper.rotation.y = THREE.MathUtils.degToRad(lon) + lonFudge;
+  latHelper.rotation.x = THREE.MathUtils.degToRad(lat) + latFudge;
+
+  // get the position of the lat/lon
+  positionHelper.updateWorldMatrix(true, false);
+  const position = new THREE.Vector3();
+  positionHelper.getWorldPosition(position);
+  countryInfo.position = position;
+
+  // add an element for each country
+  const elem = document.createElement('div');
+  elem.textContent = name;
+  labelParentElem.appendChild(elem);
+  countryInfo.elem = elem;
+</pre>
+<p>The code above looks very similar to the code we wrote for making cube labels
+making an element per label. When we're done we have an array, <code class="notranslate" translate="no">countryInfos</code>,
+with one entry for each country to which we've added an <code class="notranslate" translate="no">elem</code> property for
+the label element for that country and a <code class="notranslate" translate="no">position</code> with its position on the
+globe.</p>
+<p>Just like we did for the cubes we need to update the position of the
+labels and render time.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const tempV = new THREE.Vector3();
+
+function updateLabels() {
+  // exit if we have not yet loaded the JSON file
+  if (!countryInfos) {
+    return;
+  }
+
+  for (const countryInfo of countryInfos) {
+    const {position, elem} = countryInfo;
+
+    // get the normalized screen coordinate of that position
+    // x and y will be in the -1 to +1 range with x = -1 being
+    // on the left and y = -1 being on the bottom
+    tempV.copy(position);
+    tempV.project(camera);
+
+    // convert the normalized position to CSS coordinates
+    const x = (tempV.x *  .5 + .5) * canvas.clientWidth;
+    const y = (tempV.y * -.5 + .5) * canvas.clientHeight;
+
+    // move the elem to that position
+    elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
+
+    // set the zIndex for sorting
+    elem.style.zIndex = (-tempV.z * .5 + .5) * 100000 | 0;
+  }
+}
+</pre>
+<p>You can see the code above is substantially similar to the cube example before.
+The only major difference is we pre-computed the label positions at init time.
+We can do this because the globe never moves. Only our camera moves.</p>
+<p>Lastly we need to call <code class="notranslate" translate="no">updateLabels</code> in our render loop</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
+  renderRequested = false;
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+  }
+
+  controls.update();
+
++  updateLabels();
+
+  renderer.render(scene, camera);
+}
+</pre>
+<p>And this is what we get</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/align-html-elements-to-3d-globe-too-many-labels.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/align-html-elements-to-3d-globe-too-many-labels.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>That is way too many labels!</p>
+<p>We have 2 problems.</p>
+<ol>
+<li><p>Labels facing away from us are showing up.</p>
+</li>
+<li><p>There are too many labels.</p>
+</li>
+</ol>
+<p>For issue #1 we can't really use the <code class="notranslate" translate="no">RayCaster</code> like we did above as there is
+nothing to intersect except the sphere. Instead what we can do is check if that
+particular country is facing away from us or not. This works because the label
+positions are around a sphere. In fact we're using a unit sphere, a sphere with
+a radius of 1.0. That means the positions are already unit directions making
+the math relatively easy.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const tempV = new THREE.Vector3();
++const cameraToPoint = new THREE.Vector3();
++const cameraPosition = new THREE.Vector3();
++const normalMatrix = new THREE.Matrix3();
+
+function updateLabels() {
+  // exit if we have not yet loaded the JSON file
+  if (!countryInfos) {
+    return;
+  }
+
++  const minVisibleDot = 0.2;
++  // get a matrix that represents a relative orientation of the camera
++  normalMatrix.getNormalMatrix(camera.matrixWorldInverse);
++  // get the camera's position
++  camera.getWorldPosition(cameraPosition);
+  for (const countryInfo of countryInfos) {
+    const {position, elem} = countryInfo;
+
++    // Orient the position based on the camera's orientation.
++    // Since the sphere is at the origin and the sphere is a unit sphere
++    // this gives us a camera relative direction vector for the position.
++    tempV.copy(position);
++    tempV.applyMatrix3(normalMatrix);
++
++    // compute the direction to this position from the camera
++    cameraToPoint.copy(position);
++    cameraToPoint.applyMatrix4(camera.matrixWorldInverse).normalize();
++
++    // get the dot product of camera relative direction to this position
++    // on the globe with the direction from the camera to that point.
++    // 1 = facing directly towards the camera
++    // 0 = exactly on tangent of the sphere from the camera
++    // &lt; 0 = facing away
++    const dot = tempV.dot(cameraToPoint);
++
++    // if the orientation is not facing us hide it.
++    if (dot &lt; minVisibleDot) {
++      elem.style.display = 'none';
++      continue;
++    }
++
++    // restore the element to its default display style
++    elem.style.display = '';
+
+    // get the normalized screen coordinate of that position
+    // x and y will be in the -1 to +1 range with x = -1 being
+    // on the left and y = -1 being on the bottom
+    tempV.copy(position);
+    tempV.project(camera);
+
+    // convert the normalized position to CSS coordinates
+    const x = (tempV.x *  .5 + .5) * canvas.clientWidth;
+    const y = (tempV.y * -.5 + .5) * canvas.clientHeight;
+
+    // move the elem to that position
+    countryInfo.elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
+
+    // set the zIndex for sorting
+    elem.style.zIndex = (-tempV.z * .5 + .5) * 100000 | 0;
+  }
+}
+</pre>
+<p>Above we use the positions as a direction and get that direction relative to the
+camera. Then we get the camera relative direction from the camera to that
+position on the globe and take the <em>dot product</em>. The dot product returns the cosine
+of the angle between the to vectors. This gives us a value from -1
+to +1 where -1 means the label is facing the camera, 0 means the label is directly
+on the edge of the sphere relative to the camera, and anything greater than zero is
+behind. We then use that value to show or hide the element.</p>
+<div class="spread">
+  <div>
+    <div data-diagram="dotProduct" style="height: 400px"></div>
+  </div>
+</div>
+
+<p>In the diagram above we can see the dot product of the direction the label is
+facing to direction from the camera to that position. If you rotate the
+direction you'll see the dot product is -1.0 when the direction is directly
+facing the camera, it's 0.0 when exactly on the tangent of the sphere relative
+to the camera or to put it another way it's 0 when the 2 vectors are
+perpendicular to each other, 90 degrees It's greater than zero with the label is
+behind the sphere.</p>
+<p>For issue #2, too many labels we need some way to decide which labels
+to show. One way would be to only show labels for large countries.
+The data we're loading contains min and max values for the area a
+country covers. From that we can compute an area and then use that
+area to decide whether or not to display the country.</p>
+<p>At init time let's compute the area</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const labelParentElem = document.querySelector('#labels');
+for (const countryInfo of countryInfos) {
+  const {lat, lon, min, max, name} = countryInfo;
+
+  // adjust the helpers to point to the latitude and longitude
+  lonHelper.rotation.y = THREE.MathUtils.degToRad(lon) + lonFudge;
+  latHelper.rotation.x = THREE.MathUtils.degToRad(lat) + latFudge;
+
+  // get the position of the lat/lon
+  positionHelper.updateWorldMatrix(true, false);
+  const position = new THREE.Vector3();
+  positionHelper.getWorldPosition(position);
+  countryInfo.position = position;
+
++  // compute the area for each country
++  const width = max[0] - min[0];
++  const height = max[1] - min[1];
++  const area = width * height;
++  countryInfo.area = area;
+
+  // add an element for each country
+  const elem = document.createElement('div');
+  elem.textContent = name;
+  labelParentElem.appendChild(elem);
+  countryInfo.elem = elem;
+}
+</pre>
+<p>Then at render time let's use the area to decide to display the label
+or not</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const large = 20 * 20;
+const maxVisibleDot = 0.2;
+// get a matrix that represents a relative orientation of the camera
+normalMatrix.getNormalMatrix(camera.matrixWorldInverse);
+// get the camera's position
+camera.getWorldPosition(cameraPosition);
+for (const countryInfo of countryInfos) {
+-  const {position, elem} = countryInfo;
++  const {position, elem, area} = countryInfo;
++  // large enough?
++  if (area &lt; large) {
++    elem.style.display = 'none';
++    continue;
++  }
+
+  ...
+</pre>
+<p>Finally, since I'm not sure what good values are for these settings lets
+add a GUI so we can play with them</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
+import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
++import {GUI} from '/examples/jsm/libs/lil-gui.module.min.js';
+</pre>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const settings = {
++  minArea: 20,
++  maxVisibleDot: -0.2,
++};
++const gui = new GUI({width: 300});
++gui.add(settings, 'minArea', 0, 50).onChange(requestRenderIfNotRequested);
++gui.add(settings, 'maxVisibleDot', -1, 1, 0.01).onChange(requestRenderIfNotRequested);
+
+function updateLabels() {
+  if (!countryInfos) {
+    return;
+  }
+
+-  const large = 20 * 20;
+-  const maxVisibleDot = -0.2;
++  const large = settings.minArea * settings.minArea;
+  // get a matrix that represents a relative orientation of the camera
+  normalMatrix.getNormalMatrix(camera.matrixWorldInverse);
+  // get the camera's position
+  camera.getWorldPosition(cameraPosition);
+  for (const countryInfo of countryInfos) {
+
+    ...
+
+    // if the orientation is not facing us hide it.
+-    if (dot &gt; maxVisibleDot) {
++    if (dot &gt; settings.maxVisibleDot) {
+      elem.style.display = 'none';
+      continue;
+    }
+</pre>
+<p>and here's the result</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/align-html-elements-to-3d-globe.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/align-html-elements-to-3d-globe.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>You can see as you rotate the earth labels that go behind disappear.
+Adjust the <code class="notranslate" translate="no">minVisibleDot</code> to see the cutoff change.
+You can also adjust the <code class="notranslate" translate="no">minArea</code> value to see larger or smaller countries
+appear.</p>
+<p>The more I worked on this the more I realized just how much
+work is put into Google Maps. They have also have to decide which labels to
+show. I'm pretty sure they use all kinds of criteria. For example your current
+location, your default language setting, your account settings if you have an
+account, they probably use population or popularity, they might give priority
+to the countries in the center of the view, etc ... Lots to think about.</p>
+<p>In any case I hope these examples gave you some idea of how to align HTML
+elements with your 3D. A few things I might change.</p>
+<p>Next up let's make it so you can <a href="indexed-textures.html">pick and highlight a country</a>.</p>
+<p><link rel="stylesheet" href="../resources/threejs-align-html-elements-to-3d.css"></p>
+<script type="module" src="../resources/threejs-align-html-elements-to-3d.js"></script>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 256 - 0
manual/en/backgrounds.html

@@ -0,0 +1,256 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Backgrounds and Skyboxes</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Backgrounds and Skyboxes">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Backgrounds and Skyboxes</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>Most of the articles here use a solid color for a background.</p>
+<p>Adding as static background can be as simple as setting some CSS. Taking
+an example from <a href="responsive.html">the article on making THREE.js responsive</a>
+we only need to change 2 things.</p>
+<p>We need to add some CSS to our canvas to set its background to an image</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;style&gt;
+body {
+    margin: 0;
+}
+#c {
+    width: 100%;
+    height: 100%;
+    display: block;
++    background: url(resources/images/daikanyama.jpg) no-repeat center center;
++    background-size: cover;
+}
+&lt;/style&gt;
+</pre>
+<p>and we need to tell the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a> to use <code class="notranslate" translate="no">alpha</code> so places we are not
+drawing anything are transparent.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
+  const canvas = document.querySelector('#c');
+-  const renderer = new THREE.WebGLRenderer({canvas});
++  const renderer = new THREE.WebGLRenderer({
++    canvas,
++    alpha: true,
++  });
+</pre>
+<p>And we get a background.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/background-css.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/background-css.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>If we want the background to be able to be affected by <a href="post-processing.html">post processing
+effects</a> then we need to draw the background using
+THREE.js.</p>
+<p>THREE.js makes this some what simple. We can just set the background of the scene to
+a texture.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loader = new THREE.TextureLoader();
+const bgTexture = loader.load('resources/images/daikanyama.jpg');
+scene.background = bgTexture;
+</pre>
+<p>which gives us</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/background-scene-background.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/background-scene-background.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>This gets us a background image but its stretched to fit the screen.</p>
+<p>We can solve this issue by setting the <code class="notranslate" translate="no">repeat</code> and <code class="notranslate" translate="no">offset</code> properties of
+the texture to show only a portion of image.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
+
+   ...
+
++  // Set the repeat and offset properties of the background texture
++  // to keep the image's aspect correct.
++  // Note the image may not have loaded yet.
++  const canvasAspect = canvas.clientWidth / canvas.clientHeight;
++  const imageAspect = bgTexture.image ? bgTexture.image.width / bgTexture.image.height : 1;
++  const aspect = imageAspect / canvasAspect;
++
++  bgTexture.offset.x = aspect &gt; 1 ? (1 - 1 / aspect) / 2 : 0;
++  bgTexture.repeat.x = aspect &gt; 1 ? 1 / aspect : 1;
++
++  bgTexture.offset.y = aspect &gt; 1 ? 0 : (1 - aspect) / 2;
++  bgTexture.repeat.y = aspect &gt; 1 ? 1 : aspect;
+
+  ...
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>and now THREE.js drawing the background. There is no visible difference from
+the CSS version at the top but now if we used a <a href="post-processing.html">post processing
+effect</a> the background would be affected too.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/background-scene-background-fixed-aspect.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/background-scene-background-fixed-aspect.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Of course a static background is not usually what we want in a 3D scene. Instead
+we usually want some kind of <em>skybox</em>. A skybox is just that, box with the sky
+draw on it. We put the camera inside the box and it looks like there is a sky in
+the background.</p>
+<p>The most common way to implement a skybox is to make a cube, apply a texture to
+it, draw it from the inside. On each side of the cube put a texture (using
+texture coordinates) that looks like some image of the horizon. It's also often
+common to use a sky sphere or a sky dome with a texture drawn on it. You can
+probably figure that one out on your own. Just make a cube or sphere, 
+<a href="textures.html">apply a texture</a>, mark it as <code class="notranslate" translate="no">THREE.BackSide</code> so we 
+render the inside instead of the outside, and either put it in your scene directly 
+or like above, or, make 2 scenes, a special one to draw the skybox/sphere/dome and the
+normal one to draw everything else. You'd use your normal <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> to
+draw. No need for the <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>.</p>
+<p>Another solution is to use a <em>Cubemap</em>. A Cubemap is a special kind of texture
+that has 6 sides, the sides of a cube. Instead of using standard texture
+coordinates it uses a direction from the center pointing outward to decide where
+to get a color.</p>
+<p>Here are the 6 images of a cubemap from the computer history museum in Mountain
+View, California.</p>
+<div class="threejs_center">
+  <img src="../examples/resources/images/cubemaps/computer-history-museum/pos-x.jpg" style="width: 200px" class="border">
+  <img src="../examples/resources/images/cubemaps/computer-history-museum/neg-x.jpg" style="width: 200px" class="border">
+  <img src="../examples/resources/images/cubemaps/computer-history-museum/pos-y.jpg" style="width: 200px" class="border">
+</div>
+<div class="threejs_center">
+  <img src="../examples/resources/images/cubemaps/computer-history-museum/neg-y.jpg" style="width: 200px" class="border">
+  <img src="../examples/resources/images/cubemaps/computer-history-museum/pos-z.jpg" style="width: 200px" class="border">
+  <img src="../examples/resources/images/cubemaps/computer-history-museum/neg-z.jpg" style="width: 200px" class="border">
+</div>
+
+<p>To use them we use <a href="/docs/#api/en/loaders/CubeTextureLoader"><code class="notranslate" translate="no">CubeTextureLoader</code></a> to load them and then use that as a the
+scene's background.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const loader = new THREE.CubeTextureLoader();
+  const texture = loader.load([
+    'resources/images/cubemaps/computer-history-museum/pos-x.jpg',
+    'resources/images/cubemaps/computer-history-museum/neg-x.jpg',
+    'resources/images/cubemaps/computer-history-museum/pos-y.jpg',
+    'resources/images/cubemaps/computer-history-museum/neg-y.jpg',
+    'resources/images/cubemaps/computer-history-museum/pos-z.jpg',
+    'resources/images/cubemaps/computer-history-museum/neg-z.jpg',
+  ]);
+  scene.background = texture;
+}
+</pre>
+<p>At render time we don't need to adjust the texture like we did above</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
+
+   ...
+
+-  // Set the repeat and offset properties of the background texture
+-  // to keep the image's aspect correct.
+-  // Note the image may not have loaded yet.
+-  const canvasAspect = canvas.clientWidth / canvas.clientHeight;
+-  const imageAspect = bgTexture.image ? bgTexture.image.width / bgTexture.image.height : 1;
+-  const aspect = imageAspect / canvasAspect;
+-
+-  bgTexture.offset.x = aspect &gt; 1 ? (1 - 1 / aspect) / 2 : 0;
+-  bgTexture.repeat.x = aspect &gt; 1 ? 1 / aspect : 1;
+-
+-  bgTexture.offset.y = aspect &gt; 1 ? 0 : (1 - aspect) / 2;
+-  bgTexture.repeat.y = aspect &gt; 1 ? 1 : aspect;
+
+  ...
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>Let's add some controls in so we can rotate the camera.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+</pre>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+-const far = 5;
++const far = 100;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+-camera.position.z = 2;
++camera.position.z = 3;
+
++const controls = new OrbitControls(camera, canvas);
++controls.target.set(0, 0, 0);
++controls.update();
+</pre>
+<p>and try it out. Drag on the example to rotate the camera and see the cubemap
+surrounds us.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/background-cubemap.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/background-cubemap.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Another option is to use an Equirectangular map. This is the kind of picture a
+<a href="https://google.com/search?q=360+camera">360 camera</a> takes.</p>
+<p><a href="https://hdrihaven.com/hdri/?h=tears_of_steel_bridge">Here's one</a> I found from
+<a href="https://hdrihaven.com">this site</a>.</p>
+<div class="threejs_center"><img src="../examples/resources/images/equirectangularmaps/tears_of_steel_bridge_2k.jpg" style="width: 600px"></div>
+
+<p>It's not much different. First we load the equirectangular image as a texture
+and then, in the callback after it has loaded, we can call <a href="/docs/#api/en/renderers/WebGLCubeRenderTarget.fromEquirectangularTexture"><code class="notranslate" translate="no">WebGLCubeRenderTarget.fromEquirectangularTexture</code></a>
+which will generate a cubemap from the equirectangular texture for us.
+We pass in the size we want the cubemap to be to <a href="/docs/#api/en/renderers/WebGLCubeRenderTarget"><code class="notranslate" translate="no">WebGLCubeRenderTarget</code></a>.
+Passing in the height of the equirectangular image seems like a good bet.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+-  const loader = new THREE.CubeTextureLoader();
+-  const texture = loader.load([
+-    'resources/images/cubemaps/computer-history-museum/pos-x.jpg',
+-    'resources/images/cubemaps/computer-history-museum/neg-x.jpg',
+-    'resources/images/cubemaps/computer-history-museum/pos-y.jpg',
+-    'resources/images/cubemaps/computer-history-museum/neg-y.jpg',
+-    'resources/images/cubemaps/computer-history-museum/pos-z.jpg',
+-    'resources/images/cubemaps/computer-history-museum/neg-z.jpg',
+-  ]);
+-  scene.background = texture;
++  const loader = new THREE.TextureLoader();
++  const texture = loader.load(
++    'resources/images/equirectangularmaps/tears_of_steel_bridge_2k.jpg',
++    () =&gt; {
++      const rt = new THREE.WebGLCubeRenderTarget(texture.image.height);
++      rt.fromEquirectangularTexture(renderer, texture);
++      scene.background = rt.texture;
++    });
+}
+</pre>
+<p>And that's all there is to it.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/background-equirectangularmap.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/background-equirectangularmap.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Rather than do it at load time you can also convert an equirectangular image
+to a cubemap beforehand. <a href="https://matheowis.github.io/HDRI-to-CubeMap/">Here's a site that will do it for you</a>.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 314 - 0
manual/en/billboards.html

@@ -0,0 +1,314 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Billboards</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Billboards">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Billboards</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>In <a href="canvas-textures.html">a previous article</a> we used a <a href="/docs/#api/en/textures/CanvasTexture"><code class="notranslate" translate="no">CanvasTexture</code></a>
+to make labels / badges on characters. Sometimes we'd like to make labels or
+other things that always face the camera. Three.js provides the <a href="/docs/#api/en/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a> and
+<a href="/docs/#api/en/materials/SpriteMaterial"><code class="notranslate" translate="no">SpriteMaterial</code></a> to make this happen.</p>
+<p>Let's change the badge example from <a href="canvas-textures.html">the article on canvas textures</a>
+to use <a href="/docs/#api/en/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a> and <a href="/docs/#api/en/materials/SpriteMaterial"><code class="notranslate" translate="no">SpriteMaterial</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makePerson(x, labelWidth, size, name, color) {
+  const canvas = makeLabelCanvas(labelWidth, size, name);
+  const texture = new THREE.CanvasTexture(canvas);
+  // because our canvas is likely not a power of 2
+  // in both dimensions set the filtering appropriately.
+  texture.minFilter = THREE.LinearFilter;
+  texture.wrapS = THREE.ClampToEdgeWrapping;
+  texture.wrapT = THREE.ClampToEdgeWrapping;
+
+-  const labelMaterial = new THREE.MeshBasicMaterial({
++  const labelMaterial = new THREE.SpriteMaterial({
+    map: texture,
+-    side: THREE.DoubleSide,
+    transparent: true,
+  });
+
+  const root = new THREE.Object3D();
+  root.position.x = x;
+
+  const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
+  root.add(body);
+  body.position.y = bodyHeight / 2;
+
+  const head = new THREE.Mesh(headGeometry, bodyMaterial);
+  root.add(head);
+  head.position.y = bodyHeight + headRadius * 1.1;
+
+-  const label = new THREE.Mesh(labelGeometry, labelMaterial);
++  const label = new THREE.Sprite(labelMaterial);
+  root.add(label);
+  label.position.y = bodyHeight * 4 / 5;
+  label.position.z = bodyRadiusTop * 1.01;
+</pre>
+<p>and the labels now always face the camera</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-labels-w-sprites.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/billboard-labels-w-sprites.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>One problem is from certain angles the labels now intersect the
+characters. </p>
+<div class="threejs_center"><img src="../resources/images/billboard-label-z-issue.png" style="width: 455px;"></div>
+
+<p>We can move the position of the labels to fix.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+// if units are meters then 0.01 here makes size
++// of the label into centimeters.
++const labelBaseScale = 0.01;
+const label = new THREE.Sprite(labelMaterial);
+root.add(label);
+-label.position.y = bodyHeight * 4 / 5;
+-label.position.z = bodyRadiusTop * 1.01;
++label.position.y = head.position.y + headRadius + size * labelBaseScale;
+
+-// if units are meters then 0.01 here makes size
+-// of the label into centimeters.
+-const labelBaseScale = 0.01;
+label.scale.x = canvas.width  * labelBaseScale;
+label.scale.y = canvas.height * labelBaseScale;
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-labels-w-sprites-adjust-height.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/billboard-labels-w-sprites-adjust-height.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Another thing we can do with billboards is draw facades.</p>
+<p>Instead of drawing 3D objects we draw 2D planes with an image
+of 3D objects. This is often faster than drawing 3D objects.</p>
+<p>For example let's make a scene with grid of trees. We'll make each
+tree from a cylinder for the base and a cone for the top.</p>
+<p>First we make the cone and cylinder geometry and materials that
+all the trees will share</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const trunkRadius = .2;
+const trunkHeight = 1;
+const trunkRadialSegments = 12;
+const trunkGeometry = new THREE.CylinderGeometry(
+    trunkRadius, trunkRadius, trunkHeight, trunkRadialSegments);
+
+const topRadius = trunkRadius * 4;
+const topHeight = trunkHeight * 2;
+const topSegments = 12;
+const topGeometry = new THREE.ConeGeometry(
+    topRadius, topHeight, topSegments);
+
+const trunkMaterial = new THREE.MeshPhongMaterial({color: 'brown'});
+const topMaterial = new THREE.MeshPhongMaterial({color: 'green'});
+</pre>
+<p>Then we'll make a function that makes a <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> each for the trunk and top
+of a tree and parents both to an <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeTree(x, z) {
+  const root = new THREE.Object3D();
+  const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
+  trunk.position.y = trunkHeight / 2;
+  root.add(trunk);
+
+  const top = new THREE.Mesh(topGeometry, topMaterial);
+  top.position.y = trunkHeight + topHeight / 2;
+  root.add(top);
+
+  root.position.set(x, 0, z);
+  scene.add(root);
+
+  return root;
+}
+</pre>
+<p>Then we'll make a loop to place a grid of trees.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">for (let z = -50; z &lt;= 50; z += 10) {
+  for (let x = -50; x &lt;= 50; x += 10) {
+    makeTree(x, z);
+  }
+}
+</pre>
+<p>Let's also add a ground plane while we're at it</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// add ground
+{
+  const size = 400;
+  const geometry = new THREE.PlaneGeometry(size, size);
+  const material = new THREE.MeshPhongMaterial({color: 'gray'});
+  const mesh = new THREE.Mesh(geometry, material);
+  mesh.rotation.x = Math.PI * -0.5;
+  scene.add(mesh);
+}
+</pre>
+<p>and change the background to light blue</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+-scene.background = new THREE.Color('white');
++scene.background = new THREE.Color('lightblue');
+</pre>
+<p>and we get a grid of trees</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-trees-no-billboards.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/billboard-trees-no-billboards.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>There are 11x11 or 121 trees. Each tree is made from a 12 polygon
+cone and a 48 polygon trunk so each tree is 60 polygons. 121 * 60
+is 7260 polygons. That's not that many but of course a more detailed
+3D tree might be 1000-3000 polygons. If they were 3000 polygons each
+then 121 trees would be 363000 polygons to draw.</p>
+<p>Using facades we can bring that number down.</p>
+<p>We could manually create a facade in some painting program but let's write 
+some code to try to generate one.</p>
+<p>Let's write some code to render an object to a texture
+using a <code class="notranslate" translate="no">RenderTarget</code>. We covered rendering to a <code class="notranslate" translate="no">RenderTarget</code>
+in <a href="rendertargets.html">the article on render targets</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
+  const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
+  const halfFovY = THREE.MathUtils.degToRad(camera.fov * .5);
+  const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
+
+  camera.position.copy(boxCenter);
+  camera.position.z += distance;
+
+  // pick some near and far values for the frustum that
+  // will contain the box.
+  camera.near = boxSize / 100;
+  camera.far = boxSize * 100;
+
+  camera.updateProjectionMatrix();
+}
+
+function makeSpriteTexture(textureSize, obj) {
+  const rt = new THREE.WebGLRenderTarget(textureSize, textureSize);
+
+  const aspect = 1;  // because the render target is square
+  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+
+  scene.add(obj);
+
+  // compute the box that contains obj
+  const box = new THREE.Box3().setFromObject(obj);
+
+  const boxSize = box.getSize(new THREE.Vector3());
+  const boxCenter = box.getCenter(new THREE.Vector3());
+
+  // set the camera to frame the box
+  const fudge = 1.1;
+  const size = Math.max(...boxSize.toArray()) * fudge;
+  frameArea(size, size, boxCenter, camera);
+
+  renderer.autoClear = false;
+  renderer.setRenderTarget(rt);
+  renderer.render(scene, camera);
+  renderer.setRenderTarget(null);
+  renderer.autoClear = true;
+
+  scene.remove(obj);
+
+  return {
+    position: boxCenter.multiplyScalar(fudge),
+    scale: size,
+    texture: rt.texture,
+  };
+}
+</pre>
+<p>Some things to note about the code above:</p>
+<p>We're using the field of view (<code class="notranslate" translate="no">fov</code>) defined above this code.</p>
+<p>We're computing a box that contains the tree the same way
+we did in <a href="load-obj.html">the article on loading a .obj file</a>
+with a few minor changes.</p>
+<p>We call <code class="notranslate" translate="no">frameArea</code> again adapted <a href="load-obj.html">the article on loading a .obj file</a>.
+In this case we compute how far the camera needs to be away from the object
+given its field of view to contain the object. We then position the camera -z that distance
+from the center of the box that contains the object.</p>
+<p>We multiply the size we want to fit by 1.1 (<code class="notranslate" translate="no">fudge</code>) to make sure the tree fits
+completely in the render target. The issue here is the size we're using to
+calculate if the object fits in the camera's view is not taking into account
+that the very edges of the object will end up dipping outside area we
+calculated. We could compute how to make 100% of the box fit but that would
+waste space as well so instead we just <em>fudge</em> it.</p>
+<p>Then we render to the render target and remove the object from
+the scene. </p>
+<p>It's important to note we need the lights in the scene but we
+need to make sure nothing else is in the scene.</p>
+<p>We also need to not set a background color on the scene</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+-scene.background = new THREE.Color('lightblue');
+</pre>
+<p>Finally we've made the texture we return it and the position and scale we
+need to make the facade so that it will appear to be in the same place.</p>
+<p>We then make a tree and call this code and pass it in</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// make billboard texture
+const tree = makeTree(0, 0);
+const facadeSize = 64;
+const treeSpriteInfo = makeSpriteTexture(facadeSize, tree);
+</pre>
+<p>We can then make a grid of facades instead of a grid of tree models</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function makeSprite(spriteInfo, x, z) {
++  const {texture, offset, scale} = spriteInfo;
++  const mat = new THREE.SpriteMaterial({
++    map: texture,
++    transparent: true,
++  });
++  const sprite = new THREE.Sprite(mat);
++  scene.add(sprite);
++  sprite.position.set(
++      offset.x + x,
++      offset.y,
++      offset.z + z);
++  sprite.scale.set(scale, scale, scale);
++}
+
+for (let z = -50; z &lt;= 50; z += 10) {
+  for (let x = -50; x &lt;= 50; x += 10) {
+-    makeTree(x, z);
++    makeSprite(treeSpriteInfo, x, z);
+  }
+}
+</pre>
+<p>In the code above we apply the offset and scale needed to position the facade so it
+appears the same place the original tree would have appeared.</p>
+<p>Now that we're done making the tree facade texture we can set the background again</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">scene.background = new THREE.Color('lightblue');
+</pre>
+<p>and now we get a scene of tree facades</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-trees-static-billboards.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/billboard-trees-static-billboards.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Compare to the trees models above and you can see it looks fairly similar.
+We used a low-res texture, just 64x64 pixels so the facades are blocky.
+You could increase the resolution. Often facades are used only in the far
+distance when they are fairly small so a low-res texture is enough and
+it saves on drawing detailed trees that are only a few pixels big when
+far away.</p>
+<p>Another issue is we are only viewing the tree from one side. This is often
+solved by rendering more facades, say from 8 directions around the object
+and then setting which facade to show based on which direction the camera
+is looking at the facade.</p>
+<p>Whether or not you use facades is up to you but hopefully this article
+gave you some ideas and suggested some solutions if you decide to use them.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 555 - 0
manual/en/cameras.html

@@ -0,0 +1,555 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Cameras</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Cameras">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Cameras</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This article is one in a series of articles about three.js.
+The first article was <a href="fundamentals.html">about fundamentals</a>.
+If you haven't read that yet you might want to start there.</p>
+<p>Let's talk about cameras in three.js. We covered some of this in the <a href="fundamentals.html">first article</a> but we'll cover it in more detail here.</p>
+<p>The most common camera in three.js and the one we've been using up to this point is
+the <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a>. It gives a 3d view where things in the distance appear
+smaller than things up close.</p>
+<p>The <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> defines a <em>frustum</em>. <a href="https://en.wikipedia.org/wiki/Frustum">A <em>frustum</em> is a solid pyramid shape with
+the tip cut off</a>.
+By name of a solid I mean for example a cube, a cone, a sphere, a cylinder,
+and a frustum are all names of different kinds of solids.</p>
+<div class="spread">
+  <div><div data-diagram="shapeCube"></div><div>cube</div></div>
+  <div><div data-diagram="shapeCone"></div><div>cone</div></div>
+  <div><div data-diagram="shapeSphere"></div><div>sphere</div></div>
+  <div><div data-diagram="shapeCylinder"></div><div>cylinder</div></div>
+  <div><div data-diagram="shapeFrustum"></div><div>frustum</div></div>
+</div>
+
+<p>I only point that out because I didn't know it for years. Some book or page would mention
+<em>frustum</em> and my eyes would glaze over. Understanding it's the name of a type of solid
+shape made those descriptions suddenly make more sense 😅</p>
+<p>A <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> defines its frustum based on 4 properties. <code class="notranslate" translate="no">near</code> defines where the
+front of the frustum starts. <code class="notranslate" translate="no">far</code> defines where it ends. <code class="notranslate" translate="no">fov</code>, the field of view, defines
+how tall the front and back of the frustum are by computing the correct height to get
+the specified field of view at <code class="notranslate" translate="no">near</code> units from the camera. The <code class="notranslate" translate="no">aspect</code> defines how
+wide the front and back of the frustum are. The width of the frustum is just the height
+multiplied by the aspect.</p>
+<p><img src="../resources/frustum-3d.svg" width="500" class="threejs_center"></p>
+<p>Let's use the scene from <a href="lights.html">the previous article</a> that has a ground
+plane, a sphere, and a cube and make it so we can adjust the camera's settings.</p>
+<p>To do that we'll make a <code class="notranslate" translate="no">MinMaxGUIHelper</code> for the <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings so <code class="notranslate" translate="no">far</code>
+is always greater than <code class="notranslate" translate="no">near</code>. It will have <code class="notranslate" translate="no">min</code> and <code class="notranslate" translate="no">max</code> properties that lil-gui
+will adjust. When adjusted they'll set the 2 properties we specify.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class MinMaxGUIHelper {
+  constructor(obj, minProp, maxProp, minDif) {
+    this.obj = obj;
+    this.minProp = minProp;
+    this.maxProp = maxProp;
+    this.minDif = minDif;
+  }
+  get min() {
+    return this.obj[this.minProp];
+  }
+  set min(v) {
+    this.obj[this.minProp] = v;
+    this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);
+  }
+  get max() {
+    return this.obj[this.maxProp];
+  }
+  set max(v) {
+    this.obj[this.maxProp] = v;
+    this.min = this.min;  // this will call the min setter
+  }
+}
+</pre>
+<p>Now we can setup our GUI like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function updateCamera() {
+  camera.updateProjectionMatrix();
+}
+
+const gui = new GUI();
+gui.add(camera, 'fov', 1, 180).onChange(updateCamera);
+const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
+gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
+gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
+</pre>
+<p>Anytime the camera's settings change we need to call the camera's
+<a href="/docs/#api/en/cameras/PerspectiveCamera#updateProjectionMatrix"><code class="notranslate" translate="no">updateProjectionMatrix</code></a> function
+so we made a function called <code class="notranslate" translate="no">updateCamera</code> add passed it to lil-gui to call it when things change.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-perspective.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cameras-perspective.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>You can adjust the values and see how they work. Note we didn't make <code class="notranslate" translate="no">aspect</code> settable since
+it's taken from the size of the window so if you want to adjust the aspect open the example
+in a new window and then size the window.</p>
+<p>Still, I think it's a little hard to see so let's change the example so it has 2 cameras.
+One will show our scene as we see it above, the other will show another camera looking at the
+scene the first camera is drawing and showing that camera's frustum.</p>
+<p>To do this we can use the scissor function of three.js.
+Let's change it to draw 2 scenes with 2 cameras side by side using the scissor function</p>
+<p>First off let's use some HTML and CSS to define 2 side by side elements. This will also
+help us with events so both cameras can easily have their own <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
+  &lt;canvas id="c"&gt;&lt;/canvas&gt;
++  &lt;div class="split"&gt;
++     &lt;div id="view1" tabindex="1"&gt;&lt;/div&gt;
++     &lt;div id="view2" tabindex="2"&gt;&lt;/div&gt;
++  &lt;/div&gt;
+&lt;/body&gt;
+</pre>
+<p>And the CSS that will make those 2 views show up side by side overlaid on top of
+the canvas</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">.split {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+}
+.split&gt;div {
+  width: 100%;
+  height: 100%;
+}
+</pre>
+<p>Then in our code we'll add a <a href="/docs/#api/en/helpers/CameraHelper"><code class="notranslate" translate="no">CameraHelper</code></a>. A <a href="/docs/#api/en/helpers/CameraHelper"><code class="notranslate" translate="no">CameraHelper</code></a> draws the frustum for a <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cameraHelper = new THREE.CameraHelper(camera);
+
+...
+
+scene.add(cameraHelper);
+</pre>
+<p>Now let's look up the 2 view elements.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const view1Elem = document.querySelector('#view1');
+const view2Elem = document.querySelector('#view2');
+</pre>
+<p>And we'll set our existing <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> to respond to the first
+view element only.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const controls = new OrbitControls(camera, canvas);
++const controls = new OrbitControls(camera, view1Elem);
+</pre>
+<p>Let's make a second <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> and a second <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.
+The second <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> is tied to the second camera and gets input
+from the second view element.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const camera2 = new THREE.PerspectiveCamera(
+  60,  // fov
+  2,   // aspect
+  0.1, // near
+  500, // far
+);
+camera2.position.set(40, 10, 30);
+camera2.lookAt(0, 5, 0);
+
+const controls2 = new OrbitControls(camera2, view2Elem);
+controls2.target.set(0, 5, 0);
+controls2.update();
+</pre>
+<p>Finally we need to render the scene from the point of view of each
+camera using the scissor function to only render to part of the canvas.</p>
+<p>Here is a function that given an element will compute the rectangle
+of that element that overlaps the canvas. It will then set the scissor
+and viewport to that rectangle and return the aspect for that size.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function setScissorForElement(elem) {
+  const canvasRect = canvas.getBoundingClientRect();
+  const elemRect = elem.getBoundingClientRect();
+
+  // compute a canvas relative rectangle
+  const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
+  const left = Math.max(0, elemRect.left - canvasRect.left);
+  const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
+  const top = Math.max(0, elemRect.top - canvasRect.top);
+
+  const width = Math.min(canvasRect.width, right - left);
+  const height = Math.min(canvasRect.height, bottom - top);
+
+  // setup the scissor to only render to that part of the canvas
+  const positiveYUpBottom = canvasRect.height - bottom;
+  renderer.setScissor(left, positiveYUpBottom, width, height);
+  renderer.setViewport(left, positiveYUpBottom, width, height);
+
+  // return the aspect
+  return width / height;
+}
+</pre>
+<p>And now we can use that function to draw the scene twice in our <code class="notranslate" translate="no">render</code> function</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">  function render() {
+
+-    if (resizeRendererToDisplaySize(renderer)) {
+-      const canvas = renderer.domElement;
+-      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+-      camera.updateProjectionMatrix();
+-    }
+
++    resizeRendererToDisplaySize(renderer);
++
++    // turn on the scissor
++    renderer.setScissorTest(true);
++
++    // render the original view
++    {
++      const aspect = setScissorForElement(view1Elem);
++
++      // adjust the camera for this aspect
++      camera.aspect = aspect;
++      camera.updateProjectionMatrix();
++      cameraHelper.update();
++
++      // don't draw the camera helper in the original view
++      cameraHelper.visible = false;
++
++      scene.background.set(0x000000);
++
++      // render
++      renderer.render(scene, camera);
++    }
++
++    // render from the 2nd camera
++    {
++      const aspect = setScissorForElement(view2Elem);
++
++      // adjust the camera for this aspect
++      camera2.aspect = aspect;
++      camera2.updateProjectionMatrix();
++
++      // draw the camera helper in the 2nd view
++      cameraHelper.visible = true;
++
++      scene.background.set(0x000040);
++
++      renderer.render(scene, camera2);
++    }
+
+-    renderer.render(scene, camera);
+
+    requestAnimationFrame(render);
+  }
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>The code above sets the background color of the scene when rendering the
+second view to dark blue just to make it easier to distinguish the two views.</p>
+<p>We can also remove our <code class="notranslate" translate="no">updateCamera</code> code since we're updating everything
+in the <code class="notranslate" translate="no">render</code> function.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function updateCamera() {
+-  camera.updateProjectionMatrix();
+-}
+
+const gui = new GUI();
+-gui.add(camera, 'fov', 1, 180).onChange(updateCamera);
++gui.add(camera, 'fov', 1, 180);
+const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
+-gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
+-gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
++gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near');
++gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far');
+</pre>
+<p>And now you can use one view to see the frustum of the other.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-perspective-2-scenes.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cameras-perspective-2-scenes.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>On the left you can see the original view and on the right you can
+see a view showing the frustum of the camera on the left. As you adjust
+<code class="notranslate" translate="no">near</code>, <code class="notranslate" translate="no">far</code>, <code class="notranslate" translate="no">fov</code> and move the camera with mouse you can see that
+only what's inside the frustum shown on the right appears in the scene on
+the left.</p>
+<p>Adjust <code class="notranslate" translate="no">near</code> up to around 20 and you'll easily see the front of objects
+disappear as they are no longer in the frustum. Adjust <code class="notranslate" translate="no">far</code> below about 35
+and you'll start to see the ground plane disappear as it's no longer in
+the frustum.</p>
+<p>This brings up the question, why not just set <code class="notranslate" translate="no">near</code> to 0.0000000001 and <code class="notranslate" translate="no">far</code>
+to 10000000000000 or something like that so you can just see everything?
+The reason is your GPU only has so much precision to decide if something
+is in front or behind something else. That precision is spread out between
+<code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code>. Worse, by default the precision close the camera is detailed
+and the precision far from the camera is coarse. The units start with <code class="notranslate" translate="no">near</code>
+and slowly expand as they approach <code class="notranslate" translate="no">far</code>.</p>
+<p>Starting with the top example, let's change the code to insert 20 spheres in a
+row.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const sphereRadius = 3;
+  const sphereWidthDivisions = 32;
+  const sphereHeightDivisions = 16;
+  const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
+  const numSpheres = 20;
+  for (let i = 0; i &lt; numSpheres; ++i) {
+    const sphereMat = new THREE.MeshPhongMaterial();
+    sphereMat.color.setHSL(i * .73, 1, 0.5);
+    const mesh = new THREE.Mesh(sphereGeo, sphereMat);
+    mesh.position.set(-sphereRadius - 1, sphereRadius + 2, i * sphereRadius * -2.2);
+    scene.add(mesh);
+  }
+}
+</pre>
+<p>and let's set <code class="notranslate" translate="no">near</code> to 0.00001</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 45;
+const aspect = 2;  // the canvas default
+-const near = 0.1;
++const near = 0.00001;
+const far = 100;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+</pre>
+<p>We also need to tweak the GUI code a little to allow 0.00001 if the value is edited</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
++gui.add(minMaxGUIHelper, 'min', 0.00001, 50, 0.00001).name('near').onChange(updateCamera);
+</pre>
+<p>What do you think will happen?</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-z-fighting.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cameras-z-fighting.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>This is an example of <em>z fighting</em> where the GPU on your computer does not have
+enough precision to decide which pixels are in front and which pixels are behind.</p>
+<p>Just in case the issue doesn't show on your machine here's what I see on mine</p>
+<div class="threejs_center"><img src="../resources/images/z-fighting.png" style="width: 570px;"></div>
+
+<p>One solution is to tell three.js use to a different method to compute which
+pixels are in front and which are behind. We can do that by enabling
+<code class="notranslate" translate="no">logarithmicDepthBuffer</code> when we create the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const renderer = new THREE.WebGLRenderer({canvas});
++const renderer = new THREE.WebGLRenderer({
++  canvas,
++  logarithmicDepthBuffer: true,
++});
+</pre>
+<p>and with that it might work</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-logarithmic-depth-buffer.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cameras-logarithmic-depth-buffer.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>If this didn't fix the issue for you then you've run into one reason why
+you can't always use this solution. That reason is because only certain GPUs
+support it. As of September 2018 almost no mobile devices support this
+solution whereas most desktops do.</p>
+<p>Another reason not to choose this solution is it can be significantly slower
+than the standard solution.</p>
+<p>Even with this solution there is still limited resolution. Make <code class="notranslate" translate="no">near</code> even
+smaller or <code class="notranslate" translate="no">far</code> even bigger and you'll eventually run into the same issues.</p>
+<p>What that means is that you should always make an effort to choose a <code class="notranslate" translate="no">near</code>
+and <code class="notranslate" translate="no">far</code> setting that fits your use case. Set <code class="notranslate" translate="no">near</code> as far away from the camera
+as you can and not have things disappear. Set <code class="notranslate" translate="no">far</code> as close to the camera
+as you can and not have things disappear. If you're trying to draw a giant
+scene and show a close up of someone's face so you can see their eyelashes
+while in the background you can see all the way to mountains 50 kilometers
+in the distance well then you'll need to find other creative solutions that
+maybe we'll go over later. For now, just be aware you should take care
+to choose appropriate <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> values for your needs.</p>
+<p>The 2nd most common camera is the <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>. Rather than
+specify a frustum it specifies a box with the settings <code class="notranslate" translate="no">left</code>, <code class="notranslate" translate="no">right</code>
+<code class="notranslate" translate="no">top</code>, <code class="notranslate" translate="no">bottom</code>, <code class="notranslate" translate="no">near</code>, and <code class="notranslate" translate="no">far</code>. Because it's projecting a box
+there is no perspective.</p>
+<p>Let's change the 2 view example above to use an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>
+in the first view.</p>
+<p>First let's setup an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = -1;
+const right = 1;
+const top = 1;
+const bottom = -1;
+const near = 5;
+const far = 50;
+const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
+camera.zoom = 0.2;
+</pre>
+<p>We set <code class="notranslate" translate="no">left</code> and <code class="notranslate" translate="no">bottom</code> to -1 and <code class="notranslate" translate="no">right</code> and <code class="notranslate" translate="no">top</code> to 1. This would make
+a box 2 units wide and 2 units tall but we're going to adjust the <code class="notranslate" translate="no">left</code> and <code class="notranslate" translate="no">top</code>
+by the aspect of the rectangle we're drawing to. We'll use the <code class="notranslate" translate="no">zoom</code> property
+to make it easy to adjust how many units are actually shown by the camera.</p>
+<p>Let's add a GUI setting for <code class="notranslate" translate="no">zoom</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
++gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();
+</pre>
+<p>The call to <code class="notranslate" translate="no">listen</code> tells lil-gui to watch for changes. This is here because
+the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> can also control zoom. For example the scroll wheel on
+a mouse will zoom via the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.</p>
+<p>Last we just need to change the part that renders the left
+side to update the <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const aspect = setScissorForElement(view1Elem);
+
+  // update the camera for this aspect
+-  camera.aspect = aspect;
++  camera.left   = -aspect;
++  camera.right  =  aspect;
+  camera.updateProjectionMatrix();
+  cameraHelper.update();
+
+  // don't draw the camera helper in the original view
+  cameraHelper.visible = false;
+
+  scene.background.set(0x000000);
+  renderer.render(scene, camera);
+}
+</pre>
+<p>and now you can see an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> at work.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-orthographic-2-scenes.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cameras-orthographic-2-scenes.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>An <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> is most often used if using three.js
+to draw 2D things. You'd decide how many units you want the camera
+to show. For example if you want one pixel of canvas to match
+one unit in the camera you could do something like</p>
+<p>To put the origin at the center and have 1 pixel = 1 three.js unit
+something like</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.left = -canvas.width / 2;
+camera.right = canvas.width / 2;
+camera.top = canvas.height / 2;
+camera.bottom = -canvas.height / 2;
+camera.near = -1;
+camera.far = 1;
+camera.zoom = 1;
+</pre>
+<p>Or if we wanted the origin to be in the top left just like a
+2D canvas we could use this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.left = 0;
+camera.right = canvas.width;
+camera.top = 0;
+camera.bottom = canvas.height;
+camera.near = -1;
+camera.far = 1;
+camera.zoom = 1;
+</pre>
+<p>In which case the top left corner would be 0,0 just like a 2D canvas</p>
+<p>Let's try it! First let's set the camera up</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = 0;
+const right = 300;  // default canvas size
+const top = 0;
+const bottom = 150;  // default canvas size
+const near = -1;
+const far = 1;
+const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
+camera.zoom = 1;
+</pre>
+<p>Then let's load 6 textures and make 6 planes, one for each texture.
+We'll parent each plane to a <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">THREE.Object3D</code></a> to make it easy to offset
+the plane so its center appears to be at its top left corner.</p>
+<p>If you're running locally you'll also need to have <a href="setup.html">setup</a>.
+You might also want to read about <a href="textures.html">using textures</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loader = new THREE.TextureLoader();
+const textures = [
+  loader.load('resources/images/flower-1.jpg'),
+  loader.load('resources/images/flower-2.jpg'),
+  loader.load('resources/images/flower-3.jpg'),
+  loader.load('resources/images/flower-4.jpg'),
+  loader.load('resources/images/flower-5.jpg'),
+  loader.load('resources/images/flower-6.jpg'),
+];
+const planeSize = 256;
+const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
+const planes = textures.map((texture) =&gt; {
+  const planePivot = new THREE.Object3D();
+  scene.add(planePivot);
+  texture.magFilter = THREE.NearestFilter;
+  const planeMat = new THREE.MeshBasicMaterial({
+    map: texture,
+    side: THREE.DoubleSide,
+  });
+  const mesh = new THREE.Mesh(planeGeo, planeMat);
+  planePivot.add(mesh);
+  // move plane so top left corner is origin
+  mesh.position.set(planeSize / 2, planeSize / 2, 0);
+  return planePivot;
+});
+</pre>
+<p>and we need to update the camera if the size of the canvas
+changes.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    camera.right = canvas.width;
+    camera.bottom = canvas.height;
+    camera.updateProjectionMatrix();
+  }
+
+  ...
+</pre>
+<p><code class="notranslate" translate="no">planes</code> is an array of <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">THREE.Mesh</code></a>, one for each plane.
+Let's move them around based on the time.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
+  time *= 0.001;  // convert to seconds;
+
+  ...
+
+  const distAcross = Math.max(20, canvas.width - planeSize);
+  const distDown = Math.max(20, canvas.height - planeSize);
+
+  // total distance to move across and back
+  const xRange = distAcross * 2;
+  const yRange = distDown * 2;
+  const speed = 180;
+
+  planes.forEach((plane, ndx) =&gt; {
+    // compute a unique time for each plane
+    const t = time * speed + ndx * 300;
+
+    // get a value between 0 and range
+    const xt = t % xRange;
+    const yt = t % yRange;
+
+    // set our position going forward if 0 to half of range
+    // and backward if half of range to range
+    const x = xt &lt; distAcross ? xt : xRange - xt;
+    const y = yt &lt; distDown   ? yt : yRange - yt;
+
+    plane.position.set(x, y, 0);
+  });
+
+  renderer.render(scene, camera);
+</pre>
+<p>And you can see the images bounce pixel perfect off the edges of the
+canvas using pixel math just like a 2D canvas</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-orthographic-canvas-top-left-origin.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cameras-orthographic-canvas-top-left-origin.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Another common use for an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> is to draw the
+up, down, left, right, front, back views of a 3D modeling
+program or a game engine's editor.</p>
+<div class="threejs_center"><img src="../resources/images/quad-viewport.png" style="width: 574px;"></div>
+
+<p>In the screenshot above you can see 1 view is a perspective view and 3 views are
+orthographic views.</p>
+<p>That's the fundamentals of cameras. We'll cover a few common ways to move cameras
+in other articles. For now let's move on to <a href="shadows.html">shadows</a>.</p>
+<p><canvas id="c"></canvas></p>
+<script type="module" src="../resources/threejs-cameras.js"></script>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 418 - 0
manual/en/canvas-textures.html

@@ -0,0 +1,418 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Canvas Textures</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Canvas Textures">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Canvas Textures</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This article continues from <a href="textures.html">the article on textures</a>.
+If you haven't read that yet you should probably start there.</p>
+<p>In <a href="textures.html">the previous article on textures</a> we mostly used
+image files for textures. Sometimes though we want to generate a texture
+at runtime. One way to do this is to use a <a href="/docs/#api/en/textures/CanvasTexture"><code class="notranslate" translate="no">CanvasTexture</code></a>.</p>
+<p>A canvas texture takes a <code class="notranslate" translate="no">&lt;canvas&gt;</code> as its input. If you don't know how to
+draw with the 2D canvas API on a canvas <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial">there's a good tutorial on MDN</a>.</p>
+<p>Let's make a simple canvas program. Here's one that draws dots at random places in random colors.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const ctx = document.createElement('canvas').getContext('2d');
+document.body.appendChild(ctx.canvas);
+ctx.canvas.width = 256;
+ctx.canvas.height = 256;
+ctx.fillStyle = '#FFF';
+ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+function randInt(min, max) {
+  if (max === undefined) {
+    max = min;
+    min = 0;
+  }
+  return Math.random() * (max - min) + min | 0;
+}
+
+function drawRandomDot() {
+  ctx.fillStyle = `#${randInt(0x1000000).toString(16).padStart(6, '0')}`;
+  ctx.beginPath();
+
+  const x = randInt(256);
+  const y = randInt(256);
+  const radius = randInt(10, 64);
+  ctx.arc(x, y, radius, 0, Math.PI * 2);
+  ctx.fill();
+}
+
+function render() {
+  drawRandomDot();
+  requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+</pre>
+<p>it's pretty straight forward.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/canvas-random-dots.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/canvas-random-dots.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Now let's use it to texture something. We'll start with the example of texturing
+a cube from <a href="textures.html">the previous article</a>.
+We'll remove the code that loads an image and instead use
+our canvas by creating a <a href="/docs/#api/en/textures/CanvasTexture"><code class="notranslate" translate="no">CanvasTexture</code></a> and passing it the canvas we created.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cubes = [];  // just an array we can use to rotate the cubes
+-const loader = new THREE.TextureLoader();
+-
++const ctx = document.createElement('canvas').getContext('2d');
++ctx.canvas.width = 256;
++ctx.canvas.height = 256;
++ctx.fillStyle = '#FFF';
++ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
++const texture = new THREE.CanvasTexture(ctx.canvas);
+
+const material = new THREE.MeshBasicMaterial({
+-  map: loader.load('resources/images/wall.jpg'),
++  map: texture,
+});
+const cube = new THREE.Mesh(geometry, material);
+scene.add(cube);
+cubes.push(cube);  // add to our list of cubes to rotate
+</pre>
+<p>And then call the code to draw a random dot in our render loop</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
+  time *= 0.001;
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+  }
+
++  drawRandomDot();
++  texture.needsUpdate = true;
+
+  cubes.forEach((cube, ndx) =&gt; {
+    const speed = .2 + ndx * .1;
+    const rot = time * speed;
+    cube.rotation.x = rot;
+    cube.rotation.y = rot;
+  });
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>The only extra thing we need to do is set the <code class="notranslate" translate="no">needsUpdate</code> property
+of the <a href="/docs/#api/en/textures/CanvasTexture"><code class="notranslate" translate="no">CanvasTexture</code></a> to tell three.js to update the texture with
+the latest contents of the canvas.</p>
+<p>And with that we have a canvas textured cube</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/canvas-textured-cube.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/canvas-textured-cube.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Note that if you want to use three.js to draw into the canvas you're
+better off using a <code class="notranslate" translate="no">RenderTarget</code> which is covered in <a href="rendertargets.html">this article</a>.</p>
+<p>A common use case for canvas textures is to provide text in a scene.
+For example if you wanted to put a person's name on their character's
+badge you might use a canvas texture to texture the badge.</p>
+<p>Let's make a scene with 3 people and give each person a badge
+or label.</p>
+<p>Let's take the example above and remove all the cube related
+stuff. Then let's set the background to white and add two <a href="lights.html">lights</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
++scene.background = new THREE.Color('white');
++
++function addLight(position) {
++  const color = 0xFFFFFF;
++  const intensity = 1;
++  const light = new THREE.DirectionalLight(color, intensity);
++  light.position.set(...position);
++  scene.add(light);
++  scene.add(light.target);
++}
++addLight([-3, 1, 1]);
++addLight([ 2, 1, .5]);
+</pre>
+<p>Let's make some code to make a label using canvas 2D</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function makeLabelCanvas(size, name) {
++  const borderSize = 2;
++  const ctx = document.createElement('canvas').getContext('2d');
++  const font =  `${size}px bold sans-serif`;
++  ctx.font = font;
++  // measure how long the name will be
++  const doubleBorderSize = borderSize * 2;
++  const width = ctx.measureText(name).width + doubleBorderSize;
++  const height = size + doubleBorderSize;
++  ctx.canvas.width = width;
++  ctx.canvas.height = height;
++
++  // need to set font again after resizing canvas
++  ctx.font = font;
++  ctx.textBaseline = 'top';
++
++  ctx.fillStyle = 'blue';
++  ctx.fillRect(0, 0, width, height);
++  ctx.fillStyle = 'white';
++  ctx.fillText(name, borderSize, borderSize);
++
++  return ctx.canvas;
++}
+</pre>
+<p>Then we'll make simple people from a cylinder for the body, a sphere
+for the head, and a plane for the label.</p>
+<p>First let's make the shared geometry.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const bodyRadiusTop = .4;
++const bodyRadiusBottom = .2;
++const bodyHeight = 2;
++const bodyRadialSegments = 6;
++const bodyGeometry = new THREE.CylinderGeometry(
++    bodyRadiusTop, bodyRadiusBottom, bodyHeight, bodyRadialSegments);
++
++const headRadius = bodyRadiusTop * 0.8;
++const headLonSegments = 12;
++const headLatSegments = 5;
++const headGeometry = new THREE.SphereGeometry(
++    headRadius, headLonSegments, headLatSegments);
++
++const labelGeometry = new THREE.PlaneGeometry(1, 1);
+</pre>
+<p>Then let's make a function to build a person from these
+parts.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function makePerson(x, size, name, color) {
++  const canvas = makeLabelCanvas(size, name);
++  const texture = new THREE.CanvasTexture(canvas);
++  // because our canvas is likely not a power of 2
++  // in both dimensions set the filtering appropriately.
++  texture.minFilter = THREE.LinearFilter;
++  texture.wrapS = THREE.ClampToEdgeWrapping;
++  texture.wrapT = THREE.ClampToEdgeWrapping;
++
++  const labelMaterial = new THREE.MeshBasicMaterial({
++    map: texture,
++    side: THREE.DoubleSide,
++    transparent: true,
++  });
++  const bodyMaterial = new THREE.MeshPhongMaterial({
++    color,
++    flatShading: true,
++  });
++
++  const root = new THREE.Object3D();
++  root.position.x = x;
++
++  const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
++  root.add(body);
++  body.position.y = bodyHeight / 2;
++
++  const head = new THREE.Mesh(headGeometry, bodyMaterial);
++  root.add(head);
++  head.position.y = bodyHeight + headRadius * 1.1;
++
++  const label = new THREE.Mesh(labelGeometry, labelMaterial);
++  root.add(label);
++  label.position.y = bodyHeight * 4 / 5;
++  label.position.z = bodyRadiusTop * 1.01;
++
++  // if units are meters then 0.01 here makes size
++  // of the label into centimeters.
++  const labelBaseScale = 0.01;
++  label.scale.x = canvas.width  * labelBaseScale;
++  label.scale.y = canvas.height * labelBaseScale;
++
++  scene.add(root);
++  return root;
++}
+</pre>
+<p>You can see above we put the body, head, and label on a root
+<a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> and adjust their positions. This would let us move the
+root object if we wanted to move the people. The body is 2 units
+high. If 1 unit equals 1 meter then the code above tries to
+make the label in centimeters so they will be size centimeters
+tall and however wide is needed to fit the text.</p>
+<p>We can then make people with labels</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+makePerson(-3, 32, 'Purple People Eater', 'purple');
++makePerson(-0, 32, 'Green Machine', 'green');
++makePerson(+3, 32, 'Red Menace', 'red');
+</pre>
+<p>What's left is to add some <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> so we can move
+the camera.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
++import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+</pre>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+-const far = 5;
++const far = 50;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+-camera.position.z = 2;
++camera.position.set(0, 2, 5);
+
++const controls = new OrbitControls(camera, canvas);
++controls.target.set(0, 2, 0);
++controls.update();
+</pre>
+<p>and we get simple labels.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/canvas-textured-labels.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/canvas-textured-labels.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Some things to notice.</p>
+<ul>
+<li>If you zoom in the labels get pretty low-res.</li>
+</ul>
+<p>There is no easy solution. There are more complex font
+rendering techniques but I know of no plugin solutions.
+Plus they will require the user download font data which
+would be slow.</p>
+<p>One solution is to increase the resolution of the labels.
+Try setting the size passed into to double what it is now
+and setting <code class="notranslate" translate="no">labelBaseScale</code> to half what it currently is.</p>
+<ul>
+<li>The labels get longer the longer the name.</li>
+</ul>
+<p>If you wanted to fix this you'd instead choose a fixed sized
+label and then squish the text.</p>
+<p>This is pretty easy. Pass in a base width and scale the text to fit that
+width like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function makeLabelCanvas(size, name) {
++function makeLabelCanvas(baseWidth, size, name) {
+  const borderSize = 2;
+  const ctx = document.createElement('canvas').getContext('2d');
+  const font =  `${size}px bold sans-serif`;
+  ctx.font = font;
+  // measure how long the name will be
++  const textWidth = ctx.measureText(name).width;
+
+  const doubleBorderSize = borderSize * 2;
+-  const width = ctx.measureText(name).width + doubleBorderSize;
++  const width = baseWidth + doubleBorderSize;
+  const height = size + doubleBorderSize;
+  ctx.canvas.width = width;
+  ctx.canvas.height = height;
+
+  // need to set font again after resizing canvas
+  ctx.font = font;
+-  ctx.textBaseline = 'top';
++  ctx.textBaseline = 'middle';
++  ctx.textAlign = 'center';
+
+  ctx.fillStyle = 'blue';
+  ctx.fillRect(0, 0, width, height);
+
++  // scale to fit but don't stretch
++  const scaleFactor = Math.min(1, baseWidth / textWidth);
++  ctx.translate(width / 2, height / 2);
++  ctx.scale(scaleFactor, 1);
+  ctx.fillStyle = 'white';
+  ctx.fillText(name, borderSize, borderSize);
+
+  return ctx.canvas;
+}
+</pre>
+<p>Then we can pass in a width for the labels</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function makePerson(x, size, name, color) {
+-  const canvas = makeLabelCanvas(size, name);
++function makePerson(x, labelWidth, size, name, color) {
++  const canvas = makeLabelCanvas(labelWidth, size, name);
+
+...
+
+}
+
+-makePerson(-3, 32, 'Purple People Eater', 'purple');
+-makePerson(-0, 32, 'Green Machine', 'green');
+-makePerson(+3, 32, 'Red Menace', 'red');
++makePerson(-3, 150, 32, 'Purple People Eater', 'purple');
++makePerson(-0, 150, 32, 'Green Machine', 'green');
++makePerson(+3, 150, 32, 'Red Menace', 'red');
+</pre>
+<p>and we get labels where the text is centered and scaled to fit</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/canvas-textured-labels-scale-to-fit.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/canvas-textured-labels-scale-to-fit.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Above we used a new canvas for each texture. Whether or not to use a 
+canvas per texture is up to you. If you need to update them often then 
+having one canvas per texture is probably the best option. If they are
+rarely or never updated then you can choose to use a single canvas
+for multiple textures by forcing three.js to use the texture.
+Let's change the code above to do just that.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const ctx = document.createElement('canvas').getContext('2d');
+
+function makeLabelCanvas(baseWidth, size, name) {
+  const borderSize = 2;
+-  const ctx = document.createElement('canvas').getContext('2d');
+  const font =  `${size}px bold sans-serif`;
+
+  ...
+
+}
+
++const forceTextureInitialization = function() {
++  const material = new THREE.MeshBasicMaterial();
++  const geometry = new THREE.PlaneGeometry();
++  const scene = new THREE.Scene();
++  scene.add(new THREE.Mesh(geometry, material));
++  const camera = new THREE.Camera();
++
++  return function forceTextureInitialization(texture) {
++    material.map = texture;
++    renderer.render(scene, camera);
++  };
++}();
+
+function makePerson(x, labelWidth, size, name, color) {
+  const canvas = makeLabelCanvas(labelWidth, size, name);
+  const texture = new THREE.CanvasTexture(canvas);
+  // because our canvas is likely not a power of 2
+  // in both dimensions set the filtering appropriately.
+  texture.minFilter = THREE.LinearFilter;
+  texture.wrapS = THREE.ClampToEdgeWrapping;
+  texture.wrapT = THREE.ClampToEdgeWrapping;
++  forceTextureInitialization(texture);
+
+  ...
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/canvas-textured-labels-one-canvas.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/canvas-textured-labels-one-canvas.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Another issue is that the labels don't always face the camera. If you're using 
+labels as badges that's probably a good thing. If you're using labels to put
+names over players in a 3D game maybe you want the labels to always face the camera.
+We'll cover how to do that in <a href="billboards.html">an article on billboards</a>.</p>
+<p>For labels in particular, <a href="align-html-elements-to-3d.html">another solution is to use HTML</a>.
+The labels in this article are <em>inside the 3D world</em> which is good if you want them
+to be hidden by other objects where as <a href="align-html-elements-to-3d.html">HTML labels</a> are always on top.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 437 - 0
manual/en/cleanup.html

@@ -0,0 +1,437 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Cleanup</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Cleanup">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Cleanup</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>Three.js apps often use lots of memory. A 3D model
+might be 1 to 20 meg memory for all of its vertices.
+A model might use many textures that even if they are
+compressed into jpg files they have to be expanded
+to their uncompressed form to use. Each 1024x1024
+texture takes 4 to 6meg of memory.</p>
+<p>Most three.js apps load resources at init time and
+then use those resources forever until the page is
+closed. But, what if you want to load and change resources
+over time?</p>
+<p>Unlike most JavaScript, three.js can not automatically
+clean these resources up. The browser will clean them
+up if you switch pages but otherwise it's up to you
+to manage them. This is an issue of how WebGL is designed
+and so three.js has no recourse but to pass on the
+responsibility to free resources back to you.</p>
+<p>You free three.js resource this by calling the <code class="notranslate" translate="no">dispose</code> function on
+<a href="textures.html">textures</a>, 
+<a href="primitives.html">geometries</a>, and
+<a href="materials.html">materials</a>.</p>
+<p>You could do this manually. At the start you might create
+some of these resources</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const boxGeometry = new THREE.BoxGeometry(...);
+const boxTexture = textureLoader.load(...);
+const boxMaterial = new THREE.MeshPhongMaterial({map: texture});
+</pre>
+<p>and then when you're done with them you'd free them</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">boxGeometry.dispose();
+boxTexture.dispose();
+boxMaterial.dispose();
+</pre>
+<p>As you use more and more resources that would get more and
+more tedious.</p>
+<p>To help remove some of the tedium let's make a class to track
+the resources. We'll then ask that class to do the cleanup
+for us.</p>
+<p>Here's a first pass at such a class</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ResourceTracker {
+  constructor() {
+    this.resources = new Set();
+  }
+  track(resource) {
+    if (resource.dispose) {
+      this.resources.add(resource);
+    }
+    return resource;
+  }
+  untrack(resource) {
+    this.resources.delete(resource);
+  }
+  dispose() {
+    for (const resource of this.resources) {
+      resource.dispose();
+    }
+    this.resources.clear();
+  }
+}
+</pre>
+<p>Let's use this class with the first example from <a href="textures.html">the article on textures</a>.
+We can create an instance of this class</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const resTracker = new ResourceTracker();
+</pre>
+<p>and then just to make it easier to use let's create a bound function for the <code class="notranslate" translate="no">track</code> method</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const resTracker = new ResourceTracker();
++const track = resTracker.track.bind(resTracker);
+</pre>
+<p>Now to use it we just need to call <code class="notranslate" translate="no">track</code> with for each geometry, texture, and material
+we create</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const boxWidth = 1;
+const boxHeight = 1;
+const boxDepth = 1;
+-const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
++const geometry = track(new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth));
+
+const cubes = [];  // an array we can use to rotate the cubes
+const loader = new THREE.TextureLoader();
+
+-const material = new THREE.MeshBasicMaterial({
+-  map: loader.load('resources/images/wall.jpg'),
+-});
++const material = track(new THREE.MeshBasicMaterial({
++  map: track(loader.load('resources/images/wall.jpg')),
++}));
+const cube = new THREE.Mesh(geometry, material);
+scene.add(cube);
+cubes.push(cube);  // add to our list of cubes to rotate
+</pre>
+<p>And then to free them we'd want to remove the cubes from the scene
+and then call <code class="notranslate" translate="no">resTracker.dispose</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">for (const cube of cubes) {
+  scene.remove(cube);
+}
+cubes.length = 0;  // clears the cubes array
+resTracker.dispose();
+</pre>
+<p>That would work but I find having to remove the cubes from the
+scene kind of tedious. Let's add that functionality to the <code class="notranslate" translate="no">ResourceTracker</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ResourceTracker {
+  constructor() {
+    this.resources = new Set();
+  }
+  track(resource) {
+-    if (resource.dispose) {
++    if (resource.dispose || resource instanceof THREE.Object3D) {
+      this.resources.add(resource);
+    }
+    return resource;
+  }
+  untrack(resource) {
+    this.resources.delete(resource);
+  }
+  dispose() {
+    for (const resource of this.resources) {
+-      resource.dispose();
++      if (resource instanceof THREE.Object3D) {
++        if (resource.parent) {
++          resource.parent.remove(resource);
++        }
++      }
++      if (resource.dispose) {
++        resource.dispose();
++      }
++    }
+    this.resources.clear();
+  }
+}
+</pre>
+<p>And now we can track the cubes</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = track(new THREE.MeshBasicMaterial({
+  map: track(loader.load('resources/images/wall.jpg')),
+}));
+const cube = track(new THREE.Mesh(geometry, material));
+scene.add(cube);
+cubes.push(cube);  // add to our list of cubes to rotate
+</pre>
+<p>We no longer need the code to remove the cubes from the scene.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-for (const cube of cubes) {
+-  scene.remove(cube);
+-}
+cubes.length = 0;  // clears the cube array
+resTracker.dispose();
+</pre>
+<p>Let's arrange this code so that we can re-add the cube,
+texture, and material.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+*const cubes = [];  // just an array we can use to rotate the cubes
+
++function addStuffToScene() {
+  const resTracker = new ResourceTracker();
+  const track = resTracker.track.bind(resTracker);
+
+  const boxWidth = 1;
+  const boxHeight = 1;
+  const boxDepth = 1;
+  const geometry = track(new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth));
+
+  const loader = new THREE.TextureLoader();
+
+  const material = track(new THREE.MeshBasicMaterial({
+    map: track(loader.load('resources/images/wall.jpg')),
+  }));
+  const cube = track(new THREE.Mesh(geometry, material));
+  scene.add(cube);
+  cubes.push(cube);  // add to our list of cubes to rotate
++  return resTracker;
++}
+</pre>
+<p>And then let's write some code to add and remove things over time.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function waitSeconds(seconds = 0) {
+  return new Promise(resolve =&gt; setTimeout(resolve, seconds * 1000));
+}
+
+async function process() {
+  for (;;) {
+    const resTracker = addStuffToScene();
+    await wait(2);
+    cubes.length = 0;  // remove the cubes
+    resTracker.dispose();
+    await wait(1);
+  }
+}
+process();
+</pre>
+<p>This code will create the cube, texture and material, wait for 2 seconds, then dispose of them and wait for 1 second
+and repeat.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cleanup-simple.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cleanup-simple.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>So that seems to work.</p>
+<p>For a loaded file though it's a little more work. Most loaders only return an <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>
+as a root of the hierarchy of objects they load so we need to discover what all the resources
+are.</p>
+<p>Let's update our <code class="notranslate" translate="no">ResourceTracker</code> to try to do that.</p>
+<p>First we'll check if the object is an <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> then track its geometry, material, and children</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ResourceTracker {
+  constructor() {
+    this.resources = new Set();
+  }
+  track(resource) {
+    if (resource.dispose || resource instanceof THREE.Object3D) {
+      this.resources.add(resource);
+    }
++    if (resource instanceof THREE.Object3D) {
++      this.track(resource.geometry);
++      this.track(resource.material);
++      this.track(resource.children);
++    }
+    return resource;
+  }
+  ...
+}
+</pre>
+<p>Now, because any of <code class="notranslate" translate="no">resource.geometry</code>, <code class="notranslate" translate="no">resource.material</code>, and <code class="notranslate" translate="no">resource.children</code>
+might be null or undefined we'll check at the top of <code class="notranslate" translate="no">track</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ResourceTracker {
+  constructor() {
+    this.resources = new Set();
+  }
+  track(resource) {
++    if (!resource) {
++      return resource;
++    }
+
+    if (resource.dispose || resource instanceof THREE.Object3D) {
+      this.resources.add(resource);
+    }
+    if (resource instanceof THREE.Object3D) {
+      this.track(resource.geometry);
+      this.track(resource.material);
+      this.track(resource.children);
+    }
+    return resource;
+  }
+  ...
+}
+</pre>
+<p>Also because <code class="notranslate" translate="no">resource.children</code> is an array and because <code class="notranslate" translate="no">resource.material</code> can be
+an array let's check for arrays</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ResourceTracker {
+  constructor() {
+    this.resources = new Set();
+  }
+  track(resource) {
+    if (!resource) {
+      return resource;
+    }
+
++    // handle children and when material is an array of materials.
++    if (Array.isArray(resource)) {
++      resource.forEach(resource =&gt; this.track(resource));
++      return resource;
++    }
+
+    if (resource.dispose || resource instanceof THREE.Object3D) {
+      this.resources.add(resource);
+    }
+    if (resource instanceof THREE.Object3D) {
+      this.track(resource.geometry);
+      this.track(resource.material);
+      this.track(resource.children);
+    }
+    return resource;
+  }
+  ...
+}
+</pre>
+<p>And finally we need to walk the properties and uniforms
+of a material looking for textures.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ResourceTracker {
+  constructor() {
+    this.resources = new Set();
+  }
+  track(resource) {
+    if (!resource) {
+      return resource;
+    }
+
+*    // handle children and when material is an array of materials or
+*    // uniform is array of textures
+    if (Array.isArray(resource)) {
+      resource.forEach(resource =&gt; this.track(resource));
+      return resource;
+    }
+
+    if (resource.dispose || resource instanceof THREE.Object3D) {
+      this.resources.add(resource);
+    }
+    if (resource instanceof THREE.Object3D) {
+      this.track(resource.geometry);
+      this.track(resource.material);
+      this.track(resource.children);
+-    }
++    } else if (resource instanceof THREE.Material) {
++      // We have to check if there are any textures on the material
++      for (const value of Object.values(resource)) {
++        if (value instanceof THREE.Texture) {
++          this.track(value);
++        }
++      }
++      // We also have to check if any uniforms reference textures or arrays of textures
++      if (resource.uniforms) {
++        for (const value of Object.values(resource.uniforms)) {
++          if (value) {
++            const uniformValue = value.value;
++            if (uniformValue instanceof THREE.Texture ||
++                Array.isArray(uniformValue)) {
++              this.track(uniformValue);
++            }
++          }
++        }
++      }
++    }
+    return resource;
+  }
+  ...
+}
+</pre>
+<p>And with that let's take an example from <a href="load-gltf.html">the article on loading gltf files</a>
+and make it load and free files.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gltfLoader = new GLTFLoader();
+function loadGLTF(url) {
+  return new Promise((resolve, reject) =&gt; {
+    gltfLoader.load(url, resolve, undefined, reject);
+  });
+}
+
+function waitSeconds(seconds = 0) {
+  return new Promise(resolve =&gt; setTimeout(resolve, seconds * 1000));
+}
+
+const fileURLs = [
+  'resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf',
+  'resources/models/3dbustchallange_submission/scene.gltf',
+  'resources/models/mountain_landscape/scene.gltf',
+  'resources/models/simple_house_scene/scene.gltf',
+];
+
+async function loadFiles() {
+  for (;;) {
+    for (const url of fileURLs) {
+      const resMgr = new ResourceTracker();
+      const track = resMgr.track.bind(resMgr);
+      const gltf = await loadGLTF(url);
+      const root = track(gltf.scene);
+      scene.add(root);
+
+      // compute the box that contains all the stuff
+      // from root and below
+      const box = new THREE.Box3().setFromObject(root);
+
+      const boxSize = box.getSize(new THREE.Vector3()).length();
+      const boxCenter = box.getCenter(new THREE.Vector3());
+
+      // set the camera to frame the box
+      frameArea(boxSize * 1.1, boxSize, boxCenter, camera);
+
+      await waitSeconds(2);
+      renderer.render(scene, camera);
+
+      resMgr.dispose();
+
+      await waitSeconds(1);
+
+    }
+  }
+}
+loadFiles();
+</pre>
+<p>and we get</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cleanup-loaded-files.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/cleanup-loaded-files.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Some notes about the code.</p>
+<p>If we wanted to load 2 or more files at once and free them at
+anytime we would use one <code class="notranslate" translate="no">ResourceTracker</code> per file.</p>
+<p>Above we are only tracking <code class="notranslate" translate="no">gltf.scene</code> right after loading.
+Based on our current implementation of <code class="notranslate" translate="no">ResourceTracker</code> that 
+will track all the resources just loaded. If we added more
+things to the scene we need to decide whether or not to track them.</p>
+<p>For example let's say after we loaded a character we put a tool
+in their hand by making the tool a child of their hand. As it is
+that tool will not be freed. I'm guessing more often than not
+this is what we want. </p>
+<p>That brings up a point. Originally when I first wrote the <code class="notranslate" translate="no">ResourceTracker</code>
+above I walked through everything inside the <code class="notranslate" translate="no">dispose</code> method instead of <code class="notranslate" translate="no">track</code>.
+It was only later as I thought about the tool as a child of hand case above
+that it became clear that tracking exactly what to free in <code class="notranslate" translate="no">track</code> was more
+flexible and arguably more correct since we could then track what was loaded
+from the file rather than just freeing the state of the scene graph later.</p>
+<p>I honestly am not 100% happy with <code class="notranslate" translate="no">ResourceTracker</code>. Doing things this
+way is not common in 3D engines. We shouldn't have to guess what
+resources were loaded, we should know. It would be nice if three.js
+changed so that all file loaders returned some standard object with
+references to all the resources loaded. At least at the moment,
+three.js doesn't give us any more info when loading a scene so this
+solution seems to work.</p>
+<p>I hope you find this example useful or at least a good reference for what is
+required to free resources in three.js</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 444 - 0
manual/en/custom-buffergeometry.html

@@ -0,0 +1,444 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Custom BufferGeometry</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Custom BufferGeometry">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Custom BufferGeometry</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> is three.js's way of representing all geometry. A <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>
+essentially a collection <em>named</em> of <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s.
+Each <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> represents an array of one type of data: positions,
+normals, colors, uv, etc... Together, the named <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s represent
+<em>parallel arrays</em> of all the data for each vertex.</p>
+<div class="threejs_center"><img src="../resources/threejs-attributes.svg" style="width: 700px"></div>
+
+<p>Above you can see we have 4 attributes: <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, <code class="notranslate" translate="no">color</code>, <code class="notranslate" translate="no">uv</code>.
+They represent <em>parallel arrays</em> which means that the Nth set of data in each
+attribute belongs to the same vertex. The vertex at index = 4 is highlighted
+to show that the parallel data across all attributes defines one vertex.</p>
+<p>This brings up a point, here's a diagram of a cube with one corner highlighted.</p>
+<div class="threejs_center"><img src="../resources/cube-faces-vertex.svg" style="width: 500px"></div>
+
+<p>Thinking about it that single corner needs a different normal for each face of the
+cube. A normal is info about which direction something faces. In the diagram
+the normals are presented by the arrows around the corner vertex showing that each
+face that shares that vertex position needs a normal that points in a different direction.</p>
+<p>That corner needs different UVs for each face as well. UVs are texture coordinates
+that specify which part of a texture being drawn on a triangle corresponds to that
+vertex position. You can see the green face needs that vertex to have a UV that corresponds
+to the top right corner of the F texture, the blue face needs a UV that corresponds to the
+top left corner of the F texture, and the red face needs a UV that corresponds to the bottom
+left corner of the F texture.</p>
+<p>A single <em>vertex</em> is the combination of all of its parts. If a vertex needs any
+part to be different then it must be a different vertex.</p>
+<p>As a simple example let's make a cube using <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. A cube is interesting
+because it appears to share vertices at the corners but really
+does not. For our example we'll list out all the vertices with all their data
+and then convert that data into parallel arrays and finally use those to make
+<a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s and add them to a <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
+<p>We start with a list of all the data needed for the cube. Remember again
+that if a vertex has any unique parts it has to be a separate vertex. As such
+to make a cube requires 36 vertices. 2 triangles per face, 3 vertices per triangle,
+6 faces = 36 vertices.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
+  // front
+  { pos: [-1, -1,  1], norm: [ 0,  0,  1], uv: [0, 0], },
+  { pos: [ 1, -1,  1], norm: [ 0,  0,  1], uv: [1, 0], },
+  { pos: [-1,  1,  1], norm: [ 0,  0,  1], uv: [0, 1], },
+
+  { pos: [-1,  1,  1], norm: [ 0,  0,  1], uv: [0, 1], },
+  { pos: [ 1, -1,  1], norm: [ 0,  0,  1], uv: [1, 0], },
+  { pos: [ 1,  1,  1], norm: [ 0,  0,  1], uv: [1, 1], },
+  // right
+  { pos: [ 1, -1,  1], norm: [ 1,  0,  0], uv: [0, 0], },
+  { pos: [ 1, -1, -1], norm: [ 1,  0,  0], uv: [1, 0], },
+  { pos: [ 1,  1,  1], norm: [ 1,  0,  0], uv: [0, 1], },
+
+  { pos: [ 1,  1,  1], norm: [ 1,  0,  0], uv: [0, 1], },
+  { pos: [ 1, -1, -1], norm: [ 1,  0,  0], uv: [1, 0], },
+  { pos: [ 1,  1, -1], norm: [ 1,  0,  0], uv: [1, 1], },
+  // back
+  { pos: [ 1, -1, -1], norm: [ 0,  0, -1], uv: [0, 0], },
+  { pos: [-1, -1, -1], norm: [ 0,  0, -1], uv: [1, 0], },
+  { pos: [ 1,  1, -1], norm: [ 0,  0, -1], uv: [0, 1], },
+
+  { pos: [ 1,  1, -1], norm: [ 0,  0, -1], uv: [0, 1], },
+  { pos: [-1, -1, -1], norm: [ 0,  0, -1], uv: [1, 0], },
+  { pos: [-1,  1, -1], norm: [ 0,  0, -1], uv: [1, 1], },
+  // left
+  { pos: [-1, -1, -1], norm: [-1,  0,  0], uv: [0, 0], },
+  { pos: [-1, -1,  1], norm: [-1,  0,  0], uv: [1, 0], },
+  { pos: [-1,  1, -1], norm: [-1,  0,  0], uv: [0, 1], },
+
+  { pos: [-1,  1, -1], norm: [-1,  0,  0], uv: [0, 1], },
+  { pos: [-1, -1,  1], norm: [-1,  0,  0], uv: [1, 0], },
+  { pos: [-1,  1,  1], norm: [-1,  0,  0], uv: [1, 1], },
+  // top
+  { pos: [ 1,  1, -1], norm: [ 0,  1,  0], uv: [0, 0], },
+  { pos: [-1,  1, -1], norm: [ 0,  1,  0], uv: [1, 0], },
+  { pos: [ 1,  1,  1], norm: [ 0,  1,  0], uv: [0, 1], },
+
+  { pos: [ 1,  1,  1], norm: [ 0,  1,  0], uv: [0, 1], },
+  { pos: [-1,  1, -1], norm: [ 0,  1,  0], uv: [1, 0], },
+  { pos: [-1,  1,  1], norm: [ 0,  1,  0], uv: [1, 1], },
+  // bottom
+  { pos: [ 1, -1,  1], norm: [ 0, -1,  0], uv: [0, 0], },
+  { pos: [-1, -1,  1], norm: [ 0, -1,  0], uv: [1, 0], },
+  { pos: [ 1, -1, -1], norm: [ 0, -1,  0], uv: [0, 1], },
+
+  { pos: [ 1, -1, -1], norm: [ 0, -1,  0], uv: [0, 1], },
+  { pos: [-1, -1,  1], norm: [ 0, -1,  0], uv: [1, 0], },
+  { pos: [-1, -1, -1], norm: [ 0, -1,  0], uv: [1, 1], },
+];
+</pre>
+<p>We can then translate all of that into 3 parallel arrays</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const positions = [];
+const normals = [];
+const uvs = [];
+for (const vertex of vertices) {
+  positions.push(...vertex.pos);
+  normals.push(...vertex.norm);
+  uvs.push(...vertex.uv);
+}
+</pre>
+<p>Finally we can create a <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> and then a <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> for each array
+and add it to the <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">  const geometry = new THREE.BufferGeometry();
+  const positionNumComponents = 3;
+  const normalNumComponents = 3;
+  const uvNumComponents = 2;
+  geometry.setAttribute(
+      'position',
+      new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
+  geometry.setAttribute(
+      'normal',
+      new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
+  geometry.setAttribute(
+      'uv',
+      new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
+</pre>
+<p>Note that the names are significant. You must name your attributes the names
+that match what three.js expects (unless you are creating a custom shader).
+In this case <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, and <code class="notranslate" translate="no">uv</code>. If you want vertex colors then
+name your attribute <code class="notranslate" translate="no">color</code>.</p>
+<p>Above we created 3 JavaScript native arrays, <code class="notranslate" translate="no">positions</code>, <code class="notranslate" translate="no">normals</code> and <code class="notranslate" translate="no">uvs</code>.
+We then convert those into
+<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a>
+of type <code class="notranslate" translate="no">Float32Array</code>. A <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> requires a TypedArray not a native
+array. A <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> also requires you to tell it how many components there
+are per vertex. For the positions and normals we have 3 components per vertex,
+x, y, and z. For the UVs we have 2, u and v.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>That's a lot of data. A small thing we can do is use indices to reference
+the vertices. Looking back at our cube data, each face is made from 2 triangles
+with 3 vertices each, 6 vertices total, but 2 of those vertices are exactly the same;
+The same position, the same normal, and the same uv.
+So, we can remove the matching vertices and then
+reference them by index. First we remove the matching vertices.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
+  // front
+  { pos: [-1, -1,  1], norm: [ 0,  0,  1], uv: [0, 0], }, // 0
+  { pos: [ 1, -1,  1], norm: [ 0,  0,  1], uv: [1, 0], }, // 1
+  { pos: [-1,  1,  1], norm: [ 0,  0,  1], uv: [0, 1], }, // 2
+-
+-  { pos: [-1,  1,  1], norm: [ 0,  0,  1], uv: [0, 1], },
+-  { pos: [ 1, -1,  1], norm: [ 0,  0,  1], uv: [1, 0], },
+  { pos: [ 1,  1,  1], norm: [ 0,  0,  1], uv: [1, 1], }, // 3
+  // right
+  { pos: [ 1, -1,  1], norm: [ 1,  0,  0], uv: [0, 0], }, // 4
+  { pos: [ 1, -1, -1], norm: [ 1,  0,  0], uv: [1, 0], }, // 5
+-
+-  { pos: [ 1,  1,  1], norm: [ 1,  0,  0], uv: [0, 1], },
+-  { pos: [ 1, -1, -1], norm: [ 1,  0,  0], uv: [1, 0], },
+  { pos: [ 1,  1,  1], norm: [ 1,  0,  0], uv: [0, 1], }, // 6
+  { pos: [ 1,  1, -1], norm: [ 1,  0,  0], uv: [1, 1], }, // 7
+  // back
+  { pos: [ 1, -1, -1], norm: [ 0,  0, -1], uv: [0, 0], }, // 8
+  { pos: [-1, -1, -1], norm: [ 0,  0, -1], uv: [1, 0], }, // 9
+-
+-  { pos: [ 1,  1, -1], norm: [ 0,  0, -1], uv: [0, 1], },
+-  { pos: [-1, -1, -1], norm: [ 0,  0, -1], uv: [1, 0], },
+  { pos: [ 1,  1, -1], norm: [ 0,  0, -1], uv: [0, 1], }, // 10
+  { pos: [-1,  1, -1], norm: [ 0,  0, -1], uv: [1, 1], }, // 11
+  // left
+  { pos: [-1, -1, -1], norm: [-1,  0,  0], uv: [0, 0], }, // 12
+  { pos: [-1, -1,  1], norm: [-1,  0,  0], uv: [1, 0], }, // 13
+-
+-  { pos: [-1,  1, -1], norm: [-1,  0,  0], uv: [0, 1], },
+-  { pos: [-1, -1,  1], norm: [-1,  0,  0], uv: [1, 0], },
+  { pos: [-1,  1, -1], norm: [-1,  0,  0], uv: [0, 1], }, // 14
+  { pos: [-1,  1,  1], norm: [-1,  0,  0], uv: [1, 1], }, // 15
+  // top
+  { pos: [ 1,  1, -1], norm: [ 0,  1,  0], uv: [0, 0], }, // 16
+  { pos: [-1,  1, -1], norm: [ 0,  1,  0], uv: [1, 0], }, // 17
+-
+-  { pos: [ 1,  1,  1], norm: [ 0,  1,  0], uv: [0, 1], },
+-  { pos: [-1,  1, -1], norm: [ 0,  1,  0], uv: [1, 0], },
+  { pos: [ 1,  1,  1], norm: [ 0,  1,  0], uv: [0, 1], }, // 18
+  { pos: [-1,  1,  1], norm: [ 0,  1,  0], uv: [1, 1], }, // 19
+  // bottom
+  { pos: [ 1, -1,  1], norm: [ 0, -1,  0], uv: [0, 0], }, // 20
+  { pos: [-1, -1,  1], norm: [ 0, -1,  0], uv: [1, 0], }, // 21
+-
+-  { pos: [ 1, -1, -1], norm: [ 0, -1,  0], uv: [0, 1], },
+-  { pos: [-1, -1,  1], norm: [ 0, -1,  0], uv: [1, 0], },
+  { pos: [ 1, -1, -1], norm: [ 0, -1,  0], uv: [0, 1], }, // 22
+  { pos: [-1, -1, -1], norm: [ 0, -1,  0], uv: [1, 1], }, // 23
+];
+</pre>
+<p>So now we have 24 unique vertices. Then we specify 36 indices
+for the 36 vertices we need drawn to make 12 triangles by calling <a href="/docs/#api/en/core/BufferGeometry.setIndex"><code class="notranslate" translate="no">BufferGeometry.setIndex</code></a> with an array of indices.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.setAttribute(
+    'position',
+    new THREE.BufferAttribute(positions, positionNumComponents));
+geometry.setAttribute(
+    'normal',
+    new THREE.BufferAttribute(normals, normalNumComponents));
+geometry.setAttribute(
+    'uv',
+    new THREE.BufferAttribute(uvs, uvNumComponents));
+
++geometry.setIndex([
++   0,  1,  2,   2,  1,  3,  // front
++   4,  5,  6,   6,  5,  7,  // right
++   8,  9, 10,  10,  9, 11,  // back
++  12, 13, 14,  14, 13, 15,  // left
++  16, 17, 18,  18, 17, 19,  // top
++  20, 21, 22,  22, 21, 23,  // bottom
++]);
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube-indexed.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-indexed.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> has a <a href="/docs/#api/en/core/BufferGeometry#computeVertexNormals"><code class="notranslate" translate="no">computeVertexNormals</code></a> method for computing normals if you
+are not supplying them. Unfortunately, 
+since positions can not be shared if any other part of a vertex is different,
+the results of calling <code class="notranslate" translate="no">computeVertexNormals</code> will generate seams if your
+geometry is supposed to connect to itself like a sphere or a cylinder.</p>
+<div class="spread">
+  <div>
+    <div data-diagram="bufferGeometryCylinder"></div>
+  </div>
+</div>
+
+<p>For the cylinder above the normals were created using <code class="notranslate" translate="no">computeVertexNormals</code>.
+If you look closely there is a seam on the cylinder. This is because there
+is no way to share the vertices at the start and end of the cylinder since they
+require different UVs so the function to compute them has no idea those are
+the same vertices to smooth over them. Just a small thing to be aware of.
+The solution is to supply your own normals.</p>
+<p>We can also use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a> from the start instead of native JavaScript arrays.
+The disadvantage to TypedArrays is you must specify their size up front. Of
+course that's not that large of a burden but with native arrays we can just
+<code class="notranslate" translate="no">push</code> values onto them and look at what size they end up by checking their
+<code class="notranslate" translate="no">length</code> at the end. With TypedArrays there is no push function so we need
+to do our own bookkeeping when adding values to them.</p>
+<p>In this example knowing the length up front is pretty easy since we're using
+a big block of static data to start.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const positions = [];
+-const normals = [];
+-const uvs = [];
++const numVertices = vertices.length;
++const positionNumComponents = 3;
++const normalNumComponents = 3;
++const uvNumComponents = 2;
++const positions = new Float32Array(numVertices * positionNumComponents);
++const normals = new Float32Array(numVertices * normalNumComponents);
++const uvs = new Float32Array(numVertices * uvNumComponents);
++let posNdx = 0;
++let nrmNdx = 0;
++let uvNdx = 0;
+for (const vertex of vertices) {
+-  positions.push(...vertex.pos);
+-  normals.push(...vertex.norm);
+-  uvs.push(...vertex.uv);
++  positions.set(vertex.pos, posNdx);
++  normals.set(vertex.norm, nrmNdx);
++  uvs.set(vertex.uv, uvNdx);
++  posNdx += positionNumComponents;
++  nrmNdx += normalNumComponents;
++  uvNdx += uvNumComponents;
+}
+
+geometry.setAttribute(
+    'position',
+-    new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
++    new THREE.BufferAttribute(positions, positionNumComponents));
+geometry.setAttribute(
+    'normal',
+-    new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
++    new THREE.BufferAttribute(normals, normalNumComponents));
+geometry.setAttribute(
+    'uv',
+-    new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
++    new THREE.BufferAttribute(uvs, uvNumComponents));
+
+geometry.setIndex([
+   0,  1,  2,   2,  1,  3,  // front
+   4,  5,  6,   6,  5,  7,  // right
+   8,  9, 10,  10,  9, 11,  // back
+  12, 13, 14,  14, 13, 15,  // left
+  16, 17, 18,  18, 17, 19,  // top
+  20, 21, 22,  22, 21, 23,  // bottom
+]);
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube-typedarrays.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-typedarrays.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>A good reason to use typedarrays is if you want to dynamically update any
+part of the vertices.</p>
+<p>I couldn't think of a really good example of dynamically updating the vertices
+so I decided to make a sphere and move each quad in and out from the center. Hopefully
+it's a useful example.</p>
+<p>Here's the code to generate positions and indices for a sphere. The code
+is sharing vertices within a quad but it's not sharing vertices between
+quads because we want to be able to move each quad separately.</p>
+<p>Because I'm lazy I used a small hierarchy of 3 <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> objects to compute
+sphere points. How this works is explained in <a href="optimize-lots-of-objects.html">the article on optimizing lots of objects</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeSpherePositions(segmentsAround, segmentsDown) {
+  const numVertices = segmentsAround * segmentsDown * 6;
+  const numComponents = 3;
+  const positions = new Float32Array(numVertices * numComponents);
+  const indices = [];
+
+  const longHelper = new THREE.Object3D();
+  const latHelper = new THREE.Object3D();
+  const pointHelper = new THREE.Object3D();
+  longHelper.add(latHelper);
+  latHelper.add(pointHelper);
+  pointHelper.position.z = 1;
+  const temp = new THREE.Vector3();
+
+  function getPoint(lat, long) {
+    latHelper.rotation.x = lat;
+    longHelper.rotation.y = long;
+    longHelper.updateMatrixWorld(true);
+    return pointHelper.getWorldPosition(temp).toArray();
+  }
+
+  let posNdx = 0;
+  let ndx = 0;
+  for (let down = 0; down &lt; segmentsDown; ++down) {
+    const v0 = down / segmentsDown;
+    const v1 = (down + 1) / segmentsDown;
+    const lat0 = (v0 - 0.5) * Math.PI;
+    const lat1 = (v1 - 0.5) * Math.PI;
+
+    for (let across = 0; across &lt; segmentsAround; ++across) {
+      const u0 = across / segmentsAround;
+      const u1 = (across + 1) / segmentsAround;
+      const long0 = u0 * Math.PI * 2;
+      const long1 = u1 * Math.PI * 2;
+
+      positions.set(getPoint(lat0, long0), posNdx);  posNdx += numComponents;
+      positions.set(getPoint(lat1, long0), posNdx);  posNdx += numComponents;
+      positions.set(getPoint(lat0, long1), posNdx);  posNdx += numComponents;
+      positions.set(getPoint(lat1, long1), posNdx);  posNdx += numComponents;
+
+      indices.push(
+        ndx, ndx + 1, ndx + 2,
+        ndx + 2, ndx + 1, ndx + 3,
+      );
+      ndx += 4;
+    }
+  }
+  return {positions, indices};
+}
+</pre>
+<p>We can then call it like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const segmentsAround = 24;
+const segmentsDown = 16;
+const {positions, indices} = makeSpherePositions(segmentsAround, segmentsDown);
+</pre>
+<p>Because positions returned are unit sphere positions so they are exactly the same
+values we need for normals so we can just duplicated them for the normals.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const normals = positions.slice();
+</pre>
+<p>And then we setup the attributes like before</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.BufferGeometry();
+const positionNumComponents = 3;
+const normalNumComponents = 3;
+
++const positionAttribute = new THREE.BufferAttribute(positions, positionNumComponents);
++positionAttribute.setUsage(THREE.DynamicDrawUsage);
+geometry.setAttribute(
+    'position',
++    positionAttribute);
+geometry.setAttribute(
+    'normal',
+    new THREE.BufferAttribute(normals, normalNumComponents));
+geometry.setIndex(indices);
+</pre>
+<p>I've highlighted a few differences. We save a reference to the position attribute.
+We also mark it as dynamic. This is a hint to THREE.js that we're going to be changing
+the contents of the attribute often.</p>
+<p>In our render loop we update the positions based off their normals every frame.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const temp = new THREE.Vector3();
+
+...
+
+for (let i = 0; i &lt; positions.length; i += 3) {
+  const quad = (i / 12 | 0);
+  const ringId = quad / segmentsAround | 0;
+  const ringQuadId = quad % segmentsAround;
+  const ringU = ringQuadId / segmentsAround;
+  const angle = ringU * Math.PI * 2;
+  temp.fromArray(normals, i);
+  temp.multiplyScalar(THREE.MathUtils.lerp(1, 1.4, Math.sin(time + ringId + angle) * .5 + .5));
+  temp.toArray(positions, i);
+}
+positionAttribute.needsUpdate = true;
+</pre>
+<p>And we set <code class="notranslate" translate="no">positionAttribute.needsUpdate</code> to tell THREE.js to use our changes.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-dynamic.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-buffergeometry-dynamic.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>I hope these were useful examples of how to use <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> directly to
+make your own geometry and how to dynamically update the contents of a
+<a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>.</p>
+<!-- needed in English only to prevent warning from outdated translations -->
+<p><a href="resources/threejs-geometry.svg"></a>
+<a href="custom-geometry.html"></a></p>
+<p><canvas id="c"></canvas></p>
+<script type="module" src="../resources/threejs-custom-buffergeometry.js"></script>
+
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 459 - 0
manual/en/custom-geometry.html

@@ -0,0 +1,459 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Custom Geometry</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Custom Geometry">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Custom Geometry</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <div class="warning">
+<strong>NOTE!</strong> This article is deprecated. Three.js r125
+removed support for <code class="notranslate" translate="no">Geometry</code>. Please refer to
+the article on <a href="custom-buffergeometry.html">custom BufferGeometry</a>.
+</div>
+
+<p>A <a href="primitives.html">previous article</a> gave a tour of
+the various built in primitives included in THREE.js. In this
+article we'll cover making our own geometry.</p>
+<p>Just to be clear, if you are serious about making 3D content,
+the most common way is to use a 3D modeling package like
+<a href="https://blender.org">Blender</a>,
+<a href="https://www.autodesk.com/products/maya/overview">Maya</a>,
+<a href="https://www.autodesk.com/products/3ds-max/overview">3D Studio Max</a>,
+<a href="https://www.maxon.net/en-us/">Cinema4D</a>, etc...
+You'd build a model and then export to <a href="load-gltf.html">gLTF</a>
+or <a href="load-obj.html">.obj</a> and load them up.
+Whichever one you choose, expect to spend 2 or 3 weeks going through
+their respective tutorials as all of them have a learning curve
+to be useful.</p>
+<p>Still, there are times when we might want to generate our own
+3D geometry in code instead of using a modeling package.</p>
+<p>First let's just make a cube. Even though three.js already
+provides us with <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> and <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> a
+cube is easy to understand so let's start there.</p>
+<p>There are 2 ways to make custom geometry in THREE.js. One
+is with the <code class="notranslate" translate="no">Geometry</code> class, the other is <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.
+Each has their advantages. <code class="notranslate" translate="no">Geometry</code> is arguably easier to
+use but slower and uses more memory. For few 1000s triangles
+it's a great choice but for 10s of thousands of triangles
+it might be better to use <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
+<p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> is arguably harder to use but uses less
+memory and is faster. If quick rule of thumb might be
+if you're going to generate more than 10000 triangles
+consider using <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
+<p>Note when I say <code class="notranslate" translate="no">Geometry</code> is slower I mean it is slower to
+start and slower to modify but it is not slower to draw so
+if you're not planning on modifying your geometry then
+as long as it's not too large there will only be slightly more
+delay for your program to start using <code class="notranslate" translate="no">Geometry</code> vs using
+<a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. We'll go over both eventually. For now
+though let's use geometry as it's easier to understand IMO.</p>
+<p>First let's make a cube with <code class="notranslate" translate="no">Geometry</code>. We'll start
+with an example from <a href="responsive.html">the article on responsiveness</a>.</p>
+<p>Let's remove the part that uses <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> and replace it with
+a <code class="notranslate" translate="no">Geometry</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const boxWidth = 1;
+-const boxHeight = 1;
+-const boxDepth = 1;
+-const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
++const geometry = new THREE.Geometry();
+</pre>
+<p>Now let's add the 8 corners of a cube. Here are the 8 corners.</p>
+<div class="threejs_center"><img src="../resources/cube-vertex-positions.svg" style="width: 500px"></div>
+
+<p>Centered around the origin we can add the vertex positions like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.Geometry();
++geometry.vertices.push(
++  new THREE.Vector3(-1, -1,  1),  // 0
++  new THREE.Vector3( 1, -1,  1),  // 1
++  new THREE.Vector3(-1,  1,  1),  // 2
++  new THREE.Vector3( 1,  1,  1),  // 3
++  new THREE.Vector3(-1, -1, -1),  // 4
++  new THREE.Vector3( 1, -1, -1),  // 5
++  new THREE.Vector3(-1,  1, -1),  // 6
++  new THREE.Vector3( 1,  1, -1),  // 7
++);
+</pre>
+<p>We then need to make triangles, 2 for each face of the cube</p>
+<div class="threejs_center"><img src="../resources/cube-triangles.svg" style="width: 500px"></div>
+
+<p>We do that by creating <a href="/docs/#api/en/core/Face3"><code class="notranslate" translate="no">Face3</code></a> objects and specifying the indices
+of the 3 vertices that make up that face.</p>
+<p>The order we specify the vertices is important. To be pointing toward the
+outside of the cube they must be specified in a counter clockwise direction
+when that triangle is facing the camera.</p>
+<div class="threejs_center"><img src="../resources/cube-vertex-winding-order.svg" style="width: 500px"></div>
+
+<p>Following that pattern we can specify the 12 triangles that make
+the cube like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faces.push(
+  // front
+  new THREE.Face3(0, 3, 2),
+  new THREE.Face3(0, 1, 3),
+  // right
+  new THREE.Face3(1, 7, 3),
+  new THREE.Face3(1, 5, 7),
+  // back
+  new THREE.Face3(5, 6, 7),
+  new THREE.Face3(5, 4, 6),
+  // left
+  new THREE.Face3(4, 2, 6),
+  new THREE.Face3(4, 0, 2),
+  // top
+  new THREE.Face3(2, 7, 6),
+  new THREE.Face3(2, 3, 7),
+  // bottom
+  new THREE.Face3(4, 1, 0),
+  new THREE.Face3(4, 5, 1),
+);
+</pre>
+<p>A few other minor changes to the original code and it should
+work.</p>
+<p>These cubes are twice as large as the <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> we were
+using before so let's move the camera back a little</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+-const far = 5;
++const far = 100;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+-camera.position.z = 2;
++camera.position.z = 5;
+</pre>
+<p>and let's separate them a little more and I changed their colors just because</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cubes = [
+-  makeInstance(geometry, 0x44aa88,  0),
+-  makeInstance(geometry, 0x8844aa, -2),
+-  makeInstance(geometry, 0xaa8844,  2),
++  makeInstance(geometry, 0x44FF44,  0),
++  makeInstance(geometry, 0x4444FF, -4),
++  makeInstance(geometry, 0xFF4444,  4),
+];
+</pre>
+<p>One last thing is we haven't added normals yet so we
+can't do any lighting. Let's change the material
+to something that doesn't need lights.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeInstance(geometry, color, x) {
+-  const material = new THREE.MeshPhongMaterial({color});
++  const material = new THREE.MeshBasicMaterial({color});
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+
+  ...
+</pre>
+<p>and we get cubes we made ourselves.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-cube.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>We can specify a color per face by setting the <code class="notranslate" translate="no">color</code> property of
+each face.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faces[ 0].color = geometry.faces[ 1].color = new THREE.Color('red');
+geometry.faces[ 2].color = geometry.faces[ 3].color = new THREE.Color('yellow');
+geometry.faces[ 4].color = geometry.faces[ 5].color = new THREE.Color('green');
+geometry.faces[ 6].color = geometry.faces[ 7].color = new THREE.Color('cyan');
+geometry.faces[ 8].color = geometry.faces[ 9].color = new THREE.Color('blue');
+geometry.faces[10].color = geometry.faces[11].color = new THREE.Color('magenta');
+</pre>
+<p>note we need to tell the material we want to use vertex colors</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({color});
++const material = new THREE.MeshBasicMaterial({vertexColors: true});
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-face-colors.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-cube-face-colors.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>We can instead set the color of each individual vertex by setting the <code class="notranslate" translate="no">vertexColors</code>
+property of a <code class="notranslate" translate="no">Face</code> to an array of the 3 colors for the 3 vertices.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faces.forEach((face, ndx) =&gt; {
+  face.vertexColors = [
+    (new THREE.Color()).setHSL(ndx / 12      , 1, 0.5),
+    (new THREE.Color()).setHSL(ndx / 12 + 0.1, 1, 0.5),
+    (new THREE.Color()).setHSL(ndx / 12 + 0.2, 1, 0.5),
+  ];
+});
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-vertex-colors.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-cube-vertex-colors.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>To use lighting we need normals. Normals are vectors that specify direction.
+Just like the colors we can specify a normal for the face by setting the <code class="notranslate" translate="no">normal</code>
+property on each face with</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">face.normal = new THREE.Vector3(...)
+</pre>
+<p>or we can specify a normal for each vertex by setting the <code class="notranslate" translate="no">vertexNormals</code>
+property with something like</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">face.vertexNormals = [
+  new THREE.Vector3(...),
+  new THREE.Vector3(...),
+  new THREE.Vector3(...),
+]
+</pre>
+<p>but often it's much easier to just ask THREE.js to compute normals
+for us based on the positions we specified.</p>
+<p>For face normals we'd call <code class="notranslate" translate="no">Geometry.computeFaceNormals</code> as in</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.computeFaceNormals();
+</pre>
+<p>Removing the vertex color stuff and changing the material back to <a href="/docs/#api/en/materials/MeshPhongMaterial"><code class="notranslate" translate="no">MeshPhongMaterial</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({vertexColors: true});
++const material = new THREE.MeshPhongMaterial({color});
+</pre>
+<p>and now our cubes can be lit.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-face-normals.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-cube-face-normals.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Using face normals will always give us a faceted look. We can use
+vertex normals for a smoother look by calling <code class="notranslate" translate="no">Geometry.computeVertexNormals</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-geometry.computeFaceNormals();
++geometry.computeVertexNormals();
+</pre>
+<p>Unfortunately a cube is not a good candidate for vertex normals since it
+means each vertex gets its normal from the
+normals of all the faces it shares.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-vertex-normals.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-cube-vertex-normals.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Adding texture coordinates, sometimes called UVs, is done via an array of
+layers of parallel arrays to the <code class="notranslate" translate="no">faces</code> array which is set via <code class="notranslate" translate="no">Geometry.faceVertexUvs</code>.
+For our cube we could do something like</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faceVertexUvs[0].push(
+  // front
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
+  // right
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
+  // back
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
+  // left
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
+  // top
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
+  // bottom
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
+  [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
+);
+</pre>
+<p>It's important to notice <code class="notranslate" translate="no">faceVertexUvs</code> is an array of layers. Each layer
+is another set of UV coordinates. By default there is one layer of UV coordinates,
+layer 0, so we just add our UVs to that layer.</p>
+<p>Let's <a href="textures.html">add a texture</a> to our material and switch back to compute face normals</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-geometry.computeVertexNormals();
++geometry.computeFaceNormals();
+
++const loader = new THREE.TextureLoader();
++const texture = loader.load('resources/images/star.png');
+
+function makeInstance(geometry, color, x) {
+-  const material = new THREE.MeshPhongMaterial({color});
++  const material = new THREE.MeshPhongMaterial({color, map: texture});
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+
+  ...
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-texcoords.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-cube-texcoords.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Putting that all together, let's make a simple heightmap based
+terrain mesh.</p>
+<p>A heightmap based terrain is where you have a 2D array of heights
+that you apply them to a grid. An easy way to get a 2D array of heights
+is to draw them in an image editing program. Here's an image I drew.
+It's 96x64 pixels</p>
+<div class="threejs_center"><img src="../examples/resources/images/heightmap-96x64.png" style="width: 512px; image-rendering: pixelated;"></div>
+
+<p>We'll load that and then generate a heightmap mesh from it.
+We can use the <a href="/docs/#api/en/loaders/ImageLoader"><code class="notranslate" translate="no">ImageLoader</code></a> to load the image.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const imgLoader = new THREE.ImageLoader();
+imgLoader.load('resources/images/heightmap-96x64.png', createHeightmap);
+
+function createHeightmap(image) {
+  // extract the data from the image by drawing it to a canvas
+  // and calling getImageData
+  const ctx = document.createElement('canvas').getContext('2d');
+  const {width, height} = image;
+  ctx.canvas.width = width;
+  ctx.canvas.height = height;
+  ctx.drawImage(image, 0, 0);
+  const {data} = ctx.getImageData(0, 0, width, height);
+
+  const geometry = new THREE.Geometry();
+</pre>
+<p>We extracted the data from the image, now we'll make a grid of cells.
+The cells are the squares formed by the center points of each pixel
+from the image</p>
+<div class="threejs_center"><img src="../resources/heightmap-points.svg" style="width: 500px"></div>
+
+<p>For each cell we'll generate 5 vertices. One for each corner of the cell
+and one at the center point of the cell with the average height of the 4
+corner heights.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cellsAcross = width - 1;
+const cellsDeep = height - 1;
+for (let z = 0; z &lt; cellsDeep; ++z) {
+  for (let x = 0; x &lt; cellsAcross; ++x) {
+    // compute row offsets into the height data
+    // we multiply by 4 because the data is R,G,B,A but we
+    // only care about R
+    const base0 = (z * width + x) * 4;
+    const base1 = base0 + (width * 4);
+
+    // look up the height for the for points
+    // around this cell
+    const h00 = data[base0] / 32;
+    const h01 = data[base0 + 4] / 32;
+    const h10 = data[base1] / 32;
+    const h11 = data[base1 + 4] / 32;
+    // compute the average height
+    const hm = (h00 + h01 + h10 + h11) / 4;
+
+    // the corner positions
+    const x0 = x;
+    const x1 = x + 1;
+    const z0 = z;
+    const z1 = z + 1;
+
+    // remember the first index of these 5 vertices
+    const ndx = geometry.vertices.length;
+
+    // add the 4 corners for this cell and the midpoint
+    geometry.vertices.push(
+      new THREE.Vector3(x0, h00, z0),
+      new THREE.Vector3(x1, h01, z0),
+      new THREE.Vector3(x0, h10, z1),
+      new THREE.Vector3(x1, h11, z1),
+      new THREE.Vector3((x0 + x1) / 2, hm, (z0 + z1) / 2),
+    );
+</pre>
+<p>We'll then make 4 triangles from those 5 vertices</p>
+<div class="threejs_center"><img src="../resources/heightmap-triangles.svg" style="width: 500px"></div>
+
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">    // create 4 triangles
+    geometry.faces.push(
+      new THREE.Face3(ndx + 0, ndx + 4, ndx + 1),
+      new THREE.Face3(ndx + 1, ndx + 4, ndx + 3),
+      new THREE.Face3(ndx + 3, ndx + 4, ndx + 2),
+      new THREE.Face3(ndx + 2, ndx + 4, ndx + 0),
+    );
+
+    // add the texture coordinates for each vertex of each face
+    const u0 = x / cellsAcross;
+    const v0 = z / cellsDeep;
+    const u1 = (x + 1) / cellsAcross;
+    const v1 = (z + 1) / cellsDeep;
+    const um = (u0 + u1) / 2;
+    const vm = (v0 + v1) / 2;
+    geometry.faceVertexUvs[0].push(
+      [ new THREE.Vector2(u0, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v0) ],
+      [ new THREE.Vector2(u1, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v1) ],
+      [ new THREE.Vector2(u1, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v1) ],
+      [ new THREE.Vector2(u0, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v0) ],
+    );
+  }
+}
+</pre>
+<p>and finish it up</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">  geometry.computeFaceNormals();
+
+  // center the geometry
+  geometry.translate(width / -2, 0, height / -2);
+
+  const loader = new THREE.TextureLoader();
+  const texture = loader.load('resources/images/star.png');
+
+  const material = new THREE.MeshPhongMaterial({color: 'green', map: texture});
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+}
+</pre>
+<p>A few minor changes to make it easier to view.</p>
+<ul>
+<li>include the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a></li>
+</ul>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
++import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+</pre>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+-const far = 100;
++const far = 200;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+-camera.position.z = 5;
++camera.position.set(20, 20, 20);
+
++const controls = new OrbitControls(camera, canvas);
++controls.target.set(0, 0, 0);
++controls.update();
+</pre>
+<p>add 2 lights</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-{
++function addLight(...pos) {
+  const color = 0xFFFFFF;
+  const intensity = 1;
+  const light = new THREE.DirectionalLight(color, intensity);
+-  light.position.set(-1, 2, 4\);
++  light.position.set(...pos);
+  scene.add(light);
+}
+
++addLight(-1, 2, 4);
++addLight(1, 2, -2);
+</pre>
+<p>and we deleted the code related to spinning the cubes.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-heightmap.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/custom-geometry-heightmap.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>I hope that was a useful instruction to making your own
+geometry using <code class="notranslate" translate="no">Geometry</code>.</p>
+<p>In <a href="custom-buffergeometry.html">another article</a> we'll go over <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 113 - 0
manual/en/debugging-glsl.html

@@ -0,0 +1,113 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Debugging - GLSL</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Debugging - GLSL">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Debugging - GLSL</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This site so far does not teach GLSL just like it does not teach JavaScript.
+Those are really large topics. If you want to learn GLSL consider checking out
+<a href="https://webglfundamentals.org">these articles</a> as a starting place.</p>
+<p>If you already know GLSL then here are a few tips for debugging.</p>
+<p>When I'm making a new GLSL shader and nothing appears generally
+the first thing I do is change the fragment shader to return a solid
+color. For example at the very bottom of the shader I might put</p>
+<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">void main() {
+
+  ...
+
+  gl_FragColor = vec4(1, 0, 0, 1);  // red
+}
+</pre>
+<p>If I see the object I was trying to draw then I know the issue is
+related to my fragment shader. It could be anything like bad textures,
+uninitialized uniforms, uniforms with the wrong values but at least
+I have a direction to look.</p>
+<p>To test some of those I might start trying to draw some of the inputs.
+For example if I'm using normals in the fragment shader then I might
+add</p>
+<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1);
+</pre>
+<p>Normals go from -1 to +1 so by multiplying by 0.5 and adding 0.5 we get
+values that go from 0.0 to 1.0 which makes them useful for colors.</p>
+<p>Try this with some things you know work and you'll start getting an idea
+of what normals <em>normally</em> look like. If your normals don't look normal
+then you have some clue where to look. If you're manipulating normals
+in the fragments shader you can use the same technique to draw the
+result of that manipulation.</p>
+<div class="threejs_center"><img src="../resources/images/standard-primitive-normals.jpg" style="width: 650px;"></div>
+
+<p>Similarly if we're using textures there will be texture coordinates and we
+can draw them with something like</p>
+<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">gl_FragColor = vec4(fract(vUv), 0, 1);
+</pre>
+<p>The <code class="notranslate" translate="no">fract</code> is there in case we're using texture coordinates that go outside
+the 0 to 1 range. This is common if <code class="notranslate" translate="no">texture.repeat</code> is set to something greater
+than 1.</p>
+<div class="threejs_center"><img src="../resources/images/standard-primitive-uvs.jpg" style="width: 650px;"></div>
+
+<p>You can do similar things for all values in your fragment shader. Figure out
+what their range is likely to be, add some code to set <code class="notranslate" translate="no">gl_FragColor</code> with
+that range scaled to 0.0 to 1.0</p>
+<p>To check textures try a <a href="/docs/#api/en/textures/CanvasTexture"><code class="notranslate" translate="no">CanvasTexture</code></a> or a <a href="/docs/#api/en/textures/DataTexture"><code class="notranslate" translate="no">DataTexture</code></a> that you 
+know works.</p>
+<p>Conversely, if after setting <code class="notranslate" translate="no">gl_FragColor</code> to red I still see nothing
+then I have a hint my issue might be in the direction of the things
+related to the vertex shader. Some matrices might be wrong or my
+attributes might have bad data or be setup incorrectly.</p>
+<p>I'd first look at the matrices. I might put a breakpoint right after
+my call to <code class="notranslate" translate="no">renderer.render(scene, camera)</code> and then start expanding
+things in the inspector. Is the camera's world matrix and projection
+matrix at least not full of <code class="notranslate" translate="no">NaN</code>s? Expanding the scene and looking
+at its <code class="notranslate" translate="no">children</code> I'd check that the world matrices look reasonable (no <code class="notranslate" translate="no">NaN</code>s)
+and last 4 values of each matrix look reasonable for my scene. If I 
+expect my scene to be 50x50x50 units and some matrix shows 552352623.123 
+clearly something is wrong there.</p>
+<div class="threejs_center"><img src="../resources/images/inspect-matrices.gif"></div>
+
+<p>Just like we did for the fragment shader we can also draw values from the
+vertex shader by passing them to the fragment shader. Declare a varying
+in both and pass the value you're not sure is correct. In fact if my
+shader use using normals I'll change the fragment shader to display them
+like is mentioned above and then just set <code class="notranslate" translate="no">vNormal</code> to the value I want 
+to display but scaled so the values go from 0.0 to 1.0. I then look at the
+results and see if they fit my expectations.</p>
+<p>Another good thing to do is use a simpler shader. Can you draw your data
+with <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>? If you can then try it and make sure it shows
+up as expected.</p>
+<p>If not what's the simplest vertex shader that will let you visualize your
+geometry? Usually it's as simple as</p>
+<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">gl_Position = projection * modelView * vec4(position.xyz, 1);
+</pre>
+<p>If that works start adding in your changes a little at a time.</p>
+<p>Yet another thing you can do is use the
+<a href="https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob?hl=en">Shader Editor extension for Chrome</a>
+or similar for other browsers. It's a great way to look at how other shaders
+are working. It's also good as you can make some of the changes suggested above
+live while the code is running.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 494 - 0
manual/en/debugging-javascript.html

@@ -0,0 +1,494 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Debugging JavaScript</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Debugging JavaScript">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Debugging JavaScript</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>Most of this article is not directly about THREE.js but is
+rather about debugging JavaScript in general. It seemed important in
+that many people just starting with THREE.js are also just
+starting with JavaScript so I hope this can help them more easily
+solve any issues they run into.</p>
+<p>Debugging is a big topic and I probably can't begin to cover
+everything there is to know but if you're new to JavaScript
+then here's an attempt to give a few pointers. I strongly
+suggest you take some time to learn them. They'll help you 
+enormously in your learning.</p>
+<h2 id="learn-your-browser-s-developer-tools">Learn your Browser's Developer Tools</h2>
+<p>All browsers have developer tools. 
+<a href="https://developers.google.com/web/tools/chrome-devtools/">Chrome</a>,
+<a href="https://developer.mozilla.org/en-US/docs/Tools">Firefox</a>, 
+<a href="https://developer.apple.com/safari/tools/">Safari</a>, 
+<a href="https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide">Edge</a>.</p>
+<p>In Chrome you can click the the <code class="notranslate" translate="no">⋮</code> icon, pick More Tools-&gt;Developer Tools
+to get to the developer tools. A keyboard shortcut is also shown there.</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-chrome.jpg" style="width: 789px;"></div>
+
+<p>In Firefox you click the <code class="notranslate" translate="no">☰</code> icon, pick "Web Developer", then pick
+"Toggle Tools"</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-firefox.jpg" style="width: 786px;"></div>
+
+<p>In Safari you first have to enable the Develop menu from the 
+Advanced Safari Preferences.</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-enable-safari.jpg" style="width: 775px;"></div>
+
+<p>Then in the Develop menu you can pick "Show/Connect Web Inspector".</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-safari.jpg" style="width: 777px;"></div>
+
+<p>With Chrome you can also <a href="https://developers.google.com/web/tools/chrome-devtools/remote-debugging/">use Chrome on your computer to debug webpages running on Chrome on your Android phone or tablet</a>.
+Similarly with Safari you can 
+<a href="https://www.google.com/search?q=safari+remote+debugging+ios">use your computer to debug webpages running on Safari on iPhones and iPads</a>.</p>
+<p>I'm most familiar with Chrome so this guide will be using Chrome
+as an example when referring to tools but most browsers have similar
+features so it should be easy to apply anything here to all browsers.</p>
+<h2 id="turn-off-the-cache">Turn off the cache</h2>
+<p>Browsers try to reuse data they've already downloaded. This is great
+for users so if you visit a website a second time many of the files
+used to display the site will not have be downloaded again.</p>
+<p>On the other hand this can be bad for web development. You change
+a file on your computer, reload the page, and you don't see the changes
+because the browser uses the version it got last time.</p>
+<p>One solution during web development is to turn off the cache. This
+way the browser will always get the newest versions of your files.</p>
+<p>First pick settings from the corner menu</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-chrome-settings.jpg" style="width: 778px"></div>
+
+<p>Then pick "Disable Cache (while DevTools is open)".</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-chrome-disable-cache.jpg" style="width: 779px"></div>
+
+<h2 id="use-the-javascript-console">Use the JavaScript console</h2>
+<p>Inside all devtools is a <em>console</em>. It shows warnings and error messages.</p>
+<p><strong> READ THE MESSAGES!! </strong></p>
+<p>Typically there should be only 1 or 2 messages.</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-no-errors.jpg" style="width: 779px"></div>
+
+<p>If you see any others <strong>READ THEM</strong>. For example:</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-errors.jpg" style="width: 779px"></div>
+
+<p>I mis-spelled "three" as "threee"</p>
+<p>You can also print your own info to the console with with <code class="notranslate" translate="no">console.log</code> as in</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">console.log(someObject.position.x, someObject.position.y, someObject.position.z);
+</pre>
+<p>Even cooler, if you log an object you can inspect it. For example if we log
+the root scene object from <a href="load-gltf.html">the gLTF article</a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">  {
+    const gltfLoader = new GLTFLoader();
+    gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
+      const root = gltf.scene;
+      scene.add(root);
++      console.log(root);
+</pre>
+<p>Then we can expand that object in the JavaScript console</p>
+<div class="threejs_center"><img class="border" src="../resources/images/devtools-console-object.gif"></div>
+
+<p>You can also use <code class="notranslate" translate="no">console.error</code> which reports the message in red
+in includes a stack trace.</p>
+<h2 id="put-data-on-screen">Put data on screen</h2>
+<p>Another obvious but often overlooked way is to add <code class="notranslate" translate="no">&lt;div&gt;</code> or <code class="notranslate" translate="no">&lt;pre&gt;</code> tags
+and put data in them.</p>
+<p>The most obvious way is to make some HTML elements</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;canvas id="c"&gt;&lt;/canvas&gt;
++&lt;div id="debug"&gt;
++  &lt;div&gt;x:&lt;span id="x"&gt;&lt;/span&gt;&lt;/div&gt;
++  &lt;div&gt;y:&lt;span id="y"&gt;&lt;/span&gt;&lt;/div&gt;
++  &lt;div&gt;z:&lt;span id="z"&gt;&lt;/span&gt;&lt;/div&gt;
++&lt;/div&gt;
+</pre>
+<p>Style them so they stay on top of the canvas. (assuming your canvas
+fills the page)</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;style&gt;
+#debug {
+  position: absolute;
+  left: 1em;
+  top: 1em;
+  padding: 1em;
+  background: rgba(0, 0, 0, 0.8);
+  color: white;
+  font-family: monospace;
+}
+&lt;/style&gt;
+</pre>
+<p>And then looking the elements up and setting their content.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// at init time
+const xElem = document.querySelector('#x');
+const yElem = document.querySelector('#y');
+const zElem = document.querySelector('#z');
+
+// at render or update time
+xElem.textContent = someObject.position.x.toFixed(3);
+yElem.textContent = someObject.position.y.toFixed(3);
+zElem.textContent = someObject.position.z.toFixed(3);
+</pre>
+<p>This is more useful for real time values</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/debug-js-html-elements.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/debug-js-html-elements.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Another way to put data on the screen is to make a clearing logger.
+I just made that term up but lots of games I've worked on have used this solution. The idea
+is you have a buffer that displays messages for only one frame.
+Any part of your code that wants to display data calls some function
+to add data to that buffer every frame. This is much less work
+than making an element per piece of data above.</p>
+<p>For example let's change the HTML from above to just this</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;canvas id="c"&gt;&lt;/canvas&gt;
+&lt;div id="debug"&gt;
+  &lt;pre&gt;&lt;/pre&gt;
+&lt;/div&gt;
+</pre>
+<p>And let's make simple class to manage this <em>clear back buffer</em>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ClearingLogger {
+  constructor(elem) {
+    this.elem = elem;
+    this.lines = [];
+  }
+  log(...args) {
+    this.lines.push([...args].join(' '));
+  }
+  render() {
+    this.elem.textContent = this.lines.join('\n');
+    this.lines = [];
+  }
+}
+</pre>
+<p>Then let's make a simple example that every time we click the mouse makes a mesh
+that moves in a random direction for 2 seconds. We'll start with one of the
+examples from the article on <a href="responsive.html">making things responsive</a></p>
+<p>Here's the code that adds a new <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> every time we click the mouse</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.SphereGeometry();
+const material = new THREE.MeshBasicMaterial({color: 'red'});
+
+const things = [];
+
+function rand(min, max) {
+  if (max === undefined) {
+    max = min;
+    min = 0;
+  }
+  return Math.random() * (max - min) + min;
+}
+
+function createThing() {
+  const mesh = new THREE.Mesh(geometry, material);
+  scene.add(mesh);
+  things.push({
+    mesh,
+    timer: 2,
+    velocity: new THREE.Vector3(rand(-5, 5), rand(-5, 5), rand(-5, 5)),
+  });
+}
+
+canvas.addEventListener('click', createThing);
+</pre>
+<p>And here's the code that moves the meshes we created, logs them,
+and removes them when their timer has run out</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const logger = new ClearingLogger(document.querySelector('#debug pre'));
+
+let then = 0;
+function render(now) {
+  now *= 0.001;  // convert to seconds
+  const deltaTime = now - then;
+  then = now;
+
+  ...
+
+  logger.log('fps:', (1 / deltaTime).toFixed(1));
+  logger.log('num things:', things.length);
+  for (let i = 0; i &lt; things.length;) {
+    const thing = things[i];
+    const mesh = thing.mesh;
+    const pos = mesh.position;
+    logger.log(
+        'timer:', thing.timer.toFixed(3), 
+        'pos:', pos.x.toFixed(3), pos.y.toFixed(3), pos.z.toFixed(3));
+    thing.timer -= deltaTime;
+    if (thing.timer &lt;= 0) {
+      // remove this thing. Note we don't advance `i`
+      things.splice(i, 1);
+      scene.remove(mesh);
+    } else {
+      mesh.position.addScaledVector(thing.velocity, deltaTime);
+      ++i;
+    }
+  }
+
+  renderer.render(scene, camera);
+  logger.render();
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>Now click the mouse a bunch in the example below</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/debug-js-clearing-logger.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/debug-js-clearing-logger.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<h2 id="query-parameters">Query Parameters</h2>
+<p>Another thing to remember is that webpages can have data passed
+into them either via query parameters or the anchor, sometimes called
+the search and the hash.</p>
+<pre class="prettyprint showlinemods notranslate notranslate" translate="no">https://domain/path/?query#anchor
+</pre><p>You can use this to make features optional or pass in parameters.</p>
+<p>For example let's take the previous example and make it so
+the debug stuff only shows up if we put <code class="notranslate" translate="no">?debug=true</code> in the URL.</p>
+<p>First we need some code to parse the query string</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">/**
+  * Returns the query parameters as a key/value object. 
+  * Example: If the query parameters are
+  *
+  *    abc=123&amp;def=456&amp;name=gman
+  *
+  * Then `getQuery()` will return an object like
+  *
+  *    {
+  *      abc: '123',
+  *      def: '456',
+  *      name: 'gman',
+  *    }
+  */
+function getQuery() {
+  return Object.fromEntries(new URLSearchParams(window.location.search).entries());
+}
+</pre>
+<p>Then we might make the debug element not show by default</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;canvas id="c"&gt;&lt;/canvas&gt;
++&lt;div id="debug" style="display: none;"&gt;
+  &lt;pre&gt;&lt;/pre&gt;
+&lt;/div&gt;
+</pre>
+<p>Then in the code we read the params and choose to un-hide the
+debug info if and only if <code class="notranslate" translate="no">?debug=true</code> is passed in</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const query = getQuery();
+const debug = query.debug === 'true';
+const logger = debug
+   ? new ClearingLogger(document.querySelector('#debug pre'))
+   : new DummyLogger();
+if (debug) {
+  document.querySelector('#debug').style.display = '';
+}
+</pre>
+<p>We also made a <code class="notranslate" translate="no">DummyLogger</code> that does nothing and chose to use it if <code class="notranslate" translate="no">?debug=true</code> has not been passed in.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class DummyLogger {
+  log() {}
+  render() {}
+}
+</pre>
+<p>You can see if we use this url:</p>
+<p><a target="_blank" href="../examples/debug-js-params.html">debug-js-params.html</a></p>
+<p>there is no debug info but if we use this url:</p>
+<p><a target="_blank" href="../examples/debug-js-params.html?debug=true">debug-js-params.html?debug=true</a></p>
+<p>there is debug info.</p>
+<p>Multiple parameters can be passed in by separating with '&amp;' as in <code class="notranslate" translate="no">somepage.html?someparam=somevalue&amp;someotherparam=someothervalue</code>. 
+Using parameters like this we can pass in all kinds of options. Maybe <code class="notranslate" translate="no">speed=0.01</code> to slow down our app for making it easier to understand something or <code class="notranslate" translate="no">showHelpers=true</code> for whether or not to add helpers
+that show the lights, shadow, or camera frustum seen in other lessons.</p>
+<h2 id="learn-to-use-the-debugger">Learn to use the Debugger</h2>
+<p>Every browser has a debugger where you can pause your program
+step through line by line and inspect all the variables.</p>
+<p>Teaching you how to use a debugger is too big a topic for this
+article but here's a few links</p>
+<ul>
+<li><a href="https://developers.google.com/web/tools/chrome-devtools/javascript/">Get Started with Debugging JavaScript in Chrome DevTools</a></li>
+<li><a href="https://javascript.info/debugging-chrome">Debugging in Chrome</a></li>
+<li><a href="https://hackernoon.com/tips-and-tricks-for-debugging-in-chrome-developer-tools-458ade27c7ab">Tips and Tricks for Debugging in Chrome Developer Tools</a></li>
+</ul>
+<h2 id="check-for-nan-in-the-debugger-or-elsewhere">Check for <code class="notranslate" translate="no">NaN</code> in the debugger or elsewhere</h2>
+<p><code class="notranslate" translate="no">NaN</code> is short for Not A Number. It's what JavaScript will assign
+as a value when you do something that doesn't make sense mathwise.</p>
+<p>As a simple example</p>
+<div class="threejs_center"><img class="border" src="../resources/images/nan-banana.png" style="width: 180px;"></div>
+
+<p>Often when I'm making something and nothing appears on the screen
+I'll check some values and if I see <code class="notranslate" translate="no">NaN</code> I will instantly have a 
+place to start looking.</p>
+<p>As an example when I first started making the path for the
+<a href="load-gltf.html">article about loading gLTF files</a> I made
+a curve using the <a href="/docs/#api/en/extras/curves/SplineCurve"><code class="notranslate" translate="no">SplineCurve</code></a> class which makes a 2D curve.</p>
+<p>I then used that curve to move the cars like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">curve.getPointAt(zeroToOnePointOnCurve, car.position);
+</pre>
+<p>Internally <code class="notranslate" translate="no">curve.getPointAt</code> calls the <code class="notranslate" translate="no">set</code> function
+on the object passed as the second argument. In this case that
+second argument is <code class="notranslate" translate="no">car.position</code> which is a <a href="/docs/#api/en/math/Vector3"><code class="notranslate" translate="no">Vector3</code></a>. <a href="/docs/#api/en/math/Vector3"><code class="notranslate" translate="no">Vector3</code></a>'s
+<code class="notranslate" translate="no">set</code> function requires 3 arguments, x, y, and z but <a href="/docs/#api/en/extras/curves/SplineCurve"><code class="notranslate" translate="no">SplineCurve</code></a> is a 2D curve
+and so it calls <code class="notranslate" translate="no">car.position.set</code> with just x and y.</p>
+<p>The result is that <code class="notranslate" translate="no">car.position.set</code> sets x to x, y to y, and z to <code class="notranslate" translate="no">undefined</code>.</p>
+<p>A quick glance in the debugger looking at the car's <code class="notranslate" translate="no">matrixWorld</code>
+showed a bunch of <code class="notranslate" translate="no">NaN</code> values.</p>
+<div class="threejs_center"><img class="border" src="../resources/images/debugging-nan.gif" style="width: 476px;"></div>
+
+<p>Seeing the matrix had <code class="notranslate" translate="no">NaN</code>s in it suggested something like <code class="notranslate" translate="no">position</code>,
+<code class="notranslate" translate="no">rotation</code>, <code class="notranslate" translate="no">scale</code> or some other function that affects that matrix had bad
+data. Working backward from their it was easy to track down the issue.</p>
+<p>In top of <code class="notranslate" translate="no">NaN</code> there's also <code class="notranslate" translate="no">Infinity</code> which is a similar sign there
+is a math bug somewhere.</p>
+<h2 id="look-in-the-code-">Look In the Code!</h2>
+<p>THREE.js is Open Source. Don't be afraid to look inside the code!
+You can look inside on <a href="https://github.com/mrdoob/three.js">github</a>.
+You can also look inside by stepping into functions in the debugger.</p>
+<h2 id="put-requestanimationframe-at-bottom-of-your-render-function-">Put <code class="notranslate" translate="no">requestAnimationFrame</code> at bottom of your render function.</h2>
+<p>I see this pattern often</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
+   requestAnimationFrame(render);
+
+   // -- do stuff --
+
+   renderer.render(scene, camera);
+}
+requestAnimationFrame(render);
+</pre>
+<p>I'd suggest that putting the call to <code class="notranslate" translate="no">requestAnimationFrame</code> at
+the bottom as in</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
+   // -- do stuff --
+
+   renderer.render(scene, camera);
+
+   requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+</pre>
+<p>The biggest reason is it means your code will stop if you have an error. Putting
+<code class="notranslate" translate="no">requestAnimationFrame</code> at the top means your code will keep running even if you
+have an error since you already requested another frame. IMO it's better to find
+those errors than to ignore them. They could easily be the reason something is
+not appearing as you expect it to but unless your code stops you might not even
+notice.</p>
+<h2 id="check-your-units-">Check your units!</h2>
+<p>This basically means knowing for example when to use degrees vs
+when to use radians. It's unfortunate that THREE.js does not
+consistently use the same units everywhere. Off the top of my head
+the camera's field of view is in degrees. All other angles are in
+radians.</p>
+<p>The other place to look out is your world unit size. Until
+recently 3D apps could choose any unit size they wanted. One app might choose
+1 unit = 1cm. Another might choose 1 unit = 1 foot. It's actually still
+true that you can chose any units you want for certain applications.
+That said, THREE.js assumes 1 unit = 1 meter. This is important for
+things like physically based rendering which uses meters to compute
+lighting effects. It's also important for AR and VR which need to
+deal with real world units like where your phone is or where the VR
+controllers are.</p>
+<h2 id="making-a-minimal-complete-verifiable-example-for-stack-overflow">Making a <em>Minimal, Complete, Verifiable, Example</em> for Stack Overflow</h2>
+<p>If you decide to ask a question about THREE.js it's almost always
+required for you to provide an MCVE which stands for Minimal, Complete,
+Verifiable, Example.</p>
+<p>The <strong>Minimal</strong> part is important. Let's say you where having an issue with the
+path movement in the last example of the <a href="load-gltf.html">loading a gLTF
+article</a>. That example has many parts. Listing them out
+it has</p>
+<ol>
+<li>A bunch of HTML</li>
+<li>Some CSS</li>
+<li>Lights</li>
+<li>Shadows</li>
+<li>lil-gui code to manipulate shadows</li>
+<li>Code to load a .GLTF file</li>
+<li>Code to resize the canvas.</li>
+<li>Code to move the cars along paths</li>
+</ol>
+<p>That's pretty huge. If your question is only about the path following part you
+can remove most of the HTML as you only need a <code class="notranslate" translate="no">&lt;canvas&gt;</code> and a <code class="notranslate" translate="no">&lt;script&gt;</code> tag
+for THREE.js. You can remove the CSS and the resizing code. You can remove .GLTF
+code because you only care about the path. You can remove the lights and the
+shadows by using a <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>. You can certainly remove the lil-gui
+code. The code makes a ground plane with a texture. It would be easier to use a
+<a href="/docs/#api/en/helpers/GridHelper"><code class="notranslate" translate="no">GridHelper</code></a>. Finally if our question is about moving things on a path we could
+just use cubes on the path instead of loaded car models.</p>
+<p>Here's a more minimal example taking all the above into account. It
+shrunk from 271 lines to 135. We might consider shrinking it even
+more by simplifying our path. Maybe a path with 3 or 4 points would
+work just as well as our path with 21 points.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/debugging-mcve.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/debugging-mcve.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>I kept the <code class="notranslate" translate="no">OrbitController</code> just because it's useful for others
+to move the camera and figure out what's going on but depending
+on your issue you might be able to remove that as well.</p>
+<p>The best thing about making an MCVE is we'll often solve our own
+problem. The process of removing everything that's not needed and
+making the smallest example we can that reproduces the issue more
+often than not leads us to our bug.</p>
+<p>On top of that it's respectful of all the people's time who you are
+asking to look at your code on Stack Overflow. By making the minimal
+example you make it much easier for them to help you. You'll also
+learn in the process.</p>
+<p>Also important, when you go to Stack Overflow to post your question <strong>put your
+code <a href="https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/">in a snippet</a>.</strong>
+Of course you are welcome to use JSFiddle or Codepen or similar site to test out
+your MCVE but once you actually get to posting your question on Stack Overflow
+you're required to put the code to reproduce your issue <strong>in the question itself</strong>. 
+By making a snippet you satisfy that requirement.</p>
+<p>Also note all the live examples on this site should run as snippets.
+Just copy the HTML, CSS, and JavaScript parts to their respective
+parts of the <a href="https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/">snippet editor</a>.
+Just remember to try to remove the parts that are not relevant to
+your issue and try to make your code the minimal amount needed.</p>
+<p>Follow these suggestions and you're far more likely to get help
+with your issue.</p>
+<h2 id="use-a-meshbasicmaterial-">Use a <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a></h2>
+<p>Because the <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a> uses no lights this is one way to 
+remove reasons something might not be showing up. If your objects
+show up using <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a> but not with whatever materials
+you were using then you know the issue is likely with the materials
+or the lights and not some other part of the code.</p>
+<h2 id="check-your-near-and-far-settings-for-your-camera">Check your <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings for your camera</h2>
+<p>A <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> has <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings which are covered in the
+<a href="cameras.html">article on cameras</a>. Make sure they are set to fit the
+space that contains your objects. Maybe even just <strong>temporarily</strong> set them to
+something large like <code class="notranslate" translate="no">near</code> = 0.001 and <code class="notranslate" translate="no">far</code> = 1000000. You will likely run
+into depth resolution issues but you'll at least be able to see your objects
+provided they are in front of the camera.</p>
+<h2 id="check-your-scene-is-in-front-of-the-camera">Check your scene is in front of the camera</h2>
+<p>Sometimes things don't appear because they are not in front of the camera. If
+your camera is not controllable try adding camera control like the
+<code class="notranslate" translate="no">OrbitController</code> so you can look around and find your scene. Or, try framing
+the scene using code which is covered in <a href="load-obj.html">this article</a>.
+That code finds the size of part of the scene and then moves the camera and
+adjusts the <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings to make it visible. You can then look in
+the debugger or add some <code class="notranslate" translate="no">console.log</code> messages to print the size and center of
+the scene.</p>
+<h2 id="put-something-in-front-of-the-camera">Put something in front of the camera</h2>
+<p>This is just another way of saying if all else fails start with
+something that works and then slowly add stuff back in. If you get
+a screen with nothing on it then try putting something directly in
+front of the camera. Make a sphere or box, give it a simple material
+like the <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a> and make sure you can get that on the screen.
+Then start adding things back a little at time and testing. Eventually
+you'll either reproduce your bug or you'll find it on the way.</p>
+<hr>
+<p>These were a few tips for debugging JavaScript. Let's also go
+over <a href="debugging-glsl.html">some tips for debugging GLSL</a>.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 274 - 0
manual/en/fog.html

@@ -0,0 +1,274 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Fog</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Fog">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Fog</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This article is part of a series of articles about three.js. The
+first article is <a href="fundamentals.html">three.js fundamentals</a>. If
+you haven't read that yet and you're new to three.js you might want to
+consider starting there. If you haven't read about cameras you might
+want to start with <a href="cameras.html">this article</a>.</p>
+<p>Fog in a 3D engine is generally a way of fading to a specific color
+based on the distance from the camera. In three.js you add fog by
+creating <a href="/docs/#api/en/scenes/Fog"><code class="notranslate" translate="no">Fog</code></a> or <a href="/docs/#api/en/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a> object and setting it on the scene's
+<a href="/docs/#api/en/scenes/Scene#fog"><code class="notranslate" translate="no">fog</code></a> property.</p>
+<p><a href="/docs/#api/en/scenes/Fog"><code class="notranslate" translate="no">Fog</code></a> lets you choose <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings which are distances
+from the camera. Anything closer than <code class="notranslate" translate="no">near</code> is unaffected by fog.
+Anything further than <code class="notranslate" translate="no">far</code> is completely the fog color. Parts between
+<code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> fade from their material color to the fog color.</p>
+<p>There's also <a href="/docs/#api/en/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a> which grows exponentially with distance from the camera.</p>
+<p>To use either type of fog you create one and and assign it to the scene as in</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+{
+  const color = 0xFFFFFF;  // white
+  const near = 10;
+  const far = 100;
+  scene.fog = new THREE.Fog(color, near, far);
+}
+</pre>
+<p>or for <a href="/docs/#api/en/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a> it would be</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+{
+  const color = 0xFFFFFF;
+  const density = 0.1;
+  scene.fog = new THREE.FogExp2(color, density);
+}
+</pre>
+<p><a href="/docs/#api/en/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a> is closer to reality but <a href="/docs/#api/en/scenes/Fog"><code class="notranslate" translate="no">Fog</code></a> is used
+more commonly since it lets you choose a place to apply
+the fog so you can decide to show a clear scene
+up to a certain distance and then fade out to some color
+past that distance.</p>
+<div class="spread">
+  <div>
+    <div data-diagram="fog" style="height: 300px;"></div>
+    <div class="code">THREE.Fog</div>
+  </div>
+  <div>
+    <div data-diagram="fogExp2" style="height: 300px;"></div>
+    <div class="code">THREE.FogExp2</div>
+  </div>
+</div>
+
+<p>It's important to note that the fog is applied to <em>things that are rendered</em>.
+It is part of the calculation of each pixel of the color of the object.
+What that means is if you want your scene to fade to a certain color you
+need to set the fog <strong>and</strong> the background color to the same color.
+The background color is set using the
+<a href="/docs/#api/en/scenes/Scene#background"><code class="notranslate" translate="no">scene.background</code></a>
+property. To pick a background color you attach a <a href="/docs/#api/en/math/Color"><code class="notranslate" translate="no">THREE.Color</code></a> to it. For example</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">scene.background = new THREE.Color('#F00');  // red
+</pre>
+<div class="spread">
+  <div>
+    <div data-diagram="fogBlueBackgroundRed" style="height: 300px;" class="border"></div>
+    <div class="code">fog blue, background red</div>
+  </div>
+  <div>
+    <div data-diagram="fogBlueBackgroundBlue" style="height: 300px;" class="border"></div>
+    <div class="code">fog blue, background blue</div>
+  </div>
+</div>
+
+<p>Here is one of our previous examples with fog added. The only addition
+is right after setting up the scene we add the fog and set the scene's
+background color</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+
++{
++  const near = 1;
++  const far = 2;
++  const color = 'lightblue';
++  scene.fog = new THREE.Fog(color, near, far);
++  scene.background = new THREE.Color(color);
++}
+</pre>
+<p>In the example below the camera's <code class="notranslate" translate="no">near</code> is 0.1 and its <code class="notranslate" translate="no">far</code> is 5.
+The camera is at <code class="notranslate" translate="no">z = 2</code>. The cubes are 1 unit large and at Z = 0.
+This means with a fog setting of <code class="notranslate" translate="no">near = 1</code> and <code class="notranslate" translate="no">far = 2</code> the cubes
+will fade out right around their center.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fog.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/fog.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Let's add an interface so we can adjust the fog. Again we'll use
+<a href="https://github.com/georgealways/lil-gui">lil-gui</a>. lil-gui takes
+an object and a property and automagically makes an interface
+for that type of property. We could just simply let it manipulate
+the fog's <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> properties but it's invalid to have
+<code class="notranslate" translate="no">near</code> be greater than <code class="notranslate" translate="no">far</code> so let's make a helper so lil-gui
+can manipulate a <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> property but we'll make sure <code class="notranslate" translate="no">near</code>
+is less than or equal to <code class="notranslate" translate="no">far</code> and <code class="notranslate" translate="no">far</code> is greater than or equal <code class="notranslate" translate="no">near</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// We use this class to pass to lil-gui
+// so when it manipulates near or far
+// near is never &gt; far and far is never &lt; near
+class FogGUIHelper {
+  constructor(fog) {
+    this.fog = fog;
+  }
+  get near() {
+    return this.fog.near;
+  }
+  set near(v) {
+    this.fog.near = v;
+    this.fog.far = Math.max(this.fog.far, v);
+  }
+  get far() {
+    return this.fog.far;
+  }
+  set far(v) {
+    this.fog.far = v;
+    this.fog.near = Math.min(this.fog.near, v);
+  }
+}
+</pre>
+<p>We can then add it like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const near = 1;
+  const far = 2;
+  const color = 'lightblue';
+  scene.fog = new THREE.Fog(color, near, far);
+  scene.background = new THREE.Color(color);
++
++  const fogGUIHelper = new FogGUIHelper(scene.fog);
++  gui.add(fogGUIHelper, 'near', near, far).listen();
++  gui.add(fogGUIHelper, 'far', near, far).listen();
+}
+</pre>
+<p>The <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> parameters set the minimum and maximum values
+for adjusting the fog. They are set when we setup the camera.</p>
+<p>The <code class="notranslate" translate="no">.listen()</code> at the end of the last 2 lines tells lil-gui to <em>listen</em>
+for changes. That way when we change <code class="notranslate" translate="no">near</code> because of an edit to <code class="notranslate" translate="no">far</code>
+or we change <code class="notranslate" translate="no">far</code> in response to an edit to <code class="notranslate" translate="no">near</code> lil-gui will update
+the other property's UI for us.</p>
+<p>It might also be nice to be able to change the fog color but like was
+mentioned above we need to keep both the fog color and the background
+color in sync. So, let's add another <em>virtual</em> property to our helper
+that will set both colors when lil-gui manipulates it.</p>
+<p>lil-gui can manipulate colors in 4 ways, as a CSS 6 digit hex string (eg: <code class="notranslate" translate="no">#112233</code>). As an hue, saturation, value, object (eg: <code class="notranslate" translate="no">{h: 60, s: 1, v: }</code>).
+As an RGB array (eg: <code class="notranslate" translate="no">[255, 128, 64]</code>). Or, as an RGBA array (eg: <code class="notranslate" translate="no">[127, 200, 75, 0.3]</code>).</p>
+<p>It's easiest for our purpose to use the hex string version since that way
+lil-gui is only manipulating a single value. Fortunately <a href="/docs/#api/en/math/Color"><code class="notranslate" translate="no">THREE.Color</code></a>
+as a <a href="/docs/#api/en/math/Color#getHexString"><code class="notranslate" translate="no">getHexString</code></a> method
+we get use to easily get such a string, we just have to prepend a '#' to the front.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// We use this class to pass to lil-gui
+// so when it manipulates near or far
+// near is never &gt; far and far is never &lt; near
++// Also when lil-gui manipulates color we'll
++// update both the fog and background colors.
+class FogGUIHelper {
+*  constructor(fog, backgroundColor) {
+    this.fog = fog;
++    this.backgroundColor = backgroundColor;
+  }
+  get near() {
+    return this.fog.near;
+  }
+  set near(v) {
+    this.fog.near = v;
+    this.fog.far = Math.max(this.fog.far, v);
+  }
+  get far() {
+    return this.fog.far;
+  }
+  set far(v) {
+    this.fog.far = v;
+    this.fog.near = Math.min(this.fog.near, v);
+  }
++  get color() {
++    return `#${this.fog.color.getHexString()}`;
++  }
++  set color(hexString) {
++    this.fog.color.set(hexString);
++    this.backgroundColor.set(hexString);
++  }
+}
+</pre>
+<p>We then call <code class="notranslate" translate="no">gui.addColor</code> to add a color UI for our helper's virtual property.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const near = 1;
+  const far = 2;
+  const color = 'lightblue';
+  scene.fog = new THREE.Fog(color, near, far);
+  scene.background = new THREE.Color(color);
+
+*  const fogGUIHelper = new FogGUIHelper(scene.fog, scene.background);
+  gui.add(fogGUIHelper, 'near', near, far).listen();
+  gui.add(fogGUIHelper, 'far', near, far).listen();
++  gui.addColor(fogGUIHelper, 'color');
+}
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fog-gui.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/fog-gui.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>You can see setting <code class="notranslate" translate="no">near</code> to like 1.9 and <code class="notranslate" translate="no">far</code> to 2.0 gives
+a very sharp transition between un-fogged and completely fogged.
+where as <code class="notranslate" translate="no">near</code> = 1.1 and <code class="notranslate" translate="no">far</code> = 2.9 should just about be
+the smoothest given our cubes are spinning 2 units away from the camera.</p>
+<p>One last thing, there is a boolean <a href="/docs/#api/en/materials/Material#fog"><code class="notranslate" translate="no">fog</code></a>
+property on a material for whether or not objects rendered
+with that material are affected by fog. It defaults to <code class="notranslate" translate="no">true</code>
+for most materials. As an example of why you might want
+to turn the fog off, imagine you're making a 3D vehicle
+simulator with a view from the driver's seat or cockpit.
+You probably want the fog off for everything inside the vehicle when
+viewing from inside the vehicle.</p>
+<p>A better example might be a house
+and thick fog outside house. Let's say the fog is set to start
+2 meters away (near = 2) and completely fogged out at 4 meters (far = 4).
+Rooms are longer than 2 meters and the house is probably longer
+than 4 meters so you need to set the materials for the inside
+of the house to not apply fog otherwise when standing inside the
+house looking outside the wall at the far end of the room will look
+like it's in the fog.</p>
+<div class="spread">
+  <div>
+    <div data-diagram="fogHouseAll" style="height: 300px;" class="border"></div>
+    <div class="code">fog: true, all</div>
+  </div>
+</div>
+
+<p>Notice the walls and ceiling at the far end of the room are getting fog applied.
+By turning fog off on the materials for the house we can fix that issue.</p>
+<div class="spread">
+  <div>
+    <div data-diagram="fogHouseInsideNoFog" style="height: 300px;" class="border"></div>
+    <div class="code">fog: true, only outside materials</div>
+  </div>
+</div>
+
+<p><canvas id="c"></canvas></p>
+<script type="module" src="../resources/threejs-fog.js"></script>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 437 - 0
manual/en/fundamentals.html

@@ -0,0 +1,437 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Fundamentals</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Fundamentals">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Fundamentals</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This is the first article in a series of articles about three.js.
+<a href="https://threejs.org">Three.js</a> is a 3D library that tries to make
+it as easy as possible to get 3D content on a webpage.</p>
+<p>Three.js is often confused with WebGL since more often than
+not, but not always, three.js uses WebGL to draw 3D.
+<a href="https://webglfundamentals.org">WebGL is a very low-level system that only draws points, lines, and triangles</a>.
+To do anything useful with WebGL generally requires quite a bit of
+code and that is where three.js comes in. It handles stuff
+like scenes, lights, shadows, materials, textures, 3d math, all things that you'd
+have to write yourself if you were to use WebGL directly.</p>
+<p>These tutorials assume you already know JavaScript and, for the
+most part they will use ES6 style. <a href="prerequisites.html">See here for a
+terse list of things you're expected to already know</a>.
+Most browsers that support three.js are auto-updated so most users should
+be able to run this code. If you'd like to make this code run
+on really old browsers look into a transpiler like <a href="https://babeljs.io">Babel</a>.
+Of course users running really old browsers probably have machines
+that can't run three.js.</p>
+<p>When learning most programming languages the first thing people
+do is make the computer print <code class="notranslate" translate="no">"Hello World!"</code>. For 3D one
+of the most common first things to do is to make a 3D cube.
+So let's start with "Hello Cube!"</p>
+<p>Before we get started let's try to give you an idea of the structure
+of a three.js app. A three.js app requires you to create a bunch of
+objects and connect them together. Here's a diagram that represents
+a small three.js app</p>
+<div class="threejs_center"><img src="../resources/images/threejs-structure.svg" style="width: 768px;"></div>
+
+<p>Things to notice about the diagram above.</p>
+<ul>
+<li><p>There is a <a href="/docs/#api/en/constants/Renderer"><code class="notranslate" translate="no">Renderer</code></a>. This is arguably the main object of three.js. You pass a
+<a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a> and a <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a> to a <a href="/docs/#api/en/constants/Renderer"><code class="notranslate" translate="no">Renderer</code></a> and it renders (draws) the portion of
+the 3D scene that is inside the <em>frustum</em> of the camera as a 2D image to a
+canvas.</p>
+</li>
+<li><p>There is a <a href="scenegraph.html">scenegraph</a> which is a tree like
+structure, consisting of various objects like a <a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a> object, multiple
+<a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> objects, <a href="/docs/#api/en/lights/Light"><code class="notranslate" translate="no">Light</code></a> objects, <a href="/docs/#api/en/objects/Group"><code class="notranslate" translate="no">Group</code></a>, <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>, and <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a> objects. A
+<a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a> object defines the root of the scenegraph and contains properties like
+the background color and fog. These objects define a hierarchical parent/child
+tree like structure and represent where objects appear and how they are
+oriented. Children are positioned and oriented relative to their parent. For
+example the wheels on a car might be children of the car so that moving and
+orienting the car's object automatically moves the wheels. You can read more
+about this in <a href="scenegraph.html">the article on scenegraphs</a>.</p>
+<p>Note in the diagram <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a> is half in half out of the scenegraph. This is to
+represent that in three.js, unlike the other objects, a <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a> does not have
+to be in the scenegraph to function. Just like other objects, a <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a>, as a
+child of some other object, will move and orient relative to its parent object.
+There is an example of putting multiple <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a> objects in a scenegraph at
+the end of <a href="scenegraph.html">the article on scenegraphs</a>.</p>
+</li>
+<li><p><a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> objects represent drawing a specific <code class="notranslate" translate="no">Geometry</code> with a specific
+ <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a>. Both <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> objects and <code class="notranslate" translate="no">Geometry</code> objects can be used by
+ multiple <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> objects. For example to draw two blue cubes in different
+ locations we could need two <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> objects to represent the position and
+ orientation of each cube. We would only need one <code class="notranslate" translate="no">Geometry</code> to hold the vertex
+ data for a cube and we would only need one <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> to specify the color
+ blue. Both <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> objects could reference the same <code class="notranslate" translate="no">Geometry</code> object and the
+ same <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> object.</p>
+</li>
+<li><p><code class="notranslate" translate="no">Geometry</code> objects represent the vertex data of some piece of geometry like
+ a sphere, cube, plane, dog, cat, human, tree, building, etc...
+ Three.js provides many kinds of built in
+ <a href="primitives.html">geometry primitives</a>. You can also
+ <a href="custom-buffergeometry.html">create custom geometry</a> as well as
+ <a href="load-obj.html">load geometry from files</a>.</p>
+</li>
+<li><p><a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> objects represent
+<a href="materials.html">the surface properties used to draw geometry</a>
+including things like the color to use and how shiny it is. A <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> can also
+reference one or more <a href="/docs/#api/en/textures/Texture"><code class="notranslate" translate="no">Texture</code></a> objects which can be used, for example, 
+to wrap an image onto the surface of a geometry.</p>
+</li>
+<li><p><a href="/docs/#api/en/textures/Texture"><code class="notranslate" translate="no">Texture</code></a> objects generally represent images either <a href="textures.html">loaded from image files</a>,
+<a href="canvas-textures.html">generated from a canvas</a> or <a href="rendertargets.html">rendered from another scene</a>.</p>
+</li>
+<li><p><a href="/docs/#api/en/lights/Light"><code class="notranslate" translate="no">Light</code></a> objects represent <a href="lights.html">different kinds of lights</a>.</p>
+</li>
+</ul>
+<p>Given all of that we're going to make the smallest <em>"Hello Cube"</em> setup
+that looks like this</p>
+<div class="threejs_center"><img src="../resources/images/threejs-1cube-no-light-scene.svg" style="width: 500px;"></div>
+
+<p>First let's load three.js</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;script type="module"&gt;
+import * as THREE from '../../build/three.module.js';
+&lt;/script&gt;
+</pre>
+<p>It's important you put <code class="notranslate" translate="no">type="module"</code> in the script tag. This enables
+us to use the <code class="notranslate" translate="no">import</code> keyword to load three.js. There are other ways
+to load three.js but as of r106 using modules is the recommended way.
+Modules have the advantage that they can easily import other modules
+they need. That saves us from having to manually load extra scripts
+they are dependent on.</p>
+<p>Next we need is a <code class="notranslate" translate="no">&lt;canvas&gt;</code> tag so...</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
+  &lt;canvas id="c"&gt;&lt;/canvas&gt;
+&lt;/body&gt;
+</pre>
+<p>We will ask three.js to draw into that canvas so we need to look it up.</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;script type="module"&gt;
+import * as THREE from '../../build/three.module.js';
+
++function main() {
++  const canvas = document.querySelector('#c');
++  const renderer = new THREE.WebGLRenderer({canvas});
++  ...
+&lt;/script&gt;
+</pre>
+<p>After we look up the canvas we create a <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>. The renderer
+is the thing responsible for actually taking all the data you provide
+and rendering it to the canvas. In the past there have been other renderers
+like <code class="notranslate" translate="no">CSSRenderer</code>, a <code class="notranslate" translate="no">CanvasRenderer</code> and in the future there may be a
+<code class="notranslate" translate="no">WebGL2Renderer</code> or <code class="notranslate" translate="no">WebGPURenderer</code>. For now there's the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>
+that uses WebGL to render 3D to the canvas.</p>
+<p>Note there are some esoteric details here. If you don't pass a canvas
+into three.js it will create one for you but then you have to add it
+to your document. Where to add it may change depending on your use case
+and you'll have to change your code so I find that passing a canvas
+to three.js feels a little more flexible. I can put the canvas anywhere
+and the code will find it whereas if I had code to insert the canvas
+into to the document I'd likely have to change that code if my use case
+changed.</p>
+<p>Next up we need a camera. We'll create a <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+const far = 5;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+</pre>
+<p><code class="notranslate" translate="no">fov</code> is short for <code class="notranslate" translate="no">field of view</code>. In this case 75 degrees in the vertical
+dimension. Note that most angles in three.js are in radians but for some
+reason the perspective camera takes degrees.</p>
+<p><code class="notranslate" translate="no">aspect</code> is the display aspect of the canvas. We'll go over the details
+<a href="responsive.html">in another article</a> but by default a canvas is
+ 300x150 pixels which makes the aspect 300/150 or 2.</p>
+<p><code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> represent the space in front of the camera
+that will be rendered. Anything before that range or after that range
+will be clipped (not drawn).</p>
+<p>Those four settings define a <em>"frustum"</em>. A <em>frustum</em> is the name of
+a 3d shape that is like a pyramid with the tip sliced off. In other
+words think of the word "frustum" as another 3D shape like sphere,
+cube, prism, frustum.</p>
+<p><img src="../resources/frustum-3d.svg" width="500" class="threejs_center"></p>
+<p>The height of the near and far planes are determined by the field of view.
+The width of both planes is determined by the field of view and the aspect.</p>
+<p>Anything inside the defined frustum will be be drawn. Anything outside
+will not.</p>
+<p>The camera defaults to looking down the -Z axis with +Y up. We'll put our cube
+at the origin so we need to move the camera back a little from the origin
+in order to see anything.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.position.z = 2;
+</pre>
+<p>Here's what we're aiming for.</p>
+<p><img src="../resources/scene-down.svg" width="500" class="threejs_center"></p>
+<p>In the diagram above we can see our camera is at <code class="notranslate" translate="no">z = 2</code>. It's looking
+down the -Z axis. Our frustum starts 0.1 units from the front of the camera
+and goes to 5 units in front of the camera. Because in this diagram we are looking down,
+the field of view is affected by the aspect. Our canvas is twice as wide
+as it is tall so across the canvas the field of view will be much wider than
+our specified 75 degrees which is the vertical field of view.</p>
+<p>Next we make a <a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a>. A <a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a> in three.js is the root of a form of scene graph.
+Anything you want three.js to draw needs to be added to the scene. We'll
+cover more details of <a href="scenegraph.html">how scenes work in a future article</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+</pre>
+<p>Next up we create a <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> which contains the data for a box.
+Almost anything we want to display in Three.js needs geometry which defines
+the vertices that make up our 3D object.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const boxWidth = 1;
+const boxHeight = 1;
+const boxDepth = 1;
+const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
+</pre>
+<p>We then create a basic material and set its color. Colors can
+be specified using standard CSS style 6 digit hex color values.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.MeshBasicMaterial({color: 0x44aa88});
+</pre>
+<p>We then create a <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>. A <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> in three represents the combination
+of a three things</p>
+<ol>
+<li>A <code class="notranslate" translate="no">Geometry</code> (the shape of the object)</li>
+<li>A <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> (how to draw the object, shiny or flat, what color, what texture(s) to apply. Etc.)</li>
+<li>The position, orientation, and scale of that object in the scene relative to its parent. In the code below that parent is the scene.</li>
+</ol>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cube = new THREE.Mesh(geometry, material);
+</pre>
+<p>And finally we add that mesh to the scene</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">scene.add(cube);
+</pre>
+<p>We can then render the scene by calling the renderer's render function
+and passing it the scene and the camera</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">renderer.render(scene, camera);
+</pre>
+<p>Here's a working example</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/fundamentals.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>It's kind of hard to tell that is a 3D cube since we're viewing
+it directly down the -Z axis and the cube itself is axis aligned
+so we're only seeing a single face.</p>
+<p>Let's animate it spinning and hopefully that will make
+it clear it's being drawn in 3D. To animate it we'll render inside a render loop using
+<a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame"><code class="notranslate" translate="no">requestAnimationFrame</code></a>.</p>
+<p>Here's our loop</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
+  time *= 0.001;  // convert time to seconds
+
+  cube.rotation.x = time;
+  cube.rotation.y = time;
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+</pre>
+<p><code class="notranslate" translate="no">requestAnimationFrame</code> is a request to the browser that you want to animate something.
+You pass it a function to be called. In our case that function is <code class="notranslate" translate="no">render</code>. The browser
+will call your function and if you update anything related to the display of the
+page the browser will re-render the page. In our case we are calling three's
+<code class="notranslate" translate="no">renderer.render</code> function which will draw our scene.</p>
+<p><code class="notranslate" translate="no">requestAnimationFrame</code> passes the time since the page loaded to
+our function. That time is passed in milliseconds. I find it's much
+easier to work with seconds so here we're converting that to seconds.</p>
+<p>We then set the cube's X and Y rotation to the current time. These rotations
+are in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>. There are 2 pi radians
+in a circle so our cube should turn around once on each axis in about 6.28
+seconds.</p>
+<p>We then render the scene and request another animation frame to continue
+our loop.</p>
+<p>Outside the loop we call <code class="notranslate" translate="no">requestAnimationFrame</code> one time to start the loop.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals-with-animation.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/fundamentals-with-animation.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>It's a little better but it's still hard to see the 3d. What would help is to
+add some lighting so let's add a light. There are many kinds of lights in
+three.js which we'll go over in <a href="lights.html">a future article</a>. For now let's create a directional light.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const color = 0xFFFFFF;
+  const intensity = 1;
+  const light = new THREE.DirectionalLight(color, intensity);
+  light.position.set(-1, 2, 4);
+  scene.add(light);
+}
+</pre>
+<p>Directional lights have a position and a target. Both default to 0, 0, 0. In our
+case we're setting the light's position to -1, 2, 4 so it's slightly on the left,
+above, and behind our camera. The target is still 0, 0, 0 so it will shine
+toward the origin.</p>
+<p>We also need to change the material. The <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a> is not affected by
+lights. Let's change it to a <a href="/docs/#api/en/materials/MeshPhongMaterial"><code class="notranslate" translate="no">MeshPhongMaterial</code></a> which is affected by lights.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({color: 0x44aa88});  // greenish blue
++const material = new THREE.MeshPhongMaterial({color: 0x44aa88});  // greenish blue
+</pre>
+<p>Here is our new program structure</p>
+<div class="threejs_center"><img src="../resources/images/threejs-1cube-with-directionallight.svg" style="width: 500px;"></div>
+
+<p>And here it is working.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals-with-light.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/fundamentals-with-light.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>It should now be pretty clearly 3D.</p>
+<p>Just for the fun of it let's add 2 more cubes.</p>
+<p>We'll use the same geometry for each cube but make a different
+material so each cube can be a different color.</p>
+<p>First we'll make a function that creates a new material
+with the specified color. Then it creates a mesh using
+the specified geometry and adds it to the scene and
+sets its X position.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeInstance(geometry, color, x) {
+  const material = new THREE.MeshPhongMaterial({color});
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+
+  cube.position.x = x;
+
+  return cube;
+}
+</pre>
+<p>Then we'll call it 3 times with 3 different colors and X positions
+saving the <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> instances in an array.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cubes = [
+  makeInstance(geometry, 0x44aa88,  0),
+  makeInstance(geometry, 0x8844aa, -2),
+  makeInstance(geometry, 0xaa8844,  2),
+];
+</pre>
+<p>Finally we'll spin all 3 cubes in our render function. We
+compute a slightly different rotation for each one.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
+  time *= 0.001;  // convert time to seconds
+
+  cubes.forEach((cube, ndx) =&gt; {
+    const speed = 1 + ndx * .1;
+    const rot = time * speed;
+    cube.rotation.x = rot;
+    cube.rotation.y = rot;
+  });
+
+  ...
+</pre>
+<p>and here's that.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals-3-cubes.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/fundamentals-3-cubes.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>If you compare it to the top down diagram above you can see
+it matches our expectations. With cubes at X = -2 and X = +2
+they are partially outside our frustum. They are also
+somewhat exaggeratedly warped since the field of view
+across the canvas is so extreme.</p>
+<p>Our program now has this structure</p>
+<div class="threejs_center"><img src="../resources/images/threejs-3cubes-scene.svg" style="width: 610px;"></div>
+
+<p>As you can see we have 3 <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> objects each referencing the same <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a>.
+Each <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> references a unique <a href="/docs/#api/en/materials/MeshPhongMaterial"><code class="notranslate" translate="no">MeshPhongMaterial</code></a> so that each cube can have
+a different color.</p>
+<p>I hope this short intro helps to get things started. <a href="responsive.html">Next up we'll cover
+making our code responsive so it is adaptable to multiple situations</a>.</p>
+<div id="es6" class="threejs_bottombar">
+<h3>es6 modules, three.js, and folder structure</h3>
+<p>As of version r106 the preferred way to use three.js is via <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import">es6 modules</a>.</p>
+<p>
+es6 modules can be loaded via the <code class="notranslate" translate="no">import</code> keyword in a script
+or inline via a <code class="notranslate" translate="no">&lt;script type="module"&gt;</code> tag. Here's an example of
+both
+</p>
+<pre class="prettyprint">&lt;script type="module"&gt;
+import * as THREE from '../../build/three.module.js';
+
+...
+
+&lt;/script&gt;
+</pre>
+<p>
+Paths must be absolute or relative. Relative paths always start with <code class="notranslate" translate="no">./</code> or <code class="notranslate" translate="no">../</code>
+which is different than other tags like <code class="notranslate" translate="no">&lt;img&gt;</code> and <code class="notranslate" translate="no">&lt;a&gt;</code>.
+</p>
+<p>
+References to the same script will only be loaded once as long as their absolute paths
+are exactly the same. For three.js this means it's required that you put all the examples
+libraries in the correct folder structure
+</p>
+<pre class="dos">someFolder
+ |
+ ├-build
+ | |
+ | +-three.module.js
+ |
+ +-examples
+   |
+   +-jsm
+     |
+     +-controls
+     | |
+     | +-OrbitControls.js
+     | +-TrackballControls.js
+     | +-...
+     |
+     +-loaders
+     | |
+     | +-GLTFLoader.js
+     | +-...
+     |
+     ...
+</pre>
+<p>
+The reason this folder structure is required is because the scripts in the
+examples like <a href="https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/OrbitControls.js"><code class="notranslate" translate="no">OrbitControls.js</code></a>
+have hard coded relative paths like
+</p>
+<pre class="prettyprint">import * as THREE from '../../../build/three.module.js';
+</pre>
+<p>
+Using the same structure assures then when you import both three and one of the example
+libraries they'll both reference the same <code class="notranslate" translate="no">three.module.js</code> file.
+</p>
+<pre class="prettyprint">import * as THREE from './someFolder/build/three.module.js';
+import {OrbitControls} from './someFolder/examples/jsm/controls/OrbitControls.js';
+</pre>
+<p>This includes when using a CDN. Be sure your path to <code class="notranslate" translate="no">three.module.js</code> ends with
+<code class="notranslate" translate="no">/build/three.modules.js</code>. For example</p>
+<pre class="prettyprint">import * as THREE from 'https://unpkg.com/[email protected]<b>/build/three.module.js</b>';
+import {OrbitControls} from 'https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js';
+</pre>
+</div>
+
+<!-- needed in English only to prevent warning from outdated translations -->
+<p><a href="geometry.html"></a>
+<a href="Geometry"></a></p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 1925 - 0
manual/en/game.html

@@ -0,0 +1,1925 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Making a Game</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Making a Game">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Making a Game</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>Many people want to write games using three.js. This article
+will hopefully give you some ideas on how to start.</p>
+<p>At least at the time I'm writing this article it's probably going to be the
+longest article on this site. It's possible the code here is massively over
+engineered but as I wrote each new feature I'd run into a problem that needed a
+solution I'm used to from other games I've written. In other words each new
+solution seemed important so I'll try to show why. Of course the smaller your
+game the less you might need some of the solutions shown here but this is a
+pretty small game and yet with the complexities of 3D characters many things
+take more organization than they might with 2D characters.</p>
+<p>As an example if you're making PacMan in 2D, when PacMan turns a corner
+that happens instantly at 90 degrees. There is no in-between step. But
+in a 3D game often we need the character to rotate over several frames.
+That simple change can add a bunch of complexity and require different
+solutions.</p>
+<p>The majority of the code here will not really be three.js and
+that's important to note, <strong>three.js is not a game engine</strong>.
+Three.js is a 3D library. It provides a <a href="scenegraph.html">scene graph</a>
+and features for displaying 3D objects added to that scene graph
+but it does not provide all the other things needed to make a game.
+No collisions, no physics, no input systems, no path finding, etc, etc...
+So, we'll have to provide those things ourselves.</p>
+<p>I ended up writing quite a bit of code to make this simple <em>unfinished</em> 
+game like thing and again, it's certainly possible I over engineered and there
+are simpler solutions but I feel like I actually didn't write
+enough code and hopefully I can explain what I think is missing.</p>
+<p>Many of the ideas here are heavily influenced by <a href="https://unity.com">Unity</a>.
+If you're not familiar with Unity that probably does not matter.
+I only bring it up as 10s of 1000s of games have shipped using
+these ideas.</p>
+<p>Let's start with the three.js parts. We need to load models for our game.</p>
+<p>At <a href="https://opengameart.org">opengameart.org</a> I found this <a href="https://opengameart.org/content/lowpoly-animated-knight">animated knight
+model</a> by <a href="https://opengameart.org/users/quaternius">quaternius</a></p>
+<div class="threejs_center"><img src="../resources/images/knight.jpg" style="width: 375px;"></div>
+
+<p><a href="https://opengameart.org/users/quaternius">quaternius</a> also made <a href="https://opengameart.org/content/lowpoly-animated-farm-animal-pack">these animated animals</a>.</p>
+<div class="threejs_center"><img src="../resources/images/animals.jpg" style="width: 606px;"></div>
+
+<p>These seem like good models to start with so the first thing we need to
+do is load them.</p>
+<p>We covered <a href="load-gltf.html">loading glTF files before</a>. 
+The difference this time is we need to load multiple models and
+we can't start the game until all the models are loaded.</p>
+<p>Fortunately three.js provides the <a href="/docs/#api/en/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a> just for this purpose.
+We create a <a href="/docs/#api/en/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a> and pass it to the other loaders. The
+<a href="/docs/#api/en/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a> provides both <a href="/docs/#api/en/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a> and 
+<a href="/docs/#api/en/loaders/managers/LoadingManager#onLoad"><code class="notranslate" translate="no">onLoad</code></a> properties we can attach callbacks to.
+The <a href="/docs/#api/en/loaders/managers/LoadingManager#onLoad"><code class="notranslate" translate="no">onLoad</code></a> callback will be called when
+all files have been loaded. The <a href="/docs/#api/en/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a> callback
+as called after each individual file arrives to give as a chance to show
+loading progress.</p>
+<p>Starting with the code from <a href="load-gltf.html">loading a glTF file</a> I removed all
+the code related to framing the scene and added this code to load all models.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const manager = new THREE.LoadingManager();
+manager.onLoad = init;
+const models = {
+  pig:    { url: 'resources/models/animals/Pig.gltf' },
+  cow:    { url: 'resources/models/animals/Cow.gltf' },
+  llama:  { url: 'resources/models/animals/Llama.gltf' },
+  pug:    { url: 'resources/models/animals/Pug.gltf' },
+  sheep:  { url: 'resources/models/animals/Sheep.gltf' },
+  zebra:  { url: 'resources/models/animals/Zebra.gltf' },
+  horse:  { url: 'resources/models/animals/Horse.gltf' },
+  knight: { url: 'resources/models/knight/KnightCharacter.gltf' },
+};
+{
+  const gltfLoader = new GLTFLoader(manager);
+  for (const model of Object.values(models)) {
+    gltfLoader.load(model.url, (gltf) =&gt; {
+      model.gltf = gltf;
+    });
+  }
+}
+
+function init() {
+  // TBD
+}
+</pre>
+<p>This code will load all the models above and the <a href="/docs/#api/en/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a> will call
+<code class="notranslate" translate="no">init</code> when done. We'll use the <code class="notranslate" translate="no">models</code> object later to let us access the
+loaded models so the <a href="/docs/#examples/loaders/GLTFLoader"><code class="notranslate" translate="no">GLTFLoader</code></a> callback for each individual model attaches
+the loaded data to that model's info.</p>
+<p>All the models with all their animation are currently about 6.6meg. That's a
+pretty big download. Assuming your server supports compression (the server this
+site runs on does) it's able to compress them to around 1.4meg. That's
+definitely better than 6.6meg bit it's still not a tiny amount of data. It would
+probably be good if we added a progress bar so the user has some idea how much
+longer they have to wait.</p>
+<p>So, let's add an <a href="/docs/#api/en/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a> callback. It will be
+called with 3 arguments, the <code class="notranslate" translate="no">url</code> of the last loaded object and then the number
+of items loaded so far as well as the total number of items.</p>
+<p>Let's setup some HTML for a loading bar</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
+  &lt;canvas id="c"&gt;&lt;/canvas&gt;
++  &lt;div id="loading"&gt;
++    &lt;div&gt;
++      &lt;div&gt;...loading...&lt;/div&gt;
++      &lt;div class="progress"&gt;&lt;div id="progressbar"&gt;&lt;/div&gt;&lt;/div&gt;
++    &lt;/div&gt;
++  &lt;/div&gt;
+&lt;/body&gt;
+</pre>
+<p>We'll look up the <code class="notranslate" translate="no">#progressbar</code> div and we can set the width from 0% to 100% 
+to show our progress. All we need to do is set that in our callback.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const manager = new THREE.LoadingManager();
+manager.onLoad = init;
+
++const progressbarElem = document.querySelector('#progressbar');
++manager.onProgress = (url, itemsLoaded, itemsTotal) =&gt; {
++  progressbarElem.style.width = `${itemsLoaded / itemsTotal * 100 | 0}%`;
++};
+</pre>
+<p>We already setup <code class="notranslate" translate="no">init</code> to be called when all the models are loaded so
+we can turn off the progress bar by hiding the <code class="notranslate" translate="no">#loading</code> element.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
++  // hide the loading bar
++  const loadingElem = document.querySelector('#loading');
++  loadingElem.style.display = 'none';
+}
+</pre>
+<p>Here's a bunch of CSS for styling the bar. The CSS makes the <code class="notranslate" translate="no">#loading</code> <code class="notranslate" translate="no">&lt;div&gt;</code>
+the full size of the page and centers its children. The CSS makes a <code class="notranslate" translate="no">.progress</code>
+area to contain the progress bar. The CSS also gives the progress bar
+a CSS animation of diagonal stripes.</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#loading {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  font-size: xx-large;
+  font-family: sans-serif;
+}
+#loading&gt;div&gt;div {
+  padding: 2px;
+}
+.progress {
+  width: 50vw;
+  border: 1px solid black;
+}
+#progressbar {
+  width: 0;
+  transition: width ease-out .5s;
+  height: 1em;
+  background-color: #888;
+  background-image: linear-gradient(
+    -45deg, 
+    rgba(255, 255, 255, .5) 25%, 
+    transparent 25%, 
+    transparent 50%, 
+    rgba(255, 255, 255, .5) 50%, 
+    rgba(255, 255, 255, .5) 75%, 
+    transparent 75%, 
+    transparent
+  );
+  background-size: 50px 50px;
+  animation: progressanim 2s linear infinite;
+}
+
+@keyframes progressanim {
+  0% {
+    background-position: 50px 50px;
+  }
+  100% {
+    background-position: 0 0;
+  }
+}
+</pre>
+<p>Now that we have a progress bar let's deal with the models. These models
+have animations and we want to be able to access those animations.
+Animations are stored in an array by default be we'd like to be able to
+easily access them by name so let's setup an <code class="notranslate" translate="no">animations</code> property for
+each model to do that. Note of course this means animations must have unique names.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function prepModelsAndAnimations() {
++  Object.values(models).forEach(model =&gt; {
++    const animsByName = {};
++    model.gltf.animations.forEach((clip) =&gt; {
++      animsByName[clip.name] = clip;
++    });
++    model.animations = animsByName;
++  });
++}
+
+function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
++  prepModelsAndAnimations();
+}
+</pre>
+<p>Let's display the animated models.</p>
+<p>Unlike the <a href="load-gltf.html">previous example of loading a glTF file</a>
+This time we probably want to be able to display more than one instance
+of each model. To do this, instead of adding
+the loaded gltf scene directly like we did in <a href="load-gltf.html">the article on loading a glTF</a>,
+we instead want to clone the scene and in particular we want to clone
+it for skinned animated characters. Fortunately there's a utility function,
+<code class="notranslate" translate="no">SkeletonUtils.clone</code> we can use to do this. So, first we need to include
+the utils.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
+import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+import {GLTFLoader} from '/examples/jsm/loaders/GLTFLoader.js';
++import * as SkeletonUtils from '/examples/jsm/utils/SkeletonUtils.js';
+</pre>
+<p>Then we can clone the models we just loaded</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
+  prepModelsAndAnimations();
+
++  Object.values(models).forEach((model, ndx) =&gt; {
++    const clonedScene = SkeletonUtils.clone(model.gltf.scene);
++    const root = new THREE.Object3D();
++    root.add(clonedScene);
++    scene.add(root);
++    root.position.x = (ndx - 3) * 3;
++  });
+}
+</pre>
+<p>Above, for each model, we clone the <code class="notranslate" translate="no">gltf.scene</code> we loaded and we parent that
+to a new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>. We need to parent it to another object because when
+we play animations the animation will apply animated positions to the nodes
+in the loaded scene which means we won't have control over those positions.</p>
+<p>To play the animations each model we clone needs an <a href="/docs/#api/en/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>.
+An <a href="/docs/#api/en/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a> contains 1 or more <a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>s. An
+<a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a> references an <a href="/docs/#api/en/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a>. <a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>s
+have all kinds of settings for playing then chaining to another
+action or cross fading between actions. Let's just get the first 
+<a href="/docs/#api/en/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a> and create an action for it. The default is for
+an action to play its clip in a loop forever.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const mixers = [];
+
+function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
+  prepModelsAndAnimations();
+
+  Object.values(models).forEach((model, ndx) =&gt; {
+    const clonedScene = SkeletonUtils.clone(model.gltf.scene);
+    const root = new THREE.Object3D();
+    root.add(clonedScene);
+    scene.add(root);
+    root.position.x = (ndx - 3) * 3;
+
++    const mixer = new THREE.AnimationMixer(clonedScene);
++    const firstClip = Object.values(model.animations)[0];
++    const action = mixer.clipAction(firstClip);
++    action.play();
++    mixers.push(mixer);
+  });
+}
+</pre>
+<p>We called <a href="/docs/#api/en/animation/AnimationAction#play"><code class="notranslate" translate="no">play</code></a> to start the action and stored
+off all the <code class="notranslate" translate="no">AnimationMixers</code> in an array called <code class="notranslate" translate="no">mixers</code>. Finally
+we need to update each <a href="/docs/#api/en/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a> in our render loop by computing
+the time since the last frame and passing that to <a href="/docs/#api/en/animation/AnimationMixer.update"><code class="notranslate" translate="no">AnimationMixer.update</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let then = 0;
+function render(now) {
++  now *= 0.001;  // convert to seconds
++  const deltaTime = now - then;
++  then = now;
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+  }
+
++  for (const mixer of mixers) {
++    mixer.update(deltaTime);
++  }
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>And with that we should get each model loaded and playing its first animation.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-load-models.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/game-load-models.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Let's make it so we can check all of the animations.
+We'll add all of the clips as actions and then enable just one at 
+a time.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const mixers = [];
++const mixerInfos = [];
+
+function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
+  prepModelsAndAnimations();
+
+  Object.values(models).forEach((model, ndx) =&gt; {
+    const clonedScene = SkeletonUtils.clone(model.gltf.scene);
+    const root = new THREE.Object3D();
+    root.add(clonedScene);
+    scene.add(root);
+    root.position.x = (ndx - 3) * 3;
+
+    const mixer = new THREE.AnimationMixer(clonedScene);
+-    const firstClip = Object.values(model.animations)[0];
+-    const action = mixer.clipAction(firstClip);
+-    action.play();
+-    mixers.push(mixer);
++    const actions = Object.values(model.animations).map((clip) =&gt; {
++      return mixer.clipAction(clip);
++    });
++    const mixerInfo = {
++      mixer,
++      actions,
++      actionNdx: -1,
++    };
++    mixerInfos.push(mixerInfo);
++    playNextAction(mixerInfo);
+  });
+}
+
++function playNextAction(mixerInfo) {
++  const {actions, actionNdx} = mixerInfo;
++  const nextActionNdx = (actionNdx + 1) % actions.length;
++  mixerInfo.actionNdx = nextActionNdx;
++  actions.forEach((action, ndx) =&gt; {
++    const enabled = ndx === nextActionNdx;
++    action.enabled = enabled;
++    if (enabled) {
++      action.play();
++    }
++  });
++}
+</pre>
+<p>The code above makes an array of <a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>s,
+one for each <a href="/docs/#api/en/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a>. It makes an array of objects, <code class="notranslate" translate="no">mixerInfos</code>,
+with references to the <a href="/docs/#api/en/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a> and all the <a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>s 
+for each model. It then calls <code class="notranslate" translate="no">playNextAction</code> which sets <code class="notranslate" translate="no">enabled</code> on
+all but one action for that mixer.</p>
+<p>We need to update the render loop for the new array</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-for (const mixer of mixers) {
++for (const {mixer} of mixerInfos) {
+  mixer.update(deltaTime);
+}
+</pre>
+<p>Let's make it so pressing a key 1 to 8 will play the next animation
+for each model</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">window.addEventListener('keydown', (e) =&gt; {
+  const mixerInfo = mixerInfos[e.keyCode - 49];
+  if (!mixerInfo) {
+    return;
+  }
+  playNextAction(mixerInfo);
+});
+</pre>
+<p>Now you should be able to click on the example and then press keys 1 through 8
+to cycle each of the models through their available animations.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-check-animations.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/game-check-animations.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>So that is arguably the sum-total of the three.js portion of this
+article. We covered loading multiple files, cloning skinned models,
+and playing animations on them. In a real game you'd have to do a
+ton more manipulation of <a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a> objects.</p>
+<p>Let's start making a game infrastructure</p>
+<p>A common pattern for making a modern game is to use an
+<a href="https://www.google.com/search?q=entity+component+system">Entity Component System</a>.
+In an Entity Component System an object in a game is called an <em>entity</em> that consists
+of a bunch of <em>components</em>. You build up entities by deciding which components to
+attach to them. So, let's make an Entity Component System.</p>
+<p>We'll call our entities <code class="notranslate" translate="no">GameObject</code>. It's effectively just a collection
+of components and a three.js <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function removeArrayElement(array, element) {
+  const ndx = array.indexOf(element);
+  if (ndx &gt;= 0) {
+    array.splice(ndx, 1);
+  }
+}
+
+class GameObject {
+  constructor(parent, name) {
+    this.name = name;
+    this.components = [];
+    this.transform = new THREE.Object3D();
+    parent.add(this.transform);
+  }
+  addComponent(ComponentType, ...args) {
+    const component = new ComponentType(this, ...args);
+    this.components.push(component);
+    return component;
+  }
+  removeComponent(component) {
+    removeArrayElement(this.components, component);
+  }
+  getComponent(ComponentType) {
+    return this.components.find(c =&gt; c instanceof ComponentType);
+  }
+  update() {
+    for (const component of this.components) {
+      component.update();
+    }
+  }
+}
+</pre>
+<p>Calling <code class="notranslate" translate="no">GameObject.update</code> calls <code class="notranslate" translate="no">update</code> on all the components.</p>
+<p>I included a name only to help in debugging so if I look at a <code class="notranslate" translate="no">GameObject</code>
+in the debugger I can see a name to help identify it.</p>
+<p>Some things that might seem a little strange:</p>
+<p><code class="notranslate" translate="no">GameObject.addComponent</code> is used to create components. Whether or not
+this a good idea or a bad idea I'm not sure. My thinking was it makes
+no sense for a component to exist outside of a gameobject so I thought
+it might be good if creating a component automatically added that component
+to the gameobject and passed the gameobject to the component's constructor.
+In other words to add a component you do this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gameObject = new GameObject(scene, 'foo');
+gameObject.addComponent(TypeOfComponent);
+</pre>
+<p>If I didn't do it this way you'd instead do something like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gameObject = new GameObject(scene, 'foo');
+const component = new TypeOfComponent(gameObject);
+gameObject.addComponent(component);
+</pre>
+<p>Is it better that the first way is shorter and more automated or is it worse
+because it looks out of the ordinary? I don't know.</p>
+<p><code class="notranslate" translate="no">GameObject.getComponent</code> looks up components by type. That has
+the implication that you can not have 2 components of the same
+type on a single game object or at least if you do you can only
+look up the first one without adding some other API.</p>
+<p>It's common for one component to look up another and when looking them up they
+have to match by type otherwise you might get the wrong one. We could instead
+give each component a name and you could look them up by name. That would be
+more flexible in that you could have more than one component of the same type but it
+would also be more tedious. Again, I'm not sure which is better.</p>
+<p>On to the components themselves. Here is their base class.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// Base for all components
+class Component {
+  constructor(gameObject) {
+    this.gameObject = gameObject;
+  }
+  update() {
+  }
+}
+</pre>
+<p>Do components need a base class? JavaScript is not like most strictly 
+typed languages so effectively we could have no base class and just 
+leave it up to each component to do whatever it wants in its constructor
+knowing that the first argument is always the component's gameobject.
+If it doesn't care about gameobject it wouldn't store it. I kind of feel like this
+common base is good though. It means if you have a reference to a
+component you know you can find its parent gameobject always and from its
+parent you can easily look up other components as well as look at its
+transform.</p>
+<p>To manage the gameobjects we probably need some kind of gameobject manager. You
+might think we could just keep an array of gameobjects but in a real game the
+components of a gameobject might add and remove other gameobjects at runtime.
+For example a gun gameobject might add a bullet gameobject every time the gun
+fires. A monster gameobject might remove itself if it has been killed. We then
+would have an issue that we might have code like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">for (const gameObject of globalArrayOfGameObjects) {
+  gameObject.update();
+}
+</pre>
+<p>The loop above would fail or do un-expected things if
+gameobjects are added or removed from <code class="notranslate" translate="no">globalArrayOfGameObjects</code>
+in the middle of the loop in some component's <code class="notranslate" translate="no">update</code> function.</p>
+<p>To try to prevent that problem we need something a little safer.
+Here's one attempt.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class SafeArray {
+  constructor() {
+    this.array = [];
+    this.addQueue = [];
+    this.removeQueue = new Set();
+  }
+  get isEmpty() {
+    return this.addQueue.length + this.array.length &gt; 0;
+  }
+  add(element) {
+    this.addQueue.push(element);
+  }
+  remove(element) {
+    this.removeQueue.add(element);
+  }
+  forEach(fn) {
+    this._addQueued();
+    this._removeQueued();
+    for (const element of this.array) {
+      if (this.removeQueue.has(element)) {
+        continue;
+      }
+      fn(element);
+    }
+    this._removeQueued();
+  }
+  _addQueued() {
+    if (this.addQueue.length) {
+      this.array.splice(this.array.length, 0, ...this.addQueue);
+      this.addQueue = [];
+    }
+  }
+  _removeQueued() {
+    if (this.removeQueue.size) {
+      this.array = this.array.filter(element =&gt; !this.removeQueue.has(element));
+      this.removeQueue.clear();
+    }
+  }
+}
+</pre>
+<p>The class above lets you add or remove elements from the <code class="notranslate" translate="no">SafeArray</code>
+but won't mess with the array itself while it's being iterated over. Instead
+new elements get added to <code class="notranslate" translate="no">addQueue</code> and removed elements to the <code class="notranslate" translate="no">removeQueue</code>
+and then added or removed outside of the loop.</p>
+<p>Using that here is our class to manage gameobjects.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class GameObjectManager {
+  constructor() {
+    this.gameObjects = new SafeArray();
+  }
+  createGameObject(parent, name) {
+    const gameObject = new GameObject(parent, name);
+    this.gameObjects.add(gameObject);
+    return gameObject;
+  }
+  removeGameObject(gameObject) {
+    this.gameObjects.remove(gameObject);
+  }
+  update() {
+    this.gameObjects.forEach(gameObject =&gt; gameObject.update());
+  }
+}
+</pre>
+<p>With all that now let's make our first component. This component
+will just manage a skinned three.js object like the ones we just created.
+To keep it simple it will just have one method, <code class="notranslate" translate="no">setAnimation</code> that
+takes the name of the animation to play and plays it.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class SkinInstance extends Component {
+  constructor(gameObject, model) {
+    super(gameObject);
+    this.model = model;
+    this.animRoot = SkeletonUtils.clone(this.model.gltf.scene);
+    this.mixer = new THREE.AnimationMixer(this.animRoot);
+    gameObject.transform.add(this.animRoot);
+    this.actions = {};
+  }
+  setAnimation(animName) {
+    const clip = this.model.animations[animName];
+    // turn off all current actions
+    for (const action of Object.values(this.actions)) {
+      action.enabled = false;
+    }
+    // get or create existing action for clip
+    const action = this.mixer.clipAction(clip);
+    action.enabled = true;
+    action.reset();
+    action.play();
+    this.actions[animName] = action;
+  }
+  update() {
+    this.mixer.update(globals.deltaTime);
+  }
+}
+</pre>
+<p>You can see it's basically the code we had before that clones the scene we loaded,
+then sets up an <a href="/docs/#api/en/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>. <code class="notranslate" translate="no">setAnimation</code> adds a <a href="/docs/#api/en/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a> for a 
+particular <a href="/docs/#api/en/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a> if one does not already exist and disables all
+existing actions.</p>
+<p>The code references <code class="notranslate" translate="no">globals.deltaTime</code>. Let's make a globals object</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const globals = {
+  time: 0,
+  deltaTime: 0,
+};
+</pre>
+<p>And update it in the render loop</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">let then = 0;
+function render(now) {
+  // convert to seconds
+  globals.time = now * 0.001;
+  // make sure delta time isn't too big.
+  globals.deltaTime = Math.min(globals.time - then, 1 / 20);
+  then = globals.time;
+</pre>
+<p>The check above for making sure <code class="notranslate" translate="no">deltaTime</code> is not more than 1/20th
+of a second is because otherwise we'd get a huge value for <code class="notranslate" translate="no">deltaTime</code>
+if we hide the tab. We might hide it for seconds or minutes and then
+when our tab was brought to the front <code class="notranslate" translate="no">deltaTime</code> would be huge
+and might teleport characters across our game world if we had code like</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">position += velocity * deltaTime;
+</pre>
+<p>By limiting the maximum <code class="notranslate" translate="no">deltaTime</code> that issue is prevented.</p>
+<p>Now let's make a component for the player.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
+  constructor(gameObject) {
+    super(gameObject);
+    const model = models.knight;
+    this.skinInstance = gameObject.addComponent(SkinInstance, model);
+    this.skinInstance.setAnimation('Run');
+  }
+}
+</pre>
+<p>The player calls <code class="notranslate" translate="no">setAnimation</code> with <code class="notranslate" translate="no">'Run'</code>. To know which animations
+are available I modified our previous example to print out the names of 
+the animations</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function prepModelsAndAnimations() {
+  Object.values(models).forEach(model =&gt; {
++    console.log('-------&gt;:', model.url);
+    const animsByName = {};
+    model.gltf.animations.forEach((clip) =&gt; {
+      animsByName[clip.name] = clip;
++      console.log('  ', clip.name);
+    });
+    model.animations = animsByName;
+  });
+}
+</pre>
+<p>And running it got this list in <a href="https://developers.google.com/web/tools/chrome-devtools/console/javascript">the JavaScript console</a>.</p>
+<pre class="prettyprint showlinemods notranslate notranslate" translate="no"> -------&gt;:  resources/models/animals/Pig.gltf
+    Idle
+    Death
+    WalkSlow
+    Jump
+    Walk
+ -------&gt;:  resources/models/animals/Cow.gltf
+    Walk
+    Jump
+    WalkSlow
+    Death
+    Idle
+ -------&gt;:  resources/models/animals/Llama.gltf
+    Jump
+    Idle
+    Walk
+    Death
+    WalkSlow
+ -------&gt;:  resources/models/animals/Pug.gltf
+    Jump
+    Walk
+    Idle
+    WalkSlow
+    Death
+ -------&gt;:  resources/models/animals/Sheep.gltf
+    WalkSlow
+    Death
+    Jump
+    Walk
+    Idle
+ -------&gt;:  resources/models/animals/Zebra.gltf
+    Jump
+    Walk
+    Death
+    WalkSlow
+    Idle
+ -------&gt;:  resources/models/animals/Horse.gltf
+    Jump
+    WalkSlow
+    Death
+    Walk
+    Idle
+ -------&gt;:  resources/models/knight/KnightCharacter.gltf
+    Run_swordRight
+    Run
+    Idle_swordLeft
+    Roll_sword
+    Idle
+    Run_swordAttack
+</pre><p>Fortunately the names of the animations for all the animals match
+which will come in handy later. For now we only care the that the 
+player has an animation called <code class="notranslate" translate="no">Run</code>.</p>
+<p>Let's use these components. Here's the updated init function.
+All it does is create a <code class="notranslate" translate="no">GameObject</code> and add a <code class="notranslate" translate="no">Player</code> component to it.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const globals = {
+  time: 0,
+  deltaTime: 0,
+};
++const gameObjectManager = new GameObjectManager();
+
+function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
+  prepModelsAndAnimations();
+
++  {
++    const gameObject = gameObjectManager.createGameObject(scene, 'player');
++    gameObject.addComponent(Player);
++  }
+}
+</pre>
+<p>And we need to call <code class="notranslate" translate="no">gameObjectManager.update</code> in our render loop</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">let then = 0;
+function render(now) {
+  // convert to seconds
+  globals.time = now * 0.001;
+  // make sure delta time isn't too big.
+  globals.deltaTime = Math.min(globals.time - then, 1 / 20);
+  then = globals.time;
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+  }
+
+-  for (const {mixer} of mixerInfos) {
+-    mixer.update(deltaTime);
+-  }
++  gameObjectManager.update();
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>and if we run that we get a single player.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-just-player.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/game-just-player.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>That was a lot of code just for an entity component system but
+it's infrastructure that most games need.</p>
+<p>Let's add an input system. Rather than read keys directly we'll
+make a class that other parts of the code can check <code class="notranslate" translate="no">left</code> or <code class="notranslate" translate="no">right</code>.
+That way we can assign multiple ways to input <code class="notranslate" translate="no">left</code> or <code class="notranslate" translate="no">right</code> etc..
+We'll start with just keys</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// Keeps the state of keys/buttons
+//
+// You can check
+//
+//   inputManager.keys.left.down
+//
+// to see if the left key is currently held down
+// and you can check
+//
+//   inputManager.keys.left.justPressed
+//
+// To see if the left key was pressed this frame
+//
+// Keys are 'left', 'right', 'a', 'b', 'up', 'down'
+class InputManager {
+  constructor() {
+    this.keys = {};
+    const keyMap = new Map();
+
+    const setKey = (keyName, pressed) =&gt; {
+      const keyState = this.keys[keyName];
+      keyState.justPressed = pressed &amp;&amp; !keyState.down;
+      keyState.down = pressed;
+    };
+
+    const addKey = (keyCode, name) =&gt; {
+      this.keys[name] = { down: false, justPressed: false };
+      keyMap.set(keyCode, name);
+    };
+
+    const setKeyFromKeyCode = (keyCode, pressed) =&gt; {
+      const keyName = keyMap.get(keyCode);
+      if (!keyName) {
+        return;
+      }
+      setKey(keyName, pressed);
+    };
+
+    addKey(37, 'left');
+    addKey(39, 'right');
+    addKey(38, 'up');
+    addKey(40, 'down');
+    addKey(90, 'a');
+    addKey(88, 'b');
+
+    window.addEventListener('keydown', (e) =&gt; {
+      setKeyFromKeyCode(e.keyCode, true);
+    });
+    window.addEventListener('keyup', (e) =&gt; {
+      setKeyFromKeyCode(e.keyCode, false);
+    });
+  }
+  update() {
+    for (const keyState of Object.values(this.keys)) {
+      if (keyState.justPressed) {
+        keyState.justPressed = false;
+      }
+    }
+  }
+}
+</pre>
+<p>The code above tracks whether keys are up or down and you can check
+if a key is currently pressed by checking for example
+<code class="notranslate" translate="no">inputManager.keys.left.down</code>. It also has a <code class="notranslate" translate="no">justPressed</code> property
+for each key so that you can check the user just pressed the key.
+For example a jump key you don't want to know if the button is being 
+held down, you want to know did the user press it now.</p>
+<p>Let's create an instance of <code class="notranslate" translate="no">InputManager</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const globals = {
+  time: 0,
+  deltaTime: 0,
+};
+const gameObjectManager = new GameObjectManager();
++const inputManager = new InputManager();
+</pre>
+<p>and update it in our render loop</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(now) {
+
+  ...
+
+  gameObjectManager.update();
++  inputManager.update();
+
+  ...
+}
+</pre>
+<p>It needs to be called after <code class="notranslate" translate="no">gameObjectManager.update</code> otherwise
+<code class="notranslate" translate="no">justPressed</code> would never be true inside a component's <code class="notranslate" translate="no">update</code> function.</p>
+<p>Let's use it in the <code class="notranslate" translate="no">Player</code> component</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const kForward = new THREE.Vector3(0, 0, 1);
+const globals = {
+  time: 0,
+  deltaTime: 0,
++  moveSpeed: 16,
+};
+
+class Player extends Component {
+  constructor(gameObject) {
+    super(gameObject);
+    const model = models.knight;
+    this.skinInstance = gameObject.addComponent(SkinInstance, model);
+    this.skinInstance.setAnimation('Run');
++    this.turnSpeed = globals.moveSpeed / 4;
+  }
++  update() {
++    const {deltaTime, moveSpeed} = globals;
++    const {transform} = this.gameObject;
++    const delta = (inputManager.keys.left.down  ?  1 : 0) +
++                  (inputManager.keys.right.down ? -1 : 0);
++    transform.rotation.y += this.turnSpeed * delta * deltaTime;
++    transform.translateOnAxis(kForward, moveSpeed * deltaTime);
++  }
+}
+</pre>
+<p>The code above uses <a href="/docs/#api/en/core/Object3D.transformOnAxis"><code class="notranslate" translate="no">Object3D.transformOnAxis</code></a> to move the player
+forward. <a href="/docs/#api/en/core/Object3D.transformOnAxis"><code class="notranslate" translate="no">Object3D.transformOnAxis</code></a> works in local space so it only
+works if the object in question is at the root of the scene, not if it's
+parented to something else <a class="footnote" href="#parented" id="parented-backref">1</a></p>
+<p>We also added a global <code class="notranslate" translate="no">moveSpeed</code> and based a <code class="notranslate" translate="no">turnSpeed</code> on the move speed.
+The turn speed is based on the move speed to try to make sure a character
+can turn sharply enough to meet its target. If <code class="notranslate" translate="no">turnSpeed</code> so too small
+a character will turn around and around circling its target but never
+hitting it. I didn't bother to do the math to calculate the required
+turn speed for a given move speed. I just guessed.</p>
+<p>The code so far would work but if the player runs off the screen there's no
+way to find out where they are. Let's make it so if they are offscreen
+for more than a certain time they get teleported back to the origin.
+We can do that by using the three.js <a href="/docs/#api/en/math/Frustum"><code class="notranslate" translate="no">Frustum</code></a> class to check if a point
+is inside the camera's view frustum.</p>
+<p>We need to build a frustum from the camera. We could do this in the Player
+component but other objects might want to use this too so let's add another
+gameobject with a component to manage a frustum.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class CameraInfo extends Component {
+  constructor(gameObject) {
+    super(gameObject);
+    this.projScreenMatrix = new THREE.Matrix4();
+    this.frustum = new THREE.Frustum();
+  }
+  update() {
+    const {camera} = globals;
+    this.projScreenMatrix.multiplyMatrices(
+        camera.projectionMatrix,
+        camera.matrixWorldInverse);
+    this.frustum.setFromProjectionMatrix(this.projScreenMatrix);
+  }
+}
+</pre>
+<p>Then let's setup another gameobject at init time.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
+  prepModelsAndAnimations();
+
++  {
++    const gameObject = gameObjectManager.createGameObject(camera, 'camera');
++    globals.cameraInfo = gameObject.addComponent(CameraInfo);
++  }
+
+  {
+    const gameObject = gameObjectManager.createGameObject(scene, 'player');
+    gameObject.addComponent(Player);
+  }
+}
+</pre>
+<p>and now we can use it in the <code class="notranslate" translate="no">Player</code> component.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
+  constructor(gameObject) {
+    super(gameObject);
+    const model = models.knight;
+    this.skinInstance = gameObject.addComponent(SkinInstance, model);
+    this.skinInstance.setAnimation('Run');
+    this.turnSpeed = globals.moveSpeed / 4;
++    this.offscreenTimer = 0;
++    this.maxTimeOffScreen = 3;
+  }
+  update() {
+-    const {deltaTime, moveSpeed} = globals;
++    const {deltaTime, moveSpeed, cameraInfo} = globals;
+    const {transform} = this.gameObject;
+    const delta = (inputManager.keys.left.down  ?  1 : 0) +
+                  (inputManager.keys.right.down ? -1 : 0);
+    transform.rotation.y += this.turnSpeed * delta * deltaTime;
+    transform.translateOnAxis(kForward, moveSpeed * deltaTime);
+
++    const {frustum} = cameraInfo;
++    if (frustum.containsPoint(transform.position)) {
++      this.offscreenTimer = 0;
++    } else {
++      this.offscreenTimer += deltaTime;
++      if (this.offscreenTimer &gt;= this.maxTimeOffScreen) {
++        transform.position.set(0, 0, 0);
++      }
++    }
+  }
+}
+</pre>
+<p>One more thing before we try it out, let's add touchscreen support
+for mobile. First let's add some HTML to touch</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
+  &lt;canvas id="c"&gt;&lt;/canvas&gt;
++  &lt;div id="ui"&gt;
++    &lt;div id="left"&gt;&lt;img src="../resources/images/left.svg"&gt;&lt;/div&gt;
++    &lt;div style="flex: 0 0 40px;"&gt;&lt;/div&gt;
++    &lt;div id="right"&gt;&lt;img src="../resources/images/right.svg"&gt;&lt;/div&gt;
++  &lt;/div&gt;
+  &lt;div id="loading"&gt;
+    &lt;div&gt;
+      &lt;div&gt;...loading...&lt;/div&gt;
+      &lt;div class="progress"&gt;&lt;div id="progressbar"&gt;&lt;/div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+  &lt;/div&gt;
+&lt;/body&gt;
+</pre>
+<p>and some CSS to style it</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#ui {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-items: center;
+  align-content: stretch;
+}
+#ui&gt;div {
+  display: flex;
+  align-items: flex-end;
+  flex: 1 1 auto;
+}
+.bright {
+  filter: brightness(2);
+}
+#left {
+  justify-content: flex-end;
+}
+#right {
+  justify-content: flex-start;
+}
+#ui img {
+  padding: 10px;
+  width: 80px;
+  height: 80px;
+  display: block;
+}
+</pre>
+<p>The idea here is there is one div, <code class="notranslate" translate="no">#ui</code>, that 
+covers the entire page. Inside will be 2 divs, <code class="notranslate" translate="no">#left</code> and <code class="notranslate" translate="no">#right</code>
+both of which are almost half the page wide and the entire screen tall.
+In between there is a 40px separator. If the user slides their finger
+over the left or right side then we need up update <code class="notranslate" translate="no">keys.left</code> and <code class="notranslate" translate="no">keys.right</code>
+in the <code class="notranslate" translate="no">InputManager</code>. This makes the entire screen sensitive to being touched
+which seemed better than just small arrows.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class InputManager {
+  constructor() {
+    this.keys = {};
+    const keyMap = new Map();
+
+    const setKey = (keyName, pressed) =&gt; {
+      const keyState = this.keys[keyName];
+      keyState.justPressed = pressed &amp;&amp; !keyState.down;
+      keyState.down = pressed;
+    };
+
+    const addKey = (keyCode, name) =&gt; {
+      this.keys[name] = { down: false, justPressed: false };
+      keyMap.set(keyCode, name);
+    };
+
+    const setKeyFromKeyCode = (keyCode, pressed) =&gt; {
+      const keyName = keyMap.get(keyCode);
+      if (!keyName) {
+        return;
+      }
+      setKey(keyName, pressed);
+    };
+
+    addKey(37, 'left');
+    addKey(39, 'right');
+    addKey(38, 'up');
+    addKey(40, 'down');
+    addKey(90, 'a');
+    addKey(88, 'b');
+
+    window.addEventListener('keydown', (e) =&gt; {
+      setKeyFromKeyCode(e.keyCode, true);
+    });
+    window.addEventListener('keyup', (e) =&gt; {
+      setKeyFromKeyCode(e.keyCode, false);
+    });
+
++    const sides = [
++      { elem: document.querySelector('#left'),  key: 'left'  },
++      { elem: document.querySelector('#right'), key: 'right' },
++    ];
++
++    const clearKeys = () =&gt; {
++      for (const {key} of sides) {
++          setKey(key, false);
++      }
++    };
++
++    const handleMouseMove = (e) =&gt; {
++      e.preventDefault();
++      // this is needed because we call preventDefault();
++      // we also gave the canvas a tabindex so it can
++      // become the focus
++      canvas.focus();
++      window.addEventListener('pointermove', handleMouseMove);
++      window.addEventListener('pointerup', handleMouseUp);
++
++      for (const {elem, key} of sides) {
++        let pressed = false;
++        const rect = elem.getBoundingClientRect();
++        const x = e.clientX;
++        const y = e.clientY;
++        const inRect = x &gt;= rect.left &amp;&amp; x &lt; rect.right &amp;&amp;
++                       y &gt;= rect.top &amp;&amp; y &lt; rect.bottom;
++        if (inRect) {
++          pressed = true;
++        }
++        setKey(key, pressed);
++      }
++    };
++
++    function handleMouseUp() {
++      clearKeys();
++      window.removeEventListener('pointermove', handleMouseMove, {passive: false});
++      window.removeEventListener('pointerup', handleMouseUp);
++    }
++
++    const uiElem = document.querySelector('#ui');
++    uiElem.addEventListener('pointerdown', handleMouseMove, {passive: false});
++
++    uiElem.addEventListener('touchstart', (e) =&gt; {
++      // prevent scrolling
++      e.preventDefault();
++    }, {passive: false});
+  }
+  update() {
+    for (const keyState of Object.values(this.keys)) {
+      if (keyState.justPressed) {
+        keyState.justPressed = false;
+      }
+    }
+  }
+}
+</pre>
+<p>And now we should be able to control the character with the left and right
+cursor keys or with our fingers on a touchscreen</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-player-input.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/game-player-input.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Ideally we'd do something else if the player went off the screen like move
+the camera or maybe offscreen = death but this article is already going to be
+too long so for now teleporting to the middle was the simplest thing.</p>
+<p>Lets add some animals. We can start it off similar to the <code class="notranslate" translate="no">Player</code> by making
+an <code class="notranslate" translate="no">Animal</code> component.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Animal extends Component {
+  constructor(gameObject, model) {
+    super(gameObject);
+    const skinInstance = gameObject.addComponent(SkinInstance, model);
+    skinInstance.mixer.timeScale = globals.moveSpeed / 4;
+    skinInstance.setAnimation('Idle');
+  }
+}
+</pre>
+<p>The code above sets the <a href="/docs/#api/en/animation/AnimationMixer.timeScale"><code class="notranslate" translate="no">AnimationMixer.timeScale</code></a> to set the playback
+speed of the animations relative to the move speed. This way if we
+adjust the move speed the animation will speed up or slow down as well.</p>
+<p>To start we could setup one of each type of animal</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
+  // hide the loading bar
+  const loadingElem = document.querySelector('#loading');
+  loadingElem.style.display = 'none';
+
+  prepModelsAndAnimations();
+  {
+    const gameObject = gameObjectManager.createGameObject(camera, 'camera');
+    globals.cameraInfo = gameObject.addComponent(CameraInfo);
+  }
+
+  {
+    const gameObject = gameObjectManager.createGameObject(scene, 'player');
+    globals.player = gameObject.addComponent(Player);
+    globals.congaLine = [gameObject];
+  }
+
++  const animalModelNames = [
++    'pig',
++    'cow',
++    'llama',
++    'pug',
++    'sheep',
++    'zebra',
++    'horse',
++  ];
++  animalModelNames.forEach((name, ndx) =&gt; {
++    const gameObject = gameObjectManager.createGameObject(scene, name);
++    gameObject.addComponent(Animal, models[name]);
++    gameObject.transform.position.x = (ndx + 1) * 5;
++  });
+}
+</pre>
+<p>And that would get us animals standing on the screen but we want them to do
+something.</p>
+<p>Let's make them follow the player in a conga line but only if the player gets near enough.
+To do this we need several states.</p>
+<ul>
+<li><p>Idle:</p>
+<p>Animal is waiting for player to get close</p>
+</li>
+<li><p>Wait for End of Line:</p>
+<p>Animal was tagged by player but now needs to wait for the animal
+at the end of the line to come by so they can join the end of the line.</p>
+</li>
+<li><p>Go to Last:</p>
+<p>Animal needs to walk to where the animal they are following was, at the same time recording
+a history of where the animal they are following is currently.</p>
+</li>
+<li><p>Follow</p>
+<p>Animal needs to keep recording a history of where the animal they are following is while
+moving to where the animal they are following was before.</p>
+</li>
+</ul>
+<p>There are many ways to handle different states like this. A common one is to use
+a <a href="https://www.google.com/search?q=finite+state+machine">Finite State Machine</a> and
+to build some class to help us manage the state.</p>
+<p>So, let's do that.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class FiniteStateMachine {
+  constructor(states, initialState) {
+    this.states = states;
+    this.transition(initialState);
+  }
+  get state() {
+    return this.currentState;
+  }
+  transition(state) {
+    const oldState = this.states[this.currentState];
+    if (oldState &amp;&amp; oldState.exit) {
+      oldState.exit.call(this);
+    }
+    this.currentState = state;
+    const newState = this.states[state];
+    if (newState.enter) {
+      newState.enter.call(this);
+    }
+  }
+  update() {
+    const state = this.states[this.currentState];
+    if (state.update) {
+      state.update.call(this);
+    }
+  }
+}
+</pre>
+<p>Here's a simple class. We pass it an object with a bunch of states.
+Each state as 3 optional functions, <code class="notranslate" translate="no">enter</code>, <code class="notranslate" translate="no">update</code>, and <code class="notranslate" translate="no">exit</code>.
+To switch states we call <code class="notranslate" translate="no">FiniteStateMachine.transition</code> and pass it
+the name of the new state. If the current state has an <code class="notranslate" translate="no">exit</code> function
+it's called. Then if the new state has an <code class="notranslate" translate="no">enter</code> function it's called.
+Finally each frame <code class="notranslate" translate="no">FiniteStateMachine.update</code> calls the <code class="notranslate" translate="no">update</code> function
+of the current state.</p>
+<p>Let's use it to manage the states of the animals.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// Returns true of obj1 and obj2 are close
+function isClose(obj1, obj1Radius, obj2, obj2Radius) {
+  const minDist = obj1Radius + obj2Radius;
+  const dist = obj1.position.distanceTo(obj2.position);
+  return dist &lt; minDist;
+}
+
+// keeps v between -min and +min
+function minMagnitude(v, min) {
+  return Math.abs(v) &gt; min
+      ? min * Math.sign(v)
+      : v;
+}
+
+const aimTowardAndGetDistance = function() {
+  const delta = new THREE.Vector3();
+
+  return function aimTowardAndGetDistance(source, targetPos, maxTurn) {
+    delta.subVectors(targetPos, source.position);
+    // compute the direction we want to be facing
+    const targetRot = Math.atan2(delta.x, delta.z) + Math.PI * 1.5;
+    // rotate in the shortest direction
+    const deltaRot = (targetRot - source.rotation.y + Math.PI * 1.5) % (Math.PI * 2) - Math.PI;
+    // make sure we don't turn faster than maxTurn
+    const deltaRotation = minMagnitude(deltaRot, maxTurn);
+    // keep rotation between 0 and Math.PI * 2
+    source.rotation.y = THREE.MathUtils.euclideanModulo(
+        source.rotation.y + deltaRotation, Math.PI * 2);
+    // return the distance to the target
+    return delta.length();
+  };
+}();
+
+class Animal extends Component {
+  constructor(gameObject, model) {
+    super(gameObject);
++    const hitRadius = model.size / 2;
+    const skinInstance = gameObject.addComponent(SkinInstance, model);
+    skinInstance.mixer.timeScale = globals.moveSpeed / 4;
++    const transform = gameObject.transform;
++    const playerTransform = globals.player.gameObject.transform;
++    const maxTurnSpeed = Math.PI * (globals.moveSpeed / 4);
++    const targetHistory = [];
++    let targetNdx = 0;
++
++    function addHistory() {
++      const targetGO = globals.congaLine[targetNdx];
++      const newTargetPos = new THREE.Vector3();
++      newTargetPos.copy(targetGO.transform.position);
++      targetHistory.push(newTargetPos);
++    }
++
++    this.fsm = new FiniteStateMachine({
++      idle: {
++        enter: () =&gt; {
++          skinInstance.setAnimation('Idle');
++        },
++        update: () =&gt; {
++          // check if player is near
++          if (isClose(transform, hitRadius, playerTransform, globals.playerRadius)) {
++            this.fsm.transition('waitForEnd');
++          }
++        },
++      },
++      waitForEnd: {
++        enter: () =&gt; {
++          skinInstance.setAnimation('Jump');
++        },
++        update: () =&gt; {
++          // get the gameObject at the end of the conga line
++          const lastGO = globals.congaLine[globals.congaLine.length - 1];
++          const deltaTurnSpeed = maxTurnSpeed * globals.deltaTime;
++          const targetPos = lastGO.transform.position;
++          aimTowardAndGetDistance(transform, targetPos, deltaTurnSpeed);
++          // check if last thing in conga line is near
++          if (isClose(transform, hitRadius, lastGO.transform, globals.playerRadius)) {
++            this.fsm.transition('goToLast');
++          }
++        },
++      },
++      goToLast: {
++        enter: () =&gt; {
++          // remember who we're following
++          targetNdx = globals.congaLine.length - 1;
++          // add ourselves to the conga line
++          globals.congaLine.push(gameObject);
++          skinInstance.setAnimation('Walk');
++        },
++        update: () =&gt; {
++          addHistory();
++          // walk to the oldest point in the history
++          const targetPos = targetHistory[0];
++          const maxVelocity = globals.moveSpeed * globals.deltaTime;
++          const deltaTurnSpeed = maxTurnSpeed * globals.deltaTime;
++          const distance = aimTowardAndGetDistance(transform, targetPos, deltaTurnSpeed);
++          const velocity = distance;
++          transform.translateOnAxis(kForward, Math.min(velocity, maxVelocity));
++          if (distance &lt;= maxVelocity) {
++            this.fsm.transition('follow');
++          }
++        },
++      },
++      follow: {
++        update: () =&gt; {
++          addHistory();
++          // remove the oldest history and just put ourselves there.
++          const targetPos = targetHistory.shift();
++          transform.position.copy(targetPos);
++          const deltaTurnSpeed = maxTurnSpeed * globals.deltaTime;
++          aimTowardAndGetDistance(transform, targetHistory[0], deltaTurnSpeed);
++        },
++      },
++    }, 'idle');
++  }
++  update() {
++    this.fsm.update();
++  }
+}
+</pre>
+<p>That was big chunk of code but it does what was described above.
+Hopefully of you walk through each state it will be clear.</p>
+<p>A few things we need to add. We need the player to add itself
+to the globals so the animals can find it and we need to start the
+conga line with the player's <code class="notranslate" translate="no">GameObject</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
+
+  ...
+
+  {
+    const gameObject = gameObjectManager.createGameObject(scene, 'player');
++    globals.player = gameObject.addComponent(Player);
++    globals.congaLine = [gameObject];
+  }
+
+}
+</pre>
+<p>We also need to compute a size for each model</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function prepModelsAndAnimations() {
++  const box = new THREE.Box3();
++  const size = new THREE.Vector3();
+  Object.values(models).forEach(model =&gt; {
++    box.setFromObject(model.gltf.scene);
++    box.getSize(size);
++    model.size = size.length();
+    const animsByName = {};
+    model.gltf.animations.forEach((clip) =&gt; {
+      animsByName[clip.name] = clip;
+      // Should really fix this in .blend file
+      if (clip.name === 'Walk') {
+        clip.duration /= 2;
+      }
+    });
+    model.animations = animsByName;
+  });
+}
+</pre>
+<p>And we need the player to record their size</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
+  constructor(gameObject) {
+    super(gameObject);
+    const model = models.knight;
++    globals.playerRadius = model.size / 2;
+</pre>
+<p>Thinking about it now it would probably have been smarter
+for the animals to just target the head of the conga line
+instead of the player specifically. Maybe I'll come back
+and change that later.</p>
+<p>When I first started this I used just one radius for all animals
+but of course that was no good as the pug is much smaller than the horse.
+So I added the difference sizes but I wanted to be able to visualize
+things. To do that I made a <code class="notranslate" translate="no">StatusDisplayHelper</code> component.</p>
+<p>I uses a <a href="/docs/#api/en/helpers/PolarGridHelper"><code class="notranslate" translate="no">PolarGridHelper</code></a> to draw a circle around each character
+and it uses html elements to let each character show some status using
+the techniques covered in <a href="align-html-elements-to-3d.html">the article on aligning html elements to 3D</a>.</p>
+<p>First we need to add some HTML to host these elements</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
+  &lt;canvas id="c"&gt;&lt;/canvas&gt;
+  &lt;div id="ui"&gt;
+    &lt;div id="left"&gt;&lt;img src="../resources/images/left.svg"&gt;&lt;/div&gt;
+    &lt;div style="flex: 0 0 40px;"&gt;&lt;/div&gt;
+    &lt;div id="right"&gt;&lt;img src="../resources/images/right.svg"&gt;&lt;/div&gt;
+  &lt;/div&gt;
+  &lt;div id="loading"&gt;
+    &lt;div&gt;
+      &lt;div&gt;...loading...&lt;/div&gt;
+      &lt;div class="progress"&gt;&lt;div id="progressbar"&gt;&lt;/div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+  &lt;/div&gt;
++  &lt;div id="labels"&gt;&lt;/div&gt;
+&lt;/body&gt;
+</pre>
+<p>And add some CSS for them</p>
+<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#labels {
+  position: absolute;  /* let us position ourself inside the container */
+  left: 0;             /* make our position the top left of the container */
+  top: 0;
+  color: white;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  pointer-events: none;
+}
+#labels&gt;div {
+  position: absolute;  /* let us position them inside the container */
+  left: 0;             /* make their default position the top left of the container */
+  top: 0;
+  font-size: large;
+  font-family: monospace;
+  user-select: none;   /* don't let the text get selected */
+  text-shadow:         /* create a black outline */
+    -1px -1px 0 #000,
+     0   -1px 0 #000,
+     1px -1px 0 #000,
+     1px  0   0 #000,
+     1px  1px 0 #000,
+     0    1px 0 #000,
+    -1px  1px 0 #000,
+    -1px  0   0 #000;
+}
+</pre>
+<p>Then here's the component</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const labelContainerElem = document.querySelector('#labels');
+
+class StateDisplayHelper extends Component {
+  constructor(gameObject, size) {
+    super(gameObject);
+    this.elem = document.createElement('div');
+    labelContainerElem.appendChild(this.elem);
+    this.pos = new THREE.Vector3();
+
+    this.helper = new THREE.PolarGridHelper(size / 2, 1, 1, 16);
+    gameObject.transform.add(this.helper);
+  }
+  setState(s) {
+    this.elem.textContent = s;
+  }
+  setColor(cssColor) {
+    this.elem.style.color = cssColor;
+    this.helper.material.color.set(cssColor);
+  }
+  update() {
+    const {pos} = this;
+    const {transform} = this.gameObject;
+    const {canvas} = globals;
+    pos.copy(transform.position);
+
+    // get the normalized screen coordinate of that position
+    // x and y will be in the -1 to +1 range with x = -1 being
+    // on the left and y = -1 being on the bottom
+    pos.project(globals.camera);
+
+    // convert the normalized position to CSS coordinates
+    const x = (pos.x *  .5 + .5) * canvas.clientWidth;
+    const y = (pos.y * -.5 + .5) * canvas.clientHeight;
+
+    // move the elem to that position
+    this.elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
+  }
+}
+</pre>
+<p>And we can then add them to the animals like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Animal extends Component {
+  constructor(gameObject, model) {
+    super(gameObject);
++    this.helper = gameObject.addComponent(StateDisplayHelper, model.size);
+
+     ...
+
+  }
+  update() {
+    this.fsm.update();
++    const dir = THREE.MathUtils.radToDeg(this.gameObject.transform.rotation.y);
++    this.helper.setState(`${this.fsm.state}:${dir.toFixed(0)}`);
+  }
+}
+</pre>
+<p>While we're at it lets make it so we can turn them on/off using lil-gui like
+we've used else where</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
+import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+import {GLTFLoader} from '/examples/jsm/loaders/GLTFLoader.js';
+import * as SkeletonUtils from '/examples/jsm/utils/SkeletonUtils.js';
++import {GUI} from '/examples/jsm/libs/lil-gui.module.min.js';
+</pre>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const gui = new GUI();
++gui.add(globals, 'debug').onChange(showHideDebugInfo);
++showHideDebugInfo();
+
+const labelContainerElem = document.querySelector('#labels');
++function showHideDebugInfo() {
++  labelContainerElem.style.display = globals.debug ? '' : 'none';
++}
++showHideDebugInfo();
+
+class StateDisplayHelper extends Component {
+
+  ...
+
+  update() {
++    this.helper.visible = globals.debug;
++    if (!globals.debug) {
++      return;
++    }
+
+    ...
+  }
+}
+</pre>
+<p>And with that we get the kind of start of a game</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-conga-line.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/game-conga-line.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Originally I set out to make a <a href="https://www.google.com/search?q=snake+game">snake game</a>
+where as you add animals to your line it gets harder because you need to avoid
+crashing into them. I'd also have put some obstacles in the scene and maybe a fence or some
+barrier around the perimeter.</p>
+<p>Unfortunately the animals are long and thin. From above here's the zebra.</p>
+<div class="threejs_center"><img src="../resources/images/zebra.png" style="width: 113px;"></div>
+
+<p>The code so far is using circle collisions which means if we had obstacles like a fence
+then this would be considered a collision</p>
+<div class="threejs_center"><img src="../resources/images/zebra-collisions.svg" style="width: 400px;"></div>
+
+<p>That's no good. Even animal to animal we'd have the same issue</p>
+<p>I thought about writing a 2D rectangle to rectangle collision system but I
+quickly realized it could really be a lot of code. Checking that 2 arbitrarily
+oriented boxes overlap is not too much code and for our game with just a few
+objects it might work but looking into it after a few objects you quickly start
+needing to optimize the collision checking. First you might go through all
+objects that can possibly collide with each other and check their bounding
+spheres or bounding circles or their axially aligned bounding boxes. Once you
+know which objects <em>might</em> be colliding then you need to do more work to check if
+they are <em>actually</em> colliding. Often even checking the bounding spheres is too
+much work and you need some kind of better spacial structure for the objects so
+you can more quickly only check objects possibly near each other.</p>
+<p>Then, once you write the code to check if 2 objects collide you generally want
+to make a collision system rather than manually asking "do I collide with these
+objects". A collision system emits events or calls callbacks in relation to
+things colliding. The advantage is it can check all the collisions at once so no
+objects get checked more than once where as if you manually call some "am I
+colliding" function often objects will be checked more than once wasting time.</p>
+<p>Making that collision system would probably not be more than 100-300 lines of
+code for just checking arbitrarily oriented rectangles but it's still a ton more
+code so it seemed best to leave it out.</p>
+<p>Another solution would have been to try to find other characters that are
+mostly circular from the top. Other humanoid characters for example instead
+of animals in which case the circle checking might work animal to animal. 
+It would not work animal to fence, well we'd have to add circle to rectangle
+checking. I thought about making the fence a fence of bushes or poles, something
+circular but then I'd need probably 120 to 200 of them to surround the play area
+which would run into the optimization issues mentioned above.</p>
+<p>These are reasons many games use an existing solution. Often these solutions
+are part of a physics library. The physical library needs to know if objects
+collide with each other so on top of providing physics they can also be used
+to detect collision.</p>
+<p>If you're looking for a solution some of the three.js examples use
+<a href="https://github.com/kripken/ammo.js/">ammo.js</a> so that might be one.</p>
+<p>One other solution might have been to place the obstacles on a grid
+and try to make it so each animal and the player just need to look at
+the grid. While that would be performant I felt that's best left as an exercise
+for the reader 😜</p>
+<p>One more thing, many game systems have something called <a href="https://www.google.com/search?q=coroutines"><em>coroutines</em></a>.
+Coroutines are routines that can pause while running and continue later.</p>
+<p>Let's make the main character emit musical notes like they are leading
+the line by singing. There are many ways we could implement this but for now
+let's do it using coroutines.</p>
+<p>First, here's a class to manage coroutines</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function* waitSeconds(duration) {
+  while (duration &gt; 0) {
+    duration -= globals.deltaTime;
+    yield;
+  }
+}
+
+class CoroutineRunner {
+  constructor() {
+    this.generatorStacks = [];
+    this.addQueue = [];
+    this.removeQueue = new Set();
+  }
+  isBusy() {
+    return this.addQueue.length + this.generatorStacks.length &gt; 0;
+  }
+  add(generator, delay = 0) {
+    const genStack = [generator];
+    if (delay) {
+      genStack.push(waitSeconds(delay));
+    }
+    this.addQueue.push(genStack);
+  }
+  remove(generator) {
+    this.removeQueue.add(generator);
+  }
+  update() {
+    this._addQueued();
+    this._removeQueued();
+    for (const genStack of this.generatorStacks) {
+      const main = genStack[0];
+      // Handle if one coroutine removes another
+      if (this.removeQueue.has(main)) {
+        continue;
+      }
+      while (genStack.length) {
+        const topGen = genStack[genStack.length - 1];
+        const {value, done} = topGen.next();
+        if (done) {
+          if (genStack.length === 1) {
+            this.removeQueue.add(topGen);
+            break;
+          }
+          genStack.pop();
+        } else if (value) {
+          genStack.push(value);
+        } else {
+          break;
+        }
+      }
+    }
+    this._removeQueued();
+  }
+  _addQueued() {
+    if (this.addQueue.length) {
+      this.generatorStacks.splice(this.generatorStacks.length, 0, ...this.addQueue);
+      this.addQueue = [];
+    }
+  }
+  _removeQueued() {
+    if (this.removeQueue.size) {
+      this.generatorStacks = this.generatorStacks.filter(genStack =&gt; !this.removeQueue.has(genStack[0]));
+      this.removeQueue.clear();
+    }
+  }
+}
+</pre>
+<p>It does things similar to <code class="notranslate" translate="no">SafeArray</code> to make sure that it's safe to add or remove
+coroutines while other coroutines are running. It also handles nested coroutines.</p>
+<p>To make a coroutine you make a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*">JavaScript generator function</a>.
+A generator function is preceded by the keyword <code class="notranslate" translate="no">function*</code> (the asterisk is important!)</p>
+<p>Generator functions can <code class="notranslate" translate="no">yield</code>. For example</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function* countOTo9() {
+  for (let i = 0; i &lt; 10; ++i) {
+    console.log(i);
+    yield;
+  }
+}
+</pre>
+<p>If we added this function to the <code class="notranslate" translate="no">CoroutineRunner</code> above it would print
+out each number, 0 to 9, once per frame or rather once per time we called <code class="notranslate" translate="no">runner.update</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const runner = new CoroutineRunner();
+runner.add(count0To9);
+while(runner.isBusy()) {
+  runner.update();
+}
+</pre>
+<p>Coroutines are removed automatically when they are finished. 
+To remove a coroutine early, before it reaches the end you need to keep
+a reference to its generator like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gen = count0To9();
+runner.add(gen);
+
+// sometime later
+
+runner.remove(gen);
+</pre>
+<p>In any case, in the player let's use a coroutine to emit a note every half second to 1 second</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
+  constructor(gameObject) {
+
+    ...
+
++    this.runner = new CoroutineRunner();
++
++    function* emitNotes() {
++      for (;;) {
++        yield waitSeconds(rand(0.5, 1));
++        const noteGO = gameObjectManager.createGameObject(scene, 'note');
++        noteGO.transform.position.copy(gameObject.transform.position);
++        noteGO.transform.position.y += 5;
++        noteGO.addComponent(Note);
++      }
++    }
++
++    this.runner.add(emitNotes());
+  }
+  update() {
++    this.runner.update();
+
+  ...
+
+  }
+}
+
+function rand(min, max) {
+  if (max === undefined) {
+    max = min;
+    min = 0;
+  }
+  return Math.random() * (max - min) + min;
+}
+</pre>
+<p>You can see we make a <code class="notranslate" translate="no">CoroutineRunner</code> and we add an <code class="notranslate" translate="no">emitNotes</code> coroutine.
+That function will run forever, waiting 0.5 to 1 seconds and then creating a game object
+with a <code class="notranslate" translate="no">Note</code> component.</p>
+<p>For the <code class="notranslate" translate="no">Note</code> component first lets make a texture with a note on it and 
+instead of loading a note image let's make one using a canvas like we covered in <a href="canvas-textures.html">the article on canvas textures</a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeTextTexture(str) {
+  const ctx = document.createElement('canvas').getContext('2d');
+  ctx.canvas.width = 64;
+  ctx.canvas.height = 64;
+  ctx.font = '60px sans-serif';
+  ctx.textAlign = 'center';
+  ctx.textBaseline = 'middle';
+  ctx.fillStyle = '#FFF';
+  ctx.fillText(str, ctx.canvas.width / 2, ctx.canvas.height / 2);
+  return new THREE.CanvasTexture(ctx.canvas);
+}
+const noteTexture = makeTextTexture('♪');
+</pre>
+<p>The texture we create above is white each means when we use it
+we can set the material's color and get a note of any color.</p>
+<p>Now that we have a noteTexture here's the <code class="notranslate" translate="no">Note</code> component.
+It uses <a href="/docs/#api/en/materials/SpriteMaterial"><code class="notranslate" translate="no">SpriteMaterial</code></a> and a <a href="/docs/#api/en/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a> like we covered in
+<a href="billboards.html">the article on billboards</a> </p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Note extends Component {
+  constructor(gameObject) {
+    super(gameObject);
+    const {transform} = gameObject;
+    const noteMaterial = new THREE.SpriteMaterial({
+      color: new THREE.Color().setHSL(rand(1), 1, 0.5),
+      map: noteTexture,
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const note = new THREE.Sprite(noteMaterial);
+    note.scale.setScalar(3);
+    transform.add(note);
+    this.runner = new CoroutineRunner();
+    const direction = new THREE.Vector3(rand(-0.2, 0.2), 1, rand(-0.2, 0.2));
+
+    function* moveAndRemove() {
+      for (let i = 0; i &lt; 60; ++i) {
+        transform.translateOnAxis(direction, globals.deltaTime * 10);
+        noteMaterial.opacity = 1 - (i / 60);
+        yield;
+      }
+      transform.parent.remove(transform);
+      gameObjectManager.removeGameObject(gameObject);
+    }
+
+    this.runner.add(moveAndRemove());
+  }
+  update() {
+    this.runner.update();
+  }
+}
+</pre>
+<p>All it does is setup a <a href="/docs/#api/en/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a>, then pick a random velocity and move
+the transform at that velocity for 60 frames while fading out the note
+by setting the material's <a href="/docs/#api/en/materials/Material#opacity"><code class="notranslate" translate="no">opacity</code></a>. 
+After the loop it the removes the transform
+from the scene and the note itself from active gameobjects.</p>
+<p>One last thing, let's add a few more animals</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
+
+   ...
+
+  const animalModelNames = [
+    'pig',
+    'cow',
+    'llama',
+    'pug',
+    'sheep',
+    'zebra',
+    'horse',
+  ];
++  const base = new THREE.Object3D();
++  const offset = new THREE.Object3D();
++  base.add(offset);
++
++  // position animals in a spiral.
++  const numAnimals = 28;
++  const arc = 10;
++  const b = 10 / (2 * Math.PI);
++  let r = 10;
++  let phi = r / b;
++  for (let i = 0; i &lt; numAnimals; ++i) {
++    const name = animalModelNames[rand(animalModelNames.length) | 0];
+    const gameObject = gameObjectManager.createGameObject(scene, name);
+    gameObject.addComponent(Animal, models[name]);
++    base.rotation.y = phi;
++    offset.position.x = r;
++    offset.updateWorldMatrix(true, false);
++    offset.getWorldPosition(gameObject.transform.position);
++    phi += arc / r;
++    r = b * phi;
+  }
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-conga-line-w-notes.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/game-conga-line-w-notes.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>You might be asking, why not use <code class="notranslate" translate="no">setTimeout</code>? The problem with <code class="notranslate" translate="no">setTimeout</code>
+is it's not related to the game clock. For example above we made the maximum
+amount of time allowed to elapse between frames to be 1/20th of a second.
+Our coroutine system will respect that limit but <code class="notranslate" translate="no">setTimeout</code> would not.</p>
+<p>Of course we could have made a simple timer ourselves</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player ... {
+  update() {
+    this.noteTimer -= globals.deltaTime;
+    if (this.noteTimer &lt;= 0) {
+      // reset timer
+      this.noteTimer = rand(0.5, 1);
+      // create a gameobject with a note component
+    }
+  }
+</pre>
+<p>And for this particular case that might have been better but as you add
+more and things you'll get more and more variables added to your classes
+where as with coroutines you can often just <em>fire and forget</em>.</p>
+<p>Given our animal's simple states we could also have implemented them
+with a coroutine in the form of</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// pseudo code!
+function* animalCoroutine() {
+   setAnimation('Idle');
+   while(playerIsTooFar()) {
+     yield;
+   }
+   const target = endOfLine;
+   setAnimation('Jump');
+   while(targetIsTooFar()) {
+     aimAt(target);
+     yield;
+   }
+   setAnimation('Walk')
+   while(notAtOldestPositionOfTarget()) {
+     addHistory();
+     aimAt(target);
+     yield;
+   }
+   for(;;) {
+     addHistory();
+     const pos = history.unshift();
+     transform.position.copy(pos);
+     aimAt(history[0]);
+     yield;
+   }
+}
+</pre>
+<p>This would have worked but of course as soon as our states were not so linear
+we'd have had to switch to a <code class="notranslate" translate="no">FiniteStateMachine</code>.</p>
+<p>It also wasn't clear to me if coroutines should run independently of their
+components. We could have made a global <code class="notranslate" translate="no">CoroutineRunner</code> and put all
+coroutines on it. That would make cleaning them up harder. As it is now
+if the gameobject is removed all of its components are removed and
+therefore the coroutine runners created are no longer called and it will
+all get garbage collected. If we had global runner then it would be
+the responsibility of each component to remove any coroutines it added
+or else some other mechanism of registering coroutines with a particular
+component or gameobject would be needed so that removing one removes the
+others.</p>
+<p>There are lots more issues a
+normal game engine would deal with. As it is there is no order to how
+gameobjects or their components are run. They are just run in the order added.
+Many game systems add a priority so the order can be set or changed.</p>
+<p>Another issue we ran into is the <code class="notranslate" translate="no">Note</code> removing its gameobject's transform from the scene.
+That seems like something that should happen in <code class="notranslate" translate="no">GameObject</code> since it was <code class="notranslate" translate="no">GameObject</code>
+that added the transform in the first place. Maybe <code class="notranslate" translate="no">GameObject</code> should have
+a <code class="notranslate" translate="no">dispose</code> method that is called by <code class="notranslate" translate="no">GameObjectManager.removeGameObject</code>?</p>
+<p>Yet another is how we're manually calling <code class="notranslate" translate="no">gameObjectManager.update</code> and <code class="notranslate" translate="no">inputManager.update</code>.
+Maybe there should be a <code class="notranslate" translate="no">SystemManager</code> which these global services can add themselves
+and each service will have its <code class="notranslate" translate="no">update</code> function called. In this way if we added a new
+service like <code class="notranslate" translate="no">CollisionManager</code> we could just add it to the system manager and not
+have to edit the render loop.</p>
+<p>I'll leave those kinds of issues up to you.
+I hope this article has given you some ideas for your own game engine.</p>
+<p>Maybe I should promote a game jam. If you click the <em>jsfiddle</em> or <em>codepen</em> buttons
+above the last example they'll open in those sites ready to edit. Add some features,
+Change the game to a pug leading a bunch of knights. Use the knight's rolling animation
+as a bowling ball and make an animal bowling game. Make an animal relay race.
+If you make a cool game post a link in the comments below.</p>
+<div class="footnotes">
+[<a id="parented">1</a>]: technically it would still work if none of the parents have any translation, rotation, or scale <a href="#parented-backref">§</a>.
+</div>
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 627 - 0
manual/en/indexed-textures.html

@@ -0,0 +1,627 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Indexed Textures for Picking and Color</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Indexed Textures for Picking and Color">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Indexed Textures for Picking and Color</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This article is a continuation of <a href="align-html-elements-to-3d.html">an article about aligning html elements to 3d</a>.
+If you haven't read that yet you should start there before continuing here.</p>
+<p>Sometimes using three.js requires coming up with creative solutions.
+I'm not sure this is a great solution but I thought I'd share it and
+you can see if it suggests any solutions for your needs.</p>
+<p>In the <a href="align-html-elements-to-3d.html">previous article</a> we
+displayed country names around a 3d globe. How would we go about letting
+the user select a country and show their selection?</p>
+<p>The first idea that comes to mind is to generate geometry for each country.
+We could <a href="picking.html">use a picking solution</a> like we covered before.
+We'd build 3D geometry for each country. If the user clicks on the mesh for 
+that country we'd know what country was clicked.</p>
+<p>So, just to check that solution I tried generating 3D meshes of all the countries
+using the same data I used to generate the outlines 
+<a href="align-html-elements-to-3d.html">in the previous article</a>.
+The result was a 15.5meg binary GLTF (.glb) file. Making the user download 15.5meg
+sounds like too much to me.</p>
+<p>There are lots of ways to compress the data. The first would probably be
+to apply some algorithm to lower the resolution of the outlines. I didn't spend
+any time pursuing that solution. For borders of the USA that's probably a huge
+win. For a borders of Canada probably much less. </p>
+<p>Another solution would be to use just actual data compression. For example gzipping
+the file brought it down to 11meg. That's 30% less but arguably not enough.</p>
+<p>We could store all the data as 16bit ranged values instead of 32bit float values. 
+Or we could use something like <a href="https://google.github.io/draco/">draco compression</a>
+and maybe that would be enough. I didn't check and I would encourage you to check 
+yourself and tell me how it goes as I'd love to know. 😅</p>
+<p>In my case I thought about <a href="picking.html">the GPU picking solution</a> 
+we covered at the end of <a href="picking.html">the article on picking</a>. In
+that solution we drew every mesh with a unique color that represented that
+mesh's id. We then drew all the meshes and looked at the color that was clicked
+on.</p>
+<p>Taking inspiration from that we could pre-generate a map of countries where
+each country's color is its index number in our array of countries. We could
+then use a similar GPU picking technique. We'd draw the globe off screen using
+this index texture. Looking at the color of the pixel the user clicks would 
+tell us the country id.</p>
+<p>So, I <a href="https://github.com/mrdoob/three.js/blob/master/manual/resources/tools/geo-picking/">wrote some code</a> 
+to generate such a texture. Here it is. </p>
+<div class="threejs_center"><img src="../examples/resources/data/world/country-index-texture.png" style="width: 700px;"></div>
+
+<p>Note: The data used to generate this texture comes from <a href="http://thematicmapping.org/downloads/world_borders.php">this website</a> 
+and is therefore licensed as <a href="http://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>.</p>
+<p>It's only 217k, much better than the 14meg for the country meshes. In fact we could probably
+even lower the resolution but 217k seems good enough for now.</p>
+<p>So let's try using it for picking countries.</p>
+<p>Grabbing code from the <a href="picking.html">gpu picking example</a> we need
+a scene for picking.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const pickingScene = new THREE.Scene();
+pickingScene.background = new THREE.Color(0);
+</pre>
+<p>and we need to add the globe with the our index texture to the
+picking scene.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const loader = new THREE.TextureLoader();
+  const geometry = new THREE.SphereGeometry(1, 64, 32);
+
++  const indexTexture = loader.load('resources/data/world/country-index-texture.png', render);
++  indexTexture.minFilter = THREE.NearestFilter;
++  indexTexture.magFilter = THREE.NearestFilter;
++
++  const pickingMaterial = new THREE.MeshBasicMaterial({map: indexTexture});
++  pickingScene.add(new THREE.Mesh(geometry, pickingMaterial));
+
+  const texture = loader.load('resources/data/world/country-outlines-4k.png', render);
+  const material = new THREE.MeshBasicMaterial({map: texture});
+  scene.add(new THREE.Mesh(geometry, material));
+}
+</pre>
+<p>Then let's copy over the <code class="notranslate" translate="no">GPUPickingHelper</code> class we used
+before with a few minor changes.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class GPUPickHelper {
+  constructor() {
+    // create a 1x1 pixel render target
+    this.pickingTexture = new THREE.WebGLRenderTarget(1, 1);
+    this.pixelBuffer = new Uint8Array(4);
+-    this.pickedObject = null;
+-    this.pickedObjectSavedColor = 0;
+  }
+  pick(cssPosition, scene, camera) {
+    const {pickingTexture, pixelBuffer} = this;
+
+    // set the view offset to represent just a single pixel under the mouse
+    const pixelRatio = renderer.getPixelRatio();
+    camera.setViewOffset(
+        renderer.getContext().drawingBufferWidth,   // full width
+        renderer.getContext().drawingBufferHeight,  // full top
+        cssPosition.x * pixelRatio | 0,             // rect x
+        cssPosition.y * pixelRatio | 0,             // rect y
+        1,                                          // rect width
+        1,                                          // rect height
+    );
+    // render the scene
+    renderer.setRenderTarget(pickingTexture);
+    renderer.render(scene, camera);
+    renderer.setRenderTarget(null);
+    // clear the view offset so rendering returns to normal
+    camera.clearViewOffset();
+    //read the pixel
+    renderer.readRenderTargetPixels(
+        pickingTexture,
+        0,   // x
+        0,   // y
+        1,   // width
+        1,   // height
+        pixelBuffer);
+
++    const id =
++        (pixelBuffer[0] &lt;&lt; 16) |
++        (pixelBuffer[1] &lt;&lt;  8) |
++        (pixelBuffer[2] &lt;&lt;  0);
++
++    return id;
+-    const id =
+-        (pixelBuffer[0] &lt;&lt; 16) |
+-        (pixelBuffer[1] &lt;&lt;  8) |
+-        (pixelBuffer[2]      );
+-    const intersectedObject = idToObject[id];
+-    if (intersectedObject) {
+-      // pick the first object. It's the closest one
+-      this.pickedObject = intersectedObject;
+-      // save its color
+-      this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
+-      // set its emissive color to flashing red/yellow
+-      this.pickedObject.material.emissive.setHex((time * 8) % 2 &gt; 1 ? 0xFFFF00 : 0xFF0000);
+-    }
+  }
+}
+</pre>
+<p>Now we can use that to pick countries.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const pickHelper = new GPUPickHelper();
+
+function getCanvasRelativePosition(event) {
+  const rect = canvas.getBoundingClientRect();
+  return {
+    x: (event.clientX - rect.left) * canvas.width  / rect.width,
+    y: (event.clientY - rect.top ) * canvas.height / rect.height,
+  };
+}
+
+function pickCountry(event) {
+  // exit if we have not loaded the data yet
+  if (!countryInfos) {
+    return;
+  }
+
+  const position = getCanvasRelativePosition(event);
+  const id = pickHelper.pick(position, pickingScene, camera);
+  if (id &gt; 0) {
+    // we clicked a country. Toggle its 'selected' property
+    const countryInfo = countryInfos[id - 1];
+    const selected = !countryInfo.selected;
+    // if we're selecting this country and modifiers are not
+    // pressed unselect everything else.
+    if (selected &amp;&amp; !event.shiftKey &amp;&amp; !event.ctrlKey &amp;&amp; !event.metaKey) {
+      unselectAllCountries();
+    }
+    numCountriesSelected += selected ? 1 : -1;
+    countryInfo.selected = selected;
+  } else if (numCountriesSelected) {
+    // the ocean or sky was clicked
+    unselectAllCountries();
+  }
+  requestRenderIfNotRequested();
+}
+
+function unselectAllCountries() {
+  numCountriesSelected = 0;
+  countryInfos.forEach((countryInfo) =&gt; {
+    countryInfo.selected = false;
+  });
+}
+
+canvas.addEventListener('pointerup', pickCountry);
+</pre>
+<p>The code above sets/unsets the <code class="notranslate" translate="no">selected</code> property on
+the array of countries. If <code class="notranslate" translate="no">shift</code> or <code class="notranslate" translate="no">ctrl</code> or <code class="notranslate" translate="no">cmd</code>
+is pressed then you can select more than one country.</p>
+<p>All that's left is showing the selected countries. For now
+let's just update the labels.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function updateLabels() {
+  // exit if we have not loaded the data yet
+  if (!countryInfos) {
+    return;
+  }
+
+  const large = settings.minArea * settings.minArea;
+  // get a matrix that represents a relative orientation of the camera
+  normalMatrix.getNormalMatrix(camera.matrixWorldInverse);
+  // get the camera's position
+  camera.getWorldPosition(cameraPosition);
+  for (const countryInfo of countryInfos) {
+-    const {position, elem, area} = countryInfo;
+-    // large enough?
+-    if (area &lt; large) {
++    const {position, elem, area, selected} = countryInfo;
++    const largeEnough = area &gt;= large;
++    const show = selected || (numCountriesSelected === 0 &amp;&amp; largeEnough);
++    if (!show) {
+      elem.style.display = 'none';
+      continue;
+    }
+
+    ...
+</pre>
+<p>and with that we should be able to pick countries</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/indexed-textures-picking.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/indexed-textures-picking.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>The code stills shows countries based on their area but if you
+click one just that one will have a label.</p>
+<p>So that seems like a reasonable solution for picking countries 
+but what about highlighting the selected countries?</p>
+<p>For that we can take inspiration from <em>paletted graphics</em>.</p>
+<p><a href="https://en.wikipedia.org/wiki/Palette_%28computing%29">Paletted graphics</a>
+or <a href="https://en.wikipedia.org/wiki/Indexed_color">Indexed Color</a> is 
+what older systems like the Atari 800, Amiga, NES, 
+Super Nintendo, and even older IBM PCs used. Instead of storing bitmaps
+as RGB colors 8bits per color, 24 bytes per pixel or more, they stored 
+bitmaps as 8bit values or less. The value for each pixel was an index
+into a palette. So for example a value
+of 3 in the image means "display color 3". What color color#3 is is
+defined somewhere else called a "palette".</p>
+<p>In JavaScript you can think of it like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const face7x7PixelImageData = [
+  0, 1, 1, 1, 1, 1, 0,
+  1, 0, 0, 0, 0, 0, 1, 
+  1, 0, 2, 0, 2, 0, 1,
+  1, 0, 0, 0, 0, 0, 1,
+  1, 0, 3, 3, 3, 0, 1,
+  1, 0, 0, 0, 0, 0, 1,
+  0, 1, 1, 1, 1, 1, 1,
+];
+
+const palette = [
+  [255, 255, 255],  // white
+  [  0,   0,   0],  // black
+  [  0, 255, 255],  // cyan
+  [255,   0,   0],  // red
+];
+</pre>
+<p>Where each pixel in the image data is an index into palette. If you interpreted
+the image data through the palette above you'd get this image</p>
+<div class="threejs_center"><img src="../resources/images/7x7-indexed-face.png"></div>
+
+<p>In our case we already have a texture above that has a different id
+per country. So, we could use that same texture through a palette
+texture to give each country its own color. By changing the palette
+texture we can color each individual country. For example by setting
+the entire palette texture to black and then for one country's entry
+in the palette a different color, we can highlight just that country.</p>
+<p>To do paletted index graphics requires some custom shader code. 
+Let's modify the default shaders in three.js. 
+That way we can use lighting and other features if we want.</p>
+<p>Like we covered in <a href="optimize-lots-of-objects-animated.html">the article on animating lots of objects</a>
+we can modify the default shaders by adding a function to a material's
+<code class="notranslate" translate="no">onBeforeCompile</code> property.</p>
+<p>The default fragment shader looks something like this before compiling.</p>
+<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">#include &lt;common&gt;
+#include &lt;color_pars_fragment&gt;
+#include &lt;uv_pars_fragment&gt;
+#include &lt;uv2_pars_fragment&gt;
+#include &lt;map_pars_fragment&gt;
+#include &lt;alphamap_pars_fragment&gt;
+#include &lt;aomap_pars_fragment&gt;
+#include &lt;lightmap_pars_fragment&gt;
+#include &lt;envmap_pars_fragment&gt;
+#include &lt;fog_pars_fragment&gt;
+#include &lt;specularmap_pars_fragment&gt;
+#include &lt;logdepthbuf_pars_fragment&gt;
+#include &lt;clipping_planes_pars_fragment&gt;
+void main() {
+    #include &lt;clipping_planes_fragment&gt;
+    vec4 diffuseColor = vec4( diffuse, opacity );
+    #include &lt;logdepthbuf_fragment&gt;
+    #include &lt;map_fragment&gt;
+    #include &lt;color_fragment&gt;
+    #include &lt;alphamap_fragment&gt;
+    #include &lt;alphatest_fragment&gt;
+    #include &lt;specularmap_fragment&gt;
+    ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
+    #ifdef USE_LIGHTMAP
+        reflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;
+    #else
+        reflectedLight.indirectDiffuse += vec3( 1.0 );
+    #endif
+    #include &lt;aomap_fragment&gt;
+    reflectedLight.indirectDiffuse *= diffuseColor.rgb;
+    vec3 outgoingLight = reflectedLight.indirectDiffuse;
+    #include &lt;envmap_fragment&gt;
+    gl_FragColor = vec4( outgoingLight, diffuseColor.a );
+    #include &lt;premultiplied_alpha_fragment&gt;
+    #include &lt;tonemapping_fragment&gt;
+    #include &lt;encodings_fragment&gt;
+    #include &lt;fog_fragment&gt;
+}
+</pre>
+<p><a href="https://github.com/mrdoob/three.js/tree/dev/src/renderers/shaders/ShaderChunk">Digging through all those snippets</a>
+we find that three.js uses a variable called <code class="notranslate" translate="no">diffuseColor</code> to manage the 
+base material color. It sets this in the <code class="notranslate" translate="no">&lt;color_fragment&gt;</code> <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_fragment.glsl.js">snippet</a>
+so we should be able to modify it after that point.</p>
+<p><code class="notranslate" translate="no">diffuseColor</code> at that point in the shader should already be the color from 
+our outline texture so we can look up the color from a palette texture 
+and mix them for the final result.</p>
+<p>Like we <a href="optimize-lots-of-objects-animated.html">did before</a> we'll make an array
+of search and replacement strings and apply them to the shader in 
+<a href="/docs/#api/en/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const loader = new THREE.TextureLoader();
+  const geometry = new THREE.SphereGeometry(1, 64, 32);
+
+  const indexTexture = loader.load('resources/data/world/country-index-texture.png', render);
+  indexTexture.minFilter = THREE.NearestFilter;
+  indexTexture.magFilter = THREE.NearestFilter;
+
+  const pickingMaterial = new THREE.MeshBasicMaterial({map: indexTexture});
+  pickingScene.add(new THREE.Mesh(geometry, pickingMaterial));
+
++  const fragmentShaderReplacements = [
++    {
++      from: '#include &lt;common&gt;',
++      to: `
++        #include &lt;common&gt;
++        uniform sampler2D indexTexture;
++        uniform sampler2D paletteTexture;
++        uniform float paletteTextureWidth;
++      `,
++    },
++    {
++      from: '#include &lt;color_fragment&gt;',
++      to: `
++        #include &lt;color_fragment&gt;
++        {
++          vec4 indexColor = texture2D(indexTexture, vUv);
++          float index = indexColor.r * 255.0 + indexColor.g * 255.0 * 256.0;
++          vec2 paletteUV = vec2((index + 0.5) / paletteTextureWidth, 0.5);
++          vec4 paletteColor = texture2D(paletteTexture, paletteUV);
++          // diffuseColor.rgb += paletteColor.rgb;   // white outlines
++          diffuseColor.rgb = paletteColor.rgb - diffuseColor.rgb;  // black outlines
++        }
++      `,
++    },
++  ];
+
+  const texture = loader.load('resources/data/world/country-outlines-4k.png', render);
+  const material = new THREE.MeshBasicMaterial({map: texture});
++  material.onBeforeCompile = function(shader) {
++    fragmentShaderReplacements.forEach((rep) =&gt; {
++      shader.fragmentShader = shader.fragmentShader.replace(rep.from, rep.to);
++    });
++  };
+  scene.add(new THREE.Mesh(geometry, material));
+}
+</pre>
+<p>Above can see above we add 3 uniforms, <code class="notranslate" translate="no">indexTexture</code>, <code class="notranslate" translate="no">paletteTexture</code>,
+and <code class="notranslate" translate="no">paletteTextureWidth</code>. We get a color from the <code class="notranslate" translate="no">indexTexture</code>
+and convert it to an index. <code class="notranslate" translate="no">vUv</code> is the texture coordinates provided by 
+three.js. We then use that index to get a color out of the palette texture.
+We then mix the result with the current <code class="notranslate" translate="no">diffuseColor</code>. The <code class="notranslate" translate="no">diffuseColor</code>
+at this point is our black and white outline texture so if we add the 2 colors
+we'll get white outlines. If we subtract the current diffuse color we'll get
+black outlines.</p>
+<p>Before we can render we need to setup the palette texture
+and these 3 uniforms.</p>
+<p>For the palette texture it just needs to be wide enough to
+hold one color per country + one for the ocean (id = 0).
+There are 240 something countries. We could wait until the
+list of countries loads to get an exact number or look it up.
+There's not much harm in just picking some larger number so
+let's choose 512.</p>
+<p>Here's the code to create the palette texture</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const maxNumCountries = 512;
+const paletteTextureWidth = maxNumCountries;
+const paletteTextureHeight = 1;
+const palette = new Uint8Array(paletteTextureWidth * 3);
+const paletteTexture = new THREE.DataTexture(
+    palette, paletteTextureWidth, paletteTextureHeight, THREE.RGBFormat);
+paletteTexture.minFilter = THREE.NearestFilter;
+paletteTexture.magFilter = THREE.NearestFilter;
+</pre>
+<p>A <a href="/docs/#api/en/textures/DataTexture"><code class="notranslate" translate="no">DataTexture</code></a> let's us give a texture raw data. In this case
+we're giving it 512 RGB colors, 3 bytes each where each byte is
+red, green, and blue respectively using values that go from 0 to 255.</p>
+<p>Let's fill it with random colors just to see it work</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">for (let i = 1; i &lt; palette.length; ++i) {
+  palette[i] = Math.random() * 256;
+}
+// set the ocean color (index #0)
+palette.set([100, 200, 255], 0);
+paletteTexture.needsUpdate = true;
+</pre>
+<p>Anytime we want three.js to update the palette texture with
+the contents of the <code class="notranslate" translate="no">palette</code> array we need to set <code class="notranslate" translate="no">paletteTexture.needsUpdate</code>
+to <code class="notranslate" translate="no">true</code>.</p>
+<p>And then we still need to set the uniforms on the material.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.SphereGeometry(1, 64, 32);
+const material = new THREE.MeshBasicMaterial({map: texture});
+material.onBeforeCompile = function(shader) {
+  fragmentShaderReplacements.forEach((rep) =&gt; {
+    shader.fragmentShader = shader.fragmentShader.replace(rep.from, rep.to);
+  });
++  shader.uniforms.paletteTexture = {value: paletteTexture};
++  shader.uniforms.indexTexture = {value: indexTexture};
++  shader.uniforms.paletteTextureWidth = {value: paletteTextureWidth};
+};
+scene.add(new THREE.Mesh(geometry, material));
+</pre>
+<p>and with that we get randomly colored countries.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/indexed-textures-random-colors.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/indexed-textures-random-colors.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Now that we can see the index and palette textures are working
+let's manipulate the palette for highlighting.</p>
+<p>First let's make function that will let us pass in a three.js
+style color and give us values we can put in the palette texture.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const tempColor = new THREE.Color();
+function get255BasedColor(color) {
+  tempColor.set(color);
+  return tempColor.toArray().map(v =&gt; v * 255);
+}
+</pre>
+<p>Calling it like this <code class="notranslate" translate="no">color = get255BasedColor('red')</code> will
+return an array like <code class="notranslate" translate="no">[255, 0, 0]</code>.</p>
+<p>Next let's use it to make a few colors and fill out the
+palette.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const selectedColor = get255BasedColor('red');
+const unselectedColor = get255BasedColor('#444');
+const oceanColor = get255BasedColor('rgb(100,200,255)');
+resetPalette();
+
+function setPaletteColor(index, color) {
+  palette.set(color, index * 3);
+}
+
+function resetPalette() {
+  // make all colors the unselected color
+  for (let i = 1; i &lt; maxNumCountries; ++i) {
+    setPaletteColor(i, unselectedColor);
+  }
+
+  // set the ocean color (index #0)
+  setPaletteColor(0, oceanColor);
+  paletteTexture.needsUpdate = true;
+}
+</pre>
+<p>Now let's use those functions to update the palette when a country
+is selected</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function getCanvasRelativePosition(event) {
+  const rect = canvas.getBoundingClientRect();
+  return {
+    x: (event.clientX - rect.left) * canvas.width  / rect.width,
+    y: (event.clientY - rect.top ) * canvas.height / rect.height,
+  };
+}
+
+function pickCountry(event) {
+  // exit if we have not loaded the data yet
+  if (!countryInfos) {
+    return;
+  }
+
+  const position = getCanvasRelativePosition(event);
+  const id = pickHelper.pick(position, pickingScene, camera);
+  if (id &gt; 0) {
+    const countryInfo = countryInfos[id - 1];
+    const selected = !countryInfo.selected;
+    if (selected &amp;&amp; !event.shiftKey &amp;&amp; !event.ctrlKey &amp;&amp; !event.metaKey) {
+      unselectAllCountries();
+    }
+    numCountriesSelected += selected ? 1 : -1;
+    countryInfo.selected = selected;
++    setPaletteColor(id, selected ? selectedColor : unselectedColor);
++    paletteTexture.needsUpdate = true;
+  } else if (numCountriesSelected) {
+    unselectAllCountries();
+  }
+  requestRenderIfNotRequested();
+}
+
+function unselectAllCountries() {
+  numCountriesSelected = 0;
+  countryInfos.forEach((countryInfo) =&gt; {
+    countryInfo.selected = false;
+  });
++  resetPalette();
+}
+</pre>
+<p>and we that we should be able to highlight 1 or more countries.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/indexed-textures-picking-and-highlighting.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/indexed-textures-picking-and-highlighting.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>That seems to work!</p>
+<p>One minor thing is we can't spin the globe without changing
+the selection state. If we select a country and then want to
+rotate the globe the selection will change.</p>
+<p>Let's try to fix that. Off the top of my head we can check 2 things.
+How much time passed between clicking and letting go.
+Another is did the user actually move the mouse. If the
+time is short or if they didn't move the mouse then it
+was probably a click. Otherwise they were probably trying
+to drag the globe.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const maxClickTimeMs = 200;
++const maxMoveDeltaSq = 5 * 5;
++const startPosition = {};
++let startTimeMs;
++
++function recordStartTimeAndPosition(event) {
++  startTimeMs = performance.now();
++  const pos = getCanvasRelativePosition(event);
++  startPosition.x = pos.x;
++  startPosition.y = pos.y;
++}
+
+function getCanvasRelativePosition(event) {
+  const rect = canvas.getBoundingClientRect();
+  return {
+    x: (event.clientX - rect.left) * canvas.width  / rect.width,
+    y: (event.clientY - rect.top ) * canvas.height / rect.height,
+  };
+}
+
+function pickCountry(event) {
+  // exit if we have not loaded the data yet
+  if (!countryInfos) {
+    return;
+  }
+
++  // if it's been a moment since the user started
++  // then assume it was a drag action, not a select action
++  const clickTimeMs = performance.now() - startTimeMs;
++  if (clickTimeMs &gt; maxClickTimeMs) {
++    return;
++  }
++
++  // if they moved assume it was a drag action
++  const position = getCanvasRelativePosition(event);
++  const moveDeltaSq = (startPosition.x - position.x) ** 2 +
++                      (startPosition.y - position.y) ** 2;
++  if (moveDeltaSq &gt; maxMoveDeltaSq) {
++    return;
++  }
+
+-  const position = {x: event.clientX, y: event.clientY};
+  const id = pickHelper.pick(position, pickingScene, camera);
+  if (id &gt; 0) {
+    const countryInfo = countryInfos[id - 1];
+    const selected = !countryInfo.selected;
+    if (selected &amp;&amp; !event.shiftKey &amp;&amp; !event.ctrlKey &amp;&amp; !event.metaKey) {
+      unselectAllCountries();
+    }
+    numCountriesSelected += selected ? 1 : -1;
+    countryInfo.selected = selected;
+    setPaletteColor(id, selected ? selectedColor : unselectedColor);
+    paletteTexture.needsUpdate = true;
+  } else if (numCountriesSelected) {
+    unselectAllCountries();
+  }
+  requestRenderIfNotRequested();
+}
+
+function unselectAllCountries() {
+  numCountriesSelected = 0;
+  countryInfos.forEach((countryInfo) =&gt; {
+    countryInfo.selected = false;
+  });
+  resetPalette();
+}
+
++canvas.addEventListener('pointerdown', recordStartTimeAndPosition);
+canvas.addEventListener('pointerup', pickCountry);
+</pre>
+<p>and with those changes it <em>seems</em> like it works to me.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/indexed-textures-picking-debounced.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/indexed-textures-picking-debounced.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>I'm not a UX expert so I'd love to hear if there is a better
+solution.</p>
+<p>I hope that gave you some idea of how indexed graphics can be useful
+and how you can modify the shaders three.js makes to add simple features.
+How to use GLSL, the language the shaders are written in, is too much for
+this article. There are a few links to some info in
+<a href="post-processing.html">the article on post processing</a>.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 515 - 0
manual/en/lights.html

@@ -0,0 +1,515 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Lights</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Lights">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Lights</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>This article is part of a series of articles about three.js. The
+first article is <a href="fundamentals.html">three.js fundamentals</a>. If
+you haven't read that yet and you're new to three.js you might want to
+consider starting there and also the article on <a href="setup.html">setting up your environment</a>. The
+<a href="textures.html">previous article was about textures</a>.</p>
+<p>Let's go over how to use the various kinds of lights in three.</p>
+<p>Starting with one of our previous samples let's update the camera.
+We'll set the field of view to 45 degrees, the far plane to 100 units,
+and we'll move the camera 10 units up and 20 units back from the origin</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">*const fov = 45;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+*const far = 100;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
++camera.position.set(0, 10, 20);
+</pre>
+<p>Next let's add <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>. <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> let the user spin
+or <em>orbit</em> the camera around some point. The <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> are
+an optional feature of three.js so first we need to include them
+in our page</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
++import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
+</pre>
+<p>Then we can use them. We pass the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> a camera to
+control and the DOM element to use to get input events</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const controls = new OrbitControls(camera, canvas);
+controls.target.set(0, 5, 0);
+controls.update();
+</pre>
+<p>We also set the target to orbit around to 5 units above the origin
+and then call <code class="notranslate" translate="no">controls.update</code> so the controls will use the new
+target.</p>
+<p>Next up let's make some things to light up. First we'll make ground
+plane. We'll apply a tiny 2x2 pixel checkerboard texture that looks
+like this</p>
+<div class="threejs_center">
+  <img src="../examples/resources/images/checker.png" class="border" style="
+    image-rendering: pixelated;
+    width: 128px;
+  ">
+</div>
+
+<p>First we load the texture, set it to repeating, set the filtering to
+nearest, and set how many times we want it to repeat. Since the
+texture is a 2x2 pixel checkerboard, by repeating and setting the
+repeat to half the size of the plane each check on the checkerboard
+will be exactly 1 unit large;</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const planeSize = 40;
+
+const loader = new THREE.TextureLoader();
+const texture = loader.load('resources/images/checker.png');
+texture.wrapS = THREE.RepeatWrapping;
+texture.wrapT = THREE.RepeatWrapping;
+texture.magFilter = THREE.NearestFilter;
+const repeats = planeSize / 2;
+texture.repeat.set(repeats, repeats);
+</pre>
+<p>We then make a plane geometry, a material for the plane, and a mesh
+to insert it in the scene. Planes default to being in the XY plane
+but the ground is in the XZ plane so we rotate it.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
+const planeMat = new THREE.MeshPhongMaterial({
+  map: texture,
+  side: THREE.DoubleSide,
+});
+const mesh = new THREE.Mesh(planeGeo, planeMat);
+mesh.rotation.x = Math.PI * -.5;
+scene.add(mesh);
+</pre>
+<p>Let's add a cube and a sphere so we have 3 things to light including the plane</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const cubeSize = 4;
+  const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
+  const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'});
+  const mesh = new THREE.Mesh(cubeGeo, cubeMat);
+  mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
+  scene.add(mesh);
+}
+{
+  const sphereRadius = 3;
+  const sphereWidthDivisions = 32;
+  const sphereHeightDivisions = 16;
+  const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
+  const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'});
+  const mesh = new THREE.Mesh(sphereGeo, sphereMat);
+  mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
+  scene.add(mesh);
+}
+</pre>
+<p>Now that we have a scene to light up let's add lights!</p>
+<h2 id="-ambientlight-"><a href="/docs/#api/en/lights/AmbientLight"><code class="notranslate" translate="no">AmbientLight</code></a></h2>
+<p>First let's make an <a href="/docs/#api/en/lights/AmbientLight"><code class="notranslate" translate="no">AmbientLight</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const color = 0xFFFFFF;
+const intensity = 1;
+const light = new THREE.AmbientLight(color, intensity);
+scene.add(light);
+</pre>
+<p>Let's also make it so we can adjust the light's parameters.
+We'll use <a href="https://github.com/georgealways/lil-gui">lil-gui</a> again.
+To be able to adjust the color via lil-gui we need a small helper
+that presents a property to lil-gui that looks like a CSS hex color string
+(eg: <code class="notranslate" translate="no">#FF8844</code>). Our helper will get the color from a named property,
+convert it to a hex string to offer to lil-gui. When lil-gui tries
+to set the helper's property we'll assign the result back to the light's
+color.</p>
+<p>Here's the helper:</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class ColorGUIHelper {
+  constructor(object, prop) {
+    this.object = object;
+    this.prop = prop;
+  }
+  get value() {
+    return `#${this.object[this.prop].getHexString()}`;
+  }
+  set value(hexString) {
+    this.object[this.prop].set(hexString);
+  }
+}
+</pre>
+<p>And here's our code setting up lil-gui</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'intensity', 0, 2, 0.01);
+</pre>
+<p>And here's the result</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-ambient.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-ambient.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Click and drag in the scene to <em>orbit</em> the camera.</p>
+<p>Notice there is no definition. The shapes are flat. The <a href="/docs/#api/en/lights/AmbientLight"><code class="notranslate" translate="no">AmbientLight</code></a> effectively
+just multiplies the material's color by the light's color times the
+intensity.</p>
+<pre class="prettyprint showlinemods notranslate notranslate" translate="no">color = materialColor * light.color * light.intensity;
+</pre><p>That's it. It has no direction.
+This style of ambient lighting is actually not all that
+useful as lighting as it's 100% even so other than changing the color
+of everything in the scene it doesn't look much like <em>lighting</em>.
+What it does help with is making the darks not too dark.</p>
+<h2 id="-hemispherelight-"><a href="/docs/#api/en/lights/HemisphereLight"><code class="notranslate" translate="no">HemisphereLight</code></a></h2>
+<p>Let's switch the code to a <a href="/docs/#api/en/lights/HemisphereLight"><code class="notranslate" translate="no">HemisphereLight</code></a>. A <a href="/docs/#api/en/lights/HemisphereLight"><code class="notranslate" translate="no">HemisphereLight</code></a>
+takes a sky color and a ground color and just multiplies the
+material's color between those 2 colors—the sky color if the
+surface of the object is pointing up and the ground color if
+the surface of the object is pointing down.</p>
+<p>Here's the new code</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const color = 0xFFFFFF;
++const skyColor = 0xB1E1FF;  // light blue
++const groundColor = 0xB97A20;  // brownish orange
+const intensity = 1;
+-const light = new THREE.AmbientLight(color, intensity);
++const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
+scene.add(light);
+</pre>
+<p>Let's also update the lil-gui code to edit both colors</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+-gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
++gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('skyColor');
++gui.addColor(new ColorGUIHelper(light, 'groundColor'), 'value').name('groundColor');
+gui.add(light, 'intensity', 0, 2, 0.01);
+</pre>
+<p>The result:</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-hemisphere.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-hemisphere.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Notice again there is almost no definition, everything looks kind
+of flat. The <a href="/docs/#api/en/lights/HemisphereLight"><code class="notranslate" translate="no">HemisphereLight</code></a> used in combination with another light
+can help give a nice kind of influence of the color of the sky
+and ground. In that way it's best used in combination with some
+other light or a substitute for an <a href="/docs/#api/en/lights/AmbientLight"><code class="notranslate" translate="no">AmbientLight</code></a>.</p>
+<h2 id="-directionallight-"><a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a></h2>
+<p>Let's switch the code to a <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a>.
+A <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> is often used to represent the sun.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const color = 0xFFFFFF;
+const intensity = 1;
+const light = new THREE.DirectionalLight(color, intensity);
+light.position.set(0, 10, 0);
+light.target.position.set(-5, 0, 0);
+scene.add(light);
+scene.add(light.target);
+</pre>
+<p>Notice that we had to add the <code class="notranslate" translate="no">light</code> and the <code class="notranslate" translate="no">light.target</code>
+to the scene. A three.js <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> will shine
+in the direction of its target.</p>
+<p>Let's make it so we can move the target by adding it to
+our GUI.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'intensity', 0, 2, 0.01);
+gui.add(light.target.position, 'x', -10, 10);
+gui.add(light.target.position, 'z', -10, 10);
+gui.add(light.target.position, 'y', 0, 10);
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-directional.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-directional.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>It's kind of hard to see what's going on. Three.js has a bunch
+of helper objects we can add to our scene to help visualize
+invisible parts of a scene. In this case we'll use the
+<a href="/docs/#api/en/helpers/DirectionalLightHelper"><code class="notranslate" translate="no">DirectionalLightHelper</code></a> which will draw a plane, to represent
+the light, and a line from the light to the target. We just
+pass it the light and add it to the scene.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const helper = new THREE.DirectionalLightHelper(light);
+scene.add(helper);
+</pre>
+<p>While we're at it let's make it so we can set both the position
+of the light and the target. To do this we'll make a function
+that given a <a href="/docs/#api/en/math/Vector3"><code class="notranslate" translate="no">Vector3</code></a> will adjust its <code class="notranslate" translate="no">x</code>, <code class="notranslate" translate="no">y</code>, and <code class="notranslate" translate="no">z</code> properties
+using <code class="notranslate" translate="no">lil-gui</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeXYZGUI(gui, vector3, name, onChangeFn) {
+  const folder = gui.addFolder(name);
+  folder.add(vector3, 'x', -10, 10).onChange(onChangeFn);
+  folder.add(vector3, 'y', 0, 10).onChange(onChangeFn);
+  folder.add(vector3, 'z', -10, 10).onChange(onChangeFn);
+  folder.open();
+}
+</pre>
+<p>Note that we need to call the helper's <code class="notranslate" translate="no">update</code> function
+anytime we change something so the helper knows to update
+itself. As such we pass in an <code class="notranslate" translate="no">onChangeFn</code> function to
+get called anytime lil-gui updates a value.</p>
+<p>Then we can use that for both the light's position
+and the target's position like this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function updateLight() {
++  light.target.updateMatrixWorld();
++  helper.update();
++}
++updateLight();
+
+const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'intensity', 0, 2, 0.01);
+
++makeXYZGUI(gui, light.position, 'position', updateLight);
++makeXYZGUI(gui, light.target.position, 'target', updateLight);
+</pre>
+<p>Now we can move the light, and its target</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-directional-w-helper.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-directional-w-helper.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Orbit the camera and it gets easier to see. The plane
+represents a <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> because a directional
+light computes light coming in one direction. There is no
+<em>point</em> the light comes from, it's an infinite plane of light
+shooting out parallel rays of light.</p>
+<h2 id="-pointlight-"><a href="/docs/#api/en/lights/PointLight"><code class="notranslate" translate="no">PointLight</code></a></h2>
+<p>A <a href="/docs/#api/en/lights/PointLight"><code class="notranslate" translate="no">PointLight</code></a> is a light that sits at a point and shoots light
+in all directions from that point. Let's change the code.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const color = 0xFFFFFF;
+const intensity = 1;
+-const light = new THREE.DirectionalLight(color, intensity);
++const light = new THREE.PointLight(color, intensity);
+light.position.set(0, 10, 0);
+-light.target.position.set(-5, 0, 0);
+scene.add(light);
+-scene.add(light.target);
+</pre>
+<p>Let's also switch to a <a href="/docs/#api/en/helpers/PointLightHelper"><code class="notranslate" translate="no">PointLightHelper</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const helper = new THREE.DirectionalLightHelper(light);
++const helper = new THREE.PointLightHelper(light);
+scene.add(helper);
+</pre>
+<p>and as there is no target the <code class="notranslate" translate="no">onChange</code> function can be simpler.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function updateLight() {
+-  light.target.updateMatrixWorld();
+  helper.update();
+}
+-updateLight();
+</pre>
+<p>Note that at some level a <a href="/docs/#api/en/helpers/PointLightHelper"><code class="notranslate" translate="no">PointLightHelper</code></a> has no um, point.
+It just draws a small wireframe diamond. It could just as easily
+be any shape you want, just add a mesh to the light itself.</p>
+<p>A <a href="/docs/#api/en/lights/PointLight"><code class="notranslate" translate="no">PointLight</code></a> has the added property of <a href="/docs/#api/en/lights/PointLight#distance"><code class="notranslate" translate="no">distance</code></a>.
+If the <code class="notranslate" translate="no">distance</code> is 0 then the <a href="/docs/#api/en/lights/PointLight"><code class="notranslate" translate="no">PointLight</code></a> shines to
+infinity. If the <code class="notranslate" translate="no">distance</code> is greater than 0 then the light shines
+its full intensity at the light and fades to no influence at <code class="notranslate" translate="no">distance</code>
+units away from the light.</p>
+<p>Let's setup the GUI so we can adjust the distance.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'intensity', 0, 2, 0.01);
++gui.add(light, 'distance', 0, 40).onChange(updateLight);
+
+makeXYZGUI(gui, light.position, 'position', updateLight);
+-makeXYZGUI(gui, light.target.position, 'target', updateLight);
+</pre>
+<p>And now try it out.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-point.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-point.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Notice when <code class="notranslate" translate="no">distance</code> is &gt; 0 how the light fades out.</p>
+<h2 id="-spotlight-"><a href="/docs/#api/en/lights/SpotLight"><code class="notranslate" translate="no">SpotLight</code></a></h2>
+<p>Spotlights are effectively a point light with a cone
+attached where the light only shines inside the cone.
+There's actually 2 cones. An outer cone and an inner
+cone. Between the inner cone and the outer cone the
+light fades from full intensity to zero.</p>
+<p>To use a <a href="/docs/#api/en/lights/SpotLight"><code class="notranslate" translate="no">SpotLight</code></a> we need a target just like
+the directional light. The light's cone will
+open toward the target.</p>
+<p>Modifying our <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> with helper from above</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const color = 0xFFFFFF;
+const intensity = 1;
+-const light = new THREE.DirectionalLight(color, intensity);
++const light = new THREE.SpotLight(color, intensity);
+scene.add(light);
+scene.add(light.target);
+
+-const helper = new THREE.DirectionalLightHelper(light);
++const helper = new THREE.SpotLightHelper(light);
+scene.add(helper);
+</pre>
+<p>The spotlight's cone's angle is set with the <a href="/docs/#api/en/lights/SpotLight#angle"><code class="notranslate" translate="no">angle</code></a>
+property in radians. We'll use our <code class="notranslate" translate="no">DegRadHelper</code> from the
+<a href="textures.html">texture article</a> to present a UI in
+degrees.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">gui.add(new DegRadHelper(light, 'angle'), 'value', 0, 90).name('angle').onChange(updateLight);
+</pre>
+<p>The inner cone is defined by setting the <a href="/docs/#api/en/lights/SpotLight#penumbra"><code class="notranslate" translate="no">penumbra</code></a> property
+as a percentage from the outer cone. In other words when <code class="notranslate" translate="no">penumbra</code> is 0 then the
+inner cone is the same size (0 = no difference) from the outer cone. When the
+<code class="notranslate" translate="no">penumbra</code> is 1 then the light fades starting in the center of the cone to the
+outer cone. When <code class="notranslate" translate="no">penumbra</code> is .5 then the light fades starting from 50% between
+the center of the outer cone.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">gui.add(light, 'penumbra', 0, 1, 0.01);
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-spot-w-helper.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-spot-w-helper.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Notice with the default <code class="notranslate" translate="no">penumbra</code> of 0 the spotlight has a very sharp edge
+whereas as you adjust the <code class="notranslate" translate="no">penumbra</code> toward 1 the edge blurs.</p>
+<p>It might be hard to see the <em>cone</em> of the spotlight. The reason is it's
+below the ground. Shorten the distance to around 5 and you'll see the open
+end of the cone.</p>
+<h2 id="-rectarealight-"><a href="/docs/#api/en/lights/RectAreaLight"><code class="notranslate" translate="no">RectAreaLight</code></a></h2>
+<p>There's one more type of light, the <a href="/docs/#api/en/lights/RectAreaLight"><code class="notranslate" translate="no">RectAreaLight</code></a>, which represents
+exactly what it sounds like, a rectangular area of light like a long
+fluorescent light or maybe a frosted sky light in a ceiling.</p>
+<p>The <a href="/docs/#api/en/lights/RectAreaLight"><code class="notranslate" translate="no">RectAreaLight</code></a> only works with the <a href="/docs/#api/en/materials/MeshStandardMaterial"><code class="notranslate" translate="no">MeshStandardMaterial</code></a> and the
+<a href="/docs/#api/en/materials/MeshPhysicalMaterial"><code class="notranslate" translate="no">MeshPhysicalMaterial</code></a> so let's change all our materials to <a href="/docs/#api/en/materials/MeshStandardMaterial"><code class="notranslate" translate="no">MeshStandardMaterial</code></a></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">  ...
+
+  const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
+-  const planeMat = new THREE.MeshPhongMaterial({
++  const planeMat = new THREE.MeshStandardMaterial({
+    map: texture,
+    side: THREE.DoubleSide,
+  });
+  const mesh = new THREE.Mesh(planeGeo, planeMat);
+  mesh.rotation.x = Math.PI * -.5;
+  scene.add(mesh);
+}
+{
+  const cubeSize = 4;
+  const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
+- const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'});
++ const cubeMat = new THREE.MeshStandardMaterial({color: '#8AC'});
+  const mesh = new THREE.Mesh(cubeGeo, cubeMat);
+  mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
+  scene.add(mesh);
+}
+{
+  const sphereRadius = 3;
+  const sphereWidthDivisions = 32;
+  const sphereHeightDivisions = 16;
+  const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
+-  const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'});
++ const sphereMat = new THREE.MeshStandardMaterial({color: '#CA8'});
+  const mesh = new THREE.Mesh(sphereGeo, sphereMat);
+  mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
+  scene.add(mesh);
+}
+</pre>
+<p>To use the <a href="/docs/#api/en/lights/RectAreaLight"><code class="notranslate" translate="no">RectAreaLight</code></a> we need to include some extra three.js optional data and we'll
+include the <a href="/docs/#api/en/helpers/RectAreaLightHelper"><code class="notranslate" translate="no">RectAreaLightHelper</code></a> to help us visualize the light</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
++import {RectAreaLightUniformsLib} from '/examples/jsm/lights/RectAreaLightUniformsLib.js';
++import {RectAreaLightHelper} from '/examples/jsm/helpers/RectAreaLightHelper.js';
+</pre>
+<p>and we need to call <code class="notranslate" translate="no">RectAreaLightUniformsLib.init</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas});
++  RectAreaLightUniformsLib.init();
+</pre>
+<p>If you forget the data the light will still work but it will look funny so
+be sure to remember to include the extra data.</p>
+<p>Now we can create the light</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const color = 0xFFFFFF;
+*const intensity = 5;
++const width = 12;
++const height = 4;
+*const light = new THREE.RectAreaLight(color, intensity, width, height);
+light.position.set(0, 10, 0);
++light.rotation.x = THREE.MathUtils.degToRad(-90);
+scene.add(light);
+
+*const helper = new RectAreaLightHelper(light);
+*light.add(helper);
+</pre>
+<p>One thing to notice is that unlike the <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> and the <a href="/docs/#api/en/lights/SpotLight"><code class="notranslate" translate="no">SpotLight</code></a>, the
+<a href="/docs/#api/en/lights/RectAreaLight"><code class="notranslate" translate="no">RectAreaLight</code></a> does not use a target. It just uses its rotation. Another thing
+to notice is the helper needs to be a child of the light. It is not a child of the
+scene like other helpers.</p>
+<p>Let's also adjust the GUI. We'll make it so we can rotate the light and adjust
+its <code class="notranslate" translate="no">width</code> and <code class="notranslate" translate="no">height</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'intensity', 0, 10, 0.01);
+gui.add(light, 'width', 0, 20);
+gui.add(light, 'height', 0, 20);
+gui.add(new DegRadHelper(light.rotation, 'x'), 'value', -180, 180).name('x rotation');
+gui.add(new DegRadHelper(light.rotation, 'y'), 'value', -180, 180).name('y rotation');
+gui.add(new DegRadHelper(light.rotation, 'z'), 'value', -180, 180).name('z rotation');
+
+makeXYZGUI(gui, light.position, 'position');
+</pre>
+<p>And here is that.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-rectarea.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-rectarea.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>One thing we didn't cover is that there is a setting on the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>
+called <code class="notranslate" translate="no">physicallyCorrectLights</code>. It effects how light falls off as distance from light.
+It only affects <a href="/docs/#api/en/lights/PointLight"><code class="notranslate" translate="no">PointLight</code></a> and <a href="/docs/#api/en/lights/SpotLight"><code class="notranslate" translate="no">SpotLight</code></a>. <a href="/docs/#api/en/lights/RectAreaLight"><code class="notranslate" translate="no">RectAreaLight</code></a> does this automatically.</p>
+<p>For lights though the basic idea is you don't set a distance for them to fade out,
+and you don't set <code class="notranslate" translate="no">intensity</code>. Instead you set the <a href="/docs/#api/en/lights/PointLight#power"><code class="notranslate" translate="no">power</code></a> of
+the light in lumens and then three.js will use physics calculations like real lights.
+The units of three.js in this case are meters and a 60w light bulb would have
+around 800 lumens. There's also a <a href="/docs/#api/en/lights/PointLight#decay"><code class="notranslate" translate="no">decay</code></a> property. It should
+be set to <code class="notranslate" translate="no">2</code> for realistic decay.</p>
+<p>Let's test that.</p>
+<p>First we'll turn on physically correct lights</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const renderer = new THREE.WebGLRenderer({canvas});
++renderer.physicallyCorrectLights = true;
+</pre>
+<p>Then we'll set the <code class="notranslate" translate="no">power</code> to 800 lumens, the <code class="notranslate" translate="no">decay</code> to 2, and
+the <code class="notranslate" translate="no">distance</code> to <code class="notranslate" translate="no">Infinity</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const color = 0xFFFFFF;
+const intensity = 1;
+const light = new THREE.PointLight(color, intensity);
+light.power = 800;
+light.decay = 2;
+light.distance = Infinity;
+</pre>
+<p>and we'll add gui so we can change the <code class="notranslate" translate="no">power</code> and <code class="notranslate" translate="no">decay</code></p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'decay', 0, 4, 0.01);
+gui.add(light, 'power', 0, 2000);
+</pre>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lights-point-physically-correct.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/lights-point-physically-correct.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>It's important to note each light you add to the scene slows down how fast
+three.js renders the scene so you should always try to use as few as
+possible to achieve your goals.</p>
+<p>Next up let's go over <a href="cameras.html">dealing with cameras</a>.</p>
+<p><canvas id="c"></canvas></p>
+<script type="module" src="../resources/threejs-lights.js"></script>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

+ 699 - 0
manual/en/load-gltf.html

@@ -0,0 +1,699 @@
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="utf-8">
+    <title>Loading a .GLTF File</title>
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <meta name="twitter:card" content="summary_large_image">
+    <meta name="twitter:site" content="@threejs">
+    <meta name="twitter:title" content="Three.js – Loading a .GLTF File">
+    <meta property="og:image" content="https://threejs.org/files/share.png">
+    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
+    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
+
+    <link rel="stylesheet" href="/manual/resources/lesson.css">
+    <link rel="stylesheet" href="/manual/resources/lang.css">
+  </head>
+  <body>
+    <div class="container">
+      <div class="lesson-title">
+        <h1>Loading a .GLTF File</h1>
+      </div>
+      <div class="lesson">
+        <div class="lesson-main">
+          <p>In a previous lesson we <a href="load-obj.html">loaded an .OBJ file</a>. If
+you haven't read it you might want to check it out first.</p>
+<p>As pointed out over there the .OBJ file format is very old and fairly
+simple. It provides no scene graph so everything loaded is one large
+mesh. It was designed mostly as a simple way to pass data between
+3D editors.</p>
+<p><a href="https://github.com/KhronosGroup/glTF">The gLTF format</a> is actually
+a format designed from the ground up for be used for displaying
+graphics. 3D formats can be divided into 3 or 4 basic types.</p>
+<ul>
+<li><p>3D Editor Formats</p>
+<p>This are formats specific to a single app. .blend (Blender), .max (3d Studio Max),
+.mb and .ma (Maya), etc...</p>
+</li>
+<li><p>Exchange formats</p>
+<p>These are formats like .OBJ, .DAE (Collada), .FBX. They are designed to help exchange
+information between 3D editors. As such they are usually much larger than needed with
+extra info used only inside 3d editors</p>
+</li>
+<li><p>App formats</p>
+<p>These are usually specific to certain apps, usually games.</p>
+</li>
+<li><p>Transmission formats</p>
+<p>gLTF might be the first true transmission format. I suppose VRML might be considered
+one but VRML was actually a pretty poor format.</p>
+<p>gLTF is designed to do some things well that all those other formats don't do</p>
+<ol>
+<li><p>Be small for transmission</p>
+<p>For example this means much of their large data, like vertices, is stored in
+binary. When you download a .gLTF file that data can be uploaded to the GPU
+with zero processing. It's ready as is. This is in contrast to say VRML, .OBJ,
+or .DAE where vertices are stored as text and have to be parsed. Text vertex
+positions can easily be 3x to 5x larger than binary.</p>
+</li>
+<li><p>Be ready to render</p>
+<p>This again is different from other formats except maybe App formats. The data
+in a glTF file is mean to be rendered, not edited. Data that's not important to
+rendering has generally been removed. Polygons have been converted to triangles.
+Materials have known values that are supposed to work everywhere.</p>
+</li>
+</ol>
+</li>
+</ul>
+<p>gLTF was specifically designed so you should be able to download a glTF file and
+display it with a minimum of trouble. Let's cross our fingers that's truly the case
+as none of the other formats have been able to do this.</p>
+<p>I wasn't really sure what I should show. At some level loading and displaying a gLTF file
+is simpler than an .OBJ file. Unlike a .OBJ file materials are directly part of the format.
+That said I thought I should at least load one up and I think going over the issues I ran 
+into might provide some good info.</p>
+<p>Searching the net I found <a href="https://sketchfab.com/models/edd1c604e1e045a0a2a552ddd9a293e6">this low-poly city</a>
+by <a href="https://sketchfab.com/antonmoek">antonmoek</a> which seemed like if we're lucky
+might make a good example.</p>
+<div class="threejs_center"><img src="../resources/images/cartoon_lowpoly_small_city_free_pack.jpg"></div>
+
+<p>Starting with <a href="load-obj.html">an example from the .OBJ article</a> I removed the code
+for loading .OBJ and replaced it with code for loading .GLTF</p>
+<p>The old .OBJ code was</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mtlLoader = new MTLLoader();
+mtlLoader.loadMtl('resources/models/windmill/windmill-fixed.mtl', (mtl) =&gt; {
+  mtl.preload();
+  mtl.materials.Material.side = THREE.DoubleSide;
+  objLoader.setMaterials(mtl);
+  objLoader.load('resources/models/windmill/windmill.obj', (event) =&gt; {
+    const root = event.detail.loaderRootNode;
+    scene.add(root);
+    ...
+  });
+});
+</pre>
+<p>The new .GLTF code is</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const gltfLoader = new GLTFLoader();
+  const url = 'resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf';
+  gltfLoader.load(url, (gltf) =&gt; {
+    const root = gltf.scene;
+    scene.add(root);
+    ...
+  });
+</pre>
+<p>I kept the auto framing code as before</p>
+<p>We also need to include the <a href="/docs/#examples/loaders/GLTFLoader"><code class="notranslate" translate="no">GLTFLoader</code></a> and we can get rid of the <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>.</p>
+<pre class="prettyprint showlinemods notranslate lang-html" translate="no">-import {LoaderSupport} from '/examples/jsm/loaders/LoaderSupport.js';
+-import {OBJLoader} from '/examples/jsm/loaders/OBJLoader.js';
+-import {MTLLoader} from '/examples/jsm/loaders/MTLLoader.js';
++import {GLTFLoader} from '/examples/jsm/loaders/GLTFLoader.js';
+</pre>
+<p>And running that we get</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/load-gltf.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Magic! It just works, textures and all.</p>
+<p>Next I wanted to see if I could animate the cars driving around so
+I needed to check if the scene had the cars as separate entities
+and if they were setup in a way I could use them.</p>
+<p>I wrote some code to dump put the scenegraph to the <a href="debugging-javascript.html">JavaScript
+console</a>.</p>
+<p>Here's the code to print out the scenegraph.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function dumpObject(obj, lines = [], isLast = true, prefix = '') {
+  const localPrefix = isLast ? '└─' : '├─';
+  lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
+  const newPrefix = prefix + (isLast ? '  ' : '│ ');
+  const lastNdx = obj.children.length - 1;
+  obj.children.forEach((child, ndx) =&gt; {
+    const isLast = ndx === lastNdx;
+    dumpObject(child, lines, isLast, newPrefix);
+  });
+  return lines;
+}
+</pre>
+<p>And I just called it right after loading the scene.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gltfLoader = new GLTFLoader();
+gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
+  const root = gltf.scene;
+  scene.add(root);
+  console.log(dumpObject(root).join('\n'));
+</pre>
+<p><a href="../examples/load-gltf-dump-scenegraph.html">Running that</a> I got this listing</p>
+<pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
+  └─RootNode_(gltf_orientation_matrix) [Object3D]
+    └─RootNode_(model_correction_matrix) [Object3D]
+      └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
+        └─RootNode [Object3D]
+          │ ...
+          ├─Cars [Object3D]
+          │ ├─CAR_03_1 [Object3D]
+          │ │ └─CAR_03_1_World_ap_0 [Mesh]
+          │ ├─CAR_03 [Object3D]
+          │ │ └─CAR_03_World_ap_0 [Mesh]
+          │ ├─Car_04 [Object3D]
+          │ │ └─Car_04_World_ap_0 [Mesh]
+          │ ├─CAR_03_2 [Object3D]
+          │ │ └─CAR_03_2_World_ap_0 [Mesh]
+          │ ├─Car_04_1 [Object3D]
+          │ │ └─Car_04_1_World_ap_0 [Mesh]
+          │ ├─Car_04_2 [Object3D]
+          │ │ └─Car_04_2_World_ap_0 [Mesh]
+          │ ├─Car_04_3 [Object3D]
+          │ │ └─Car_04_3_World_ap_0 [Mesh]
+          │ ├─Car_04_4 [Object3D]
+          │ │ └─Car_04_4_World_ap_0 [Mesh]
+          │ ├─Car_08_4 [Object3D]
+          │ │ └─Car_08_4_World_ap8_0 [Mesh]
+          │ ├─Car_08_3 [Object3D]
+          │ │ └─Car_08_3_World_ap9_0 [Mesh]
+          │ ├─Car_04_1_2 [Object3D]
+          │ │ └─Car_04_1_2_World_ap_0 [Mesh]
+          │ ├─Car_08_2 [Object3D]
+          │ │ └─Car_08_2_World_ap11_0 [Mesh]
+          │ ├─CAR_03_1_2 [Object3D]
+          │ │ └─CAR_03_1_2_World_ap_0 [Mesh]
+          │ ├─CAR_03_2_2 [Object3D]
+          │ │ └─CAR_03_2_2_World_ap_0 [Mesh]
+          │ ├─Car_04_2_2 [Object3D]
+          │ │ └─Car_04_2_2_World_ap_0 [Mesh]
+          ...
+</pre>
+<p>From that we can see all the cars happen to be under a parent
+called <code class="notranslate" translate="no">"Cars"</code></p>
+<pre class="prettyprint showlinemods notranslate lang-text" translate="no">*          ├─Cars [Object3D]
+          │ ├─CAR_03_1 [Object3D]
+          │ │ └─CAR_03_1_World_ap_0 [Mesh]
+          │ ├─CAR_03 [Object3D]
+          │ │ └─CAR_03_World_ap_0 [Mesh]
+          │ ├─Car_04 [Object3D]
+          │ │ └─Car_04_World_ap_0 [Mesh]
+</pre>
+<p>So as a simple test I thought I would just try rotating
+all the children of the "Cars" node around their Y axis.</p>
+<p>I looked up the "Cars" node after loading the scene
+and saved the result.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let cars;
+{
+  const gltfLoader = new GLTFLoader();
+  gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
+    const root = gltf.scene;
+    scene.add(root);
++    cars = root.getObjectByName('Cars');
+</pre>
+<p>Then in the <code class="notranslate" translate="no">render</code> function we can just set the rotation
+of each child of <code class="notranslate" translate="no">cars</code>.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function render(time) {
++  time *= 0.001;  // convert to seconds
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+  }
+
++  if (cars) {
++    for (const car of cars.children) {
++      car.rotation.y = time;
++    }
++  }
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+</pre>
+<p>And we get</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-rotate-cars.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/load-gltf-rotate-cars.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Hmmm, it looks like unfortunately this scene wasn't designed to
+animate the cars as their origins are not setup for that purpose.
+The trucks are rotating in the wrong direction.</p>
+<p>This brings up an important point which is if you're going to
+do something in 3D you need to plan ahead and design your assets
+so they have their origins in the correct places, so they are
+the correct scale, etc.</p>
+<p>Since I'm not an artist and I don't know blender that well I
+will hack this example. We'll take each car and parent it to
+another <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>. We will then move those <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> objects
+to move the cars but separately we can set the car's original
+<a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> to re-orient it so it's about where we really need it.</p>
+<p>Looking back at the scene graph listing it looks like there
+are really only 3 types of cars, "Car_08", "CAR_03", and "Car_04".
+Hopefully each type of car will work with the same adjustments.</p>
+<p>I wrote this code to go through each car, parent it to a new
+<a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>, parent that new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> to the scene, and apply
+some per car <em>type</em> settings to fix its orientation, and add
+the new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> a <code class="notranslate" translate="no">cars</code> array.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-let cars;
++const cars = [];
+{
+  const gltfLoader = new GLTFLoader();
+  gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
+    const root = gltf.scene;
+    scene.add(root);
+
+-    cars = root.getObjectByName('Cars');
++    const loadedCars = root.getObjectByName('Cars');
++    const fixes = [
++      { prefix: 'Car_08', rot: [Math.PI * .5, 0, Math.PI +* .5], },
++      { prefix: 'CAR_03', rot: [0, Math.PI, 0], },
++      { prefix: 'Car_04', rot: [0, Math.PI, 0], },
++    ];
++
++    root.updateMatrixWorld();
++    for (const car of loadedCars.children.slice()) {
++      const fix = fixes.find(fix =&gt; car.name.startsWith(fix.prefix));
++      const obj = new THREE.Object3D();
++      car.getWorldPosition(obj.position);
++      car.position.set(0, 0, 0);
++      car.rotation.set(...fix.rot);
++      obj.add(car);
++      scene.add(obj);
++      cars.push(obj);
++    }
+     ...
+</pre>
+<p>This fixes the orientation of the cars. </p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-rotate-cars-fixed.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/load-gltf-rotate-cars-fixed.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Now let's drive them around.</p>
+<p>Making even a simple driving system is too much for this post but
+it seems instead we could just make one convoluted path that
+drives down all the roads and then put the cars on the path.
+Here's a picture from Blender about half way through building
+the path.</p>
+<div class="threejs_center"><img src="../resources/images/making-path-for-cars.jpg" style="width: 1094px"></div>
+
+<p>I needed a way to get the data for that path out of Blender.
+Fortunately I was able to select just my path and export .OBJ checking "write nurbs".</p>
+<div class="threejs_center"><img src="../resources/images/blender-export-obj-write-nurbs.jpg" style="width: 498px"></div>
+
+<p>Opening the .OBJ file I was able to get a list of points
+which I formatted into this</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const controlPoints = [
+  [1.118281, 5.115846, -3.681386],
+  [3.948875, 5.115846, -3.641834],
+  [3.960072, 5.115846, -0.240352],
+  [3.985447, 5.115846, 4.585005],
+  [-3.793631, 5.115846, 4.585006],
+  [-3.826839, 5.115846, -14.736200],
+  [-14.542292, 5.115846, -14.765865],
+  [-14.520929, 5.115846, -3.627002],
+  [-5.452815, 5.115846, -3.634418],
+  [-5.467251, 5.115846, 4.549161],
+  [-13.266233, 5.115846, 4.567083],
+  [-13.250067, 5.115846, -13.499271],
+  [4.081842, 5.115846, -13.435463],
+  [4.125436, 5.115846, -5.334928],
+  [-14.521364, 5.115846, -5.239871],
+  [-14.510466, 5.115846, 5.486727],
+  [5.745666, 5.115846, 5.510492],
+  [5.787942, 5.115846, -14.728308],
+  [-5.423720, 5.115846, -14.761919],
+  [-5.373599, 5.115846, -3.704133],
+  [1.004861, 5.115846, -3.641834],
+];
+</pre>
+<p>THREE.js has some curve classes. The <a href="/docs/#api/en/extras/curves/CatmullRomCurve3"><code class="notranslate" translate="no">CatmullRomCurve3</code></a> seemed
+like it might work. The thing about that kind of curve is
+it tries to make a smooth curve going through the points.</p>
+<p>In fact putting those points in directly will generate
+a curve like this</p>
+<div class="threejs_center"><img src="../resources/images/car-curves-before.png" style="width: 400px"></div>
+
+<p>but we want a sharper corners. It seemed like if we computed
+some extra points we could get what we want. For each pair
+of points we'll compute a point 10% of the way between
+the 2 points and another 90% of the way between the 2 points
+and pass the result to <a href="/docs/#api/en/extras/curves/CatmullRomCurve3"><code class="notranslate" translate="no">CatmullRomCurve3</code></a>.</p>
+<p>This will give us a curve like this</p>
+<div class="threejs_center"><img src="../resources/images/car-curves-after.png" style="width: 400px"></div>
+
+<p>Here's the code to make the curve </p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">let curve;
+let curveObject;
+{
+  const controlPoints = [
+    [1.118281, 5.115846, -3.681386],
+    [3.948875, 5.115846, -3.641834],
+    [3.960072, 5.115846, -0.240352],
+    [3.985447, 5.115846, 4.585005],
+    [-3.793631, 5.115846, 4.585006],
+    [-3.826839, 5.115846, -14.736200],
+    [-14.542292, 5.115846, -14.765865],
+    [-14.520929, 5.115846, -3.627002],
+    [-5.452815, 5.115846, -3.634418],
+    [-5.467251, 5.115846, 4.549161],
+    [-13.266233, 5.115846, 4.567083],
+    [-13.250067, 5.115846, -13.499271],
+    [4.081842, 5.115846, -13.435463],
+    [4.125436, 5.115846, -5.334928],
+    [-14.521364, 5.115846, -5.239871],
+    [-14.510466, 5.115846, 5.486727],
+    [5.745666, 5.115846, 5.510492],
+    [5.787942, 5.115846, -14.728308],
+    [-5.423720, 5.115846, -14.761919],
+    [-5.373599, 5.115846, -3.704133],
+    [1.004861, 5.115846, -3.641834],
+  ];
+  const p0 = new THREE.Vector3();
+  const p1 = new THREE.Vector3();
+  curve = new THREE.CatmullRomCurve3(
+    controlPoints.map((p, ndx) =&gt; {
+      p0.set(...p);
+      p1.set(...controlPoints[(ndx + 1) % controlPoints.length]);
+      return [
+        (new THREE.Vector3()).copy(p0),
+        (new THREE.Vector3()).lerpVectors(p0, p1, 0.1),
+        (new THREE.Vector3()).lerpVectors(p0, p1, 0.9),
+      ];
+    }).flat(),
+    true,
+  );
+  {
+    const points = curve.getPoints(250);
+    const geometry = new THREE.BufferGeometry().setFromPoints(points);
+    const material = new THREE.LineBasicMaterial({color: 0xff0000});
+    curveObject = new THREE.Line(geometry, material);
+    scene.add(curveObject);
+  }
+}
+</pre>
+<p>The first part of that code makes a curve.
+The second part of that code generates 250 points
+from the curve and then creates an object to display
+the lines made by connecting those 250 points.</p>
+<p>Running <a href="../examples/load-gltf-car-path.html">the example</a> I didn't see
+the curve. To make it visible I made it ignore the depth test and
+render last</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">    curveObject = new THREE.Line(geometry, material);
++    material.depthTest = false;
++    curveObject.renderOrder = 1;
+</pre>
+<p>And that's when I discovered it was way too small.</p>
+<div class="threejs_center"><img src="../resources/images/car-curves-too-small.png" style="width: 498px"></div>
+
+<p>Checking the hierarchy in Blender I found out that the artist had
+scaled the node all the cars are parented to.</p>
+<div class="threejs_center"><img src="../resources/images/cars-scale-0.01.png" style="width: 342px;"></div>
+
+<p>Scaling is bad for real time 3D apps. It causes all kinds of
+issues and ends up being no end of frustration when doing
+real time 3D. Artists often don't know this because it's so
+easy to scale an entire scene in a 3D editing program but
+if you decide to make a real time 3D app I suggest you request your
+artists to never scale anything. If they change the scale
+they should find a way to apply that scale to the vertices
+so that when it ends up making it to your app you can ignore
+scale.</p>
+<p>And, not just scale, in this case the cars are rotated and offset
+by their parent, the <code class="notranslate" translate="no">Cars</code> node. This will make it hard at runtime
+to move the cars around in world space. To be clear, in this case
+we want cars to drive around in world space which is why these
+issues are coming up. If something that is meant to be manipulated
+in a local space, like the moon revolving around the earth this
+is less of an issue.</p>
+<p>Going back to the function we wrote above to dump the scene graph,
+let's dump the position, rotation, and scale of each node.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function dumpVec3(v3, precision = 3) {
++  return `${v3.x.toFixed(precision)}, ${v3.y.toFixed(precision)}, ${v3.z.toFixed(precision)}`;
++}
+
+function dumpObject(obj, lines, isLast = true, prefix = '') {
+  const localPrefix = isLast ? '└─' : '├─';
+  lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
++  const dataPrefix = obj.children.length
++     ? (isLast ? '  │ ' : '│ │ ')
++     : (isLast ? '    ' : '│   ');
++  lines.push(`${prefix}${dataPrefix}  pos: ${dumpVec3(obj.position)}`);
++  lines.push(`${prefix}${dataPrefix}  rot: ${dumpVec3(obj.rotation)}`);
++  lines.push(`${prefix}${dataPrefix}  scl: ${dumpVec3(obj.scale)}`);
+  const newPrefix = prefix + (isLast ? '  ' : '│ ');
+  const lastNdx = obj.children.length - 1;
+  obj.children.forEach((child, ndx) =&gt; {
+    const isLast = ndx === lastNdx;
+    dumpObject(child, lines, isLast, newPrefix);
+  });
+  return lines;
+}
+</pre>
+<p>And the result from <a href="../examples/load-gltf-dump-scenegraph-extra.html">running it</a></p>
+<pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
+  │   pos: 0.000, 0.000, 0.000
+  │   rot: 0.000, 0.000, 0.000
+  │   scl: 1.000, 1.000, 1.000
+  └─RootNode_(gltf_orientation_matrix) [Object3D]
+    │   pos: 0.000, 0.000, 0.000
+    │   rot: -1.571, 0.000, 0.000
+    │   scl: 1.000, 1.000, 1.000
+    └─RootNode_(model_correction_matrix) [Object3D]
+      │   pos: 0.000, 0.000, 0.000
+      │   rot: 0.000, 0.000, 0.000
+      │   scl: 1.000, 1.000, 1.000
+      └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
+        │   pos: 0.000, 0.000, 0.000
+        │   rot: 1.571, 0.000, 0.000
+        │   scl: 1.000, 1.000, 1.000
+        └─RootNode [Object3D]
+          │   pos: 0.000, 0.000, 0.000
+          │   rot: 0.000, 0.000, 0.000
+          │   scl: 1.000, 1.000, 1.000
+          ├─Cars [Object3D]
+*          │ │   pos: -369.069, -90.704, -920.159
+*          │ │   rot: 0.000, 0.000, 0.000
+*          │ │   scl: 1.000, 1.000, 1.000
+          │ ├─CAR_03_1 [Object3D]
+          │ │ │   pos: 22.131, 14.663, -475.071
+          │ │ │   rot: -3.142, 0.732, 3.142
+          │ │ │   scl: 1.500, 1.500, 1.500
+          │ │ └─CAR_03_1_World_ap_0 [Mesh]
+          │ │       pos: 0.000, 0.000, 0.000
+          │ │       rot: 0.000, 0.000, 0.000
+          │ │       scl: 1.000, 1.000, 1.000
+</pre>
+<p>This shows us that <code class="notranslate" translate="no">Cars</code> in the original scene has had its rotation and scale
+removed and applied to its children. That suggests either whatever exporter was
+used to create the .GLTF file did some special work here or more likely the
+artist exported a different version of the file than the corresponding .blend
+file, which is why things don't match.</p>
+<p>The moral of that is I should have probably downloaded the .blend
+file and exported myself. Before exporting I should have inspected
+all the major nodes and removed any transformations.</p>
+<p>All these nodes at the top</p>
+<pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
+  │   pos: 0.000, 0.000, 0.000
+  │   rot: 0.000, 0.000, 0.000
+  │   scl: 1.000, 1.000, 1.000
+  └─RootNode_(gltf_orientation_matrix) [Object3D]
+    │   pos: 0.000, 0.000, 0.000
+    │   rot: -1.571, 0.000, 0.000
+    │   scl: 1.000, 1.000, 1.000
+    └─RootNode_(model_correction_matrix) [Object3D]
+      │   pos: 0.000, 0.000, 0.000
+      │   rot: 0.000, 0.000, 0.000
+      │   scl: 1.000, 1.000, 1.000
+      └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
+        │   pos: 0.000, 0.000, 0.000
+        │   rot: 1.571, 0.000, 0.000
+        │   scl: 1.000, 1.000, 1.000
+</pre>
+<p>are also a waste.</p>
+<p>Ideally the scene would consist of a single "root" node with no position,
+rotation, or scale. At runtime I could then pull all the children out of that
+root and parent them to the scene itself. There might be children of the root
+like "Cars" which would help me find all the cars but ideally it would also have
+no translation, rotation, or scale so I could re-parent the cars to the scene
+with the minimal amount of work.</p>
+<p>In any case the quickest though maybe not the best fix is to just
+adjust the object we're using to view the curve.</p>
+<p>Here's what I ended up with.</p>
+<p>First I adjusted the position of the curve and found values
+that seemed to work. I then hid it.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const points = curve.getPoints(250);
+  const geometry = new THREE.BufferGeometry().setFromPoints(points);
+  const material = new THREE.LineBasicMaterial({color: 0xff0000});
+  curveObject = new THREE.Line(geometry, material);
++  curveObject.scale.set(100, 100, 100);
++  curveObject.position.y = -621;
++  curveObject.visible = false;
+  material.depthTest = false;
+  curveObject.renderOrder = 1;
+  scene.add(curveObject);
+}
+</pre>
+<p>Then I wrote code to move the cars along the curve. For each car we pick a
+position from 0 to 1 along the curve and compute a point in world space using
+the <code class="notranslate" translate="no">curveObject</code> to transform the point. We then pick another point slightly
+further down the curve. We set the car's orientation using <code class="notranslate" translate="no">lookAt</code> and put the
+car at the mid point between the 2 points.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// create 2 Vector3s we can use for path calculations
+const carPosition = new THREE.Vector3();
+const carTarget = new THREE.Vector3();
+
+function render(time) {
+  ...
+
+-  for (const car of cars) {
+-    car.rotation.y = time;
+-  }
+
++  {
++    const pathTime = time * .01;
++    const targetOffset = 0.01;
++    cars.forEach((car, ndx) =&gt; {
++      // a number between 0 and 1 to evenly space the cars
++      const u = pathTime + ndx / cars.length;
++
++      // get the first point
++      curve.getPointAt(u % 1, carPosition);
++      carPosition.applyMatrix4(curveObject.matrixWorld);
++
++      // get a second point slightly further down the curve
++      curve.getPointAt((u + targetOffset) % 1, carTarget);
++      carTarget.applyMatrix4(curveObject.matrixWorld);
++
++      // put the car at the first point (temporarily)
++      car.position.copy(carPosition);
++      // point the car the second point
++      car.lookAt(carTarget);
++
++      // put the car between the 2 points
++      car.position.lerpVectors(carPosition, carTarget, 0.5);
++    });
++  }
+</pre>
+<p>and when I ran it I found out for each type of car, their height above their origins
+are not consistently set and so I needed to offset each one
+a little.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loadedCars = root.getObjectByName('Cars');
+const fixes = [
+-  { prefix: 'Car_08', rot: [Math.PI * .5, 0, Math.PI * .5], },
+-  { prefix: 'CAR_03', rot: [0, Math.PI, 0], },
+-  { prefix: 'Car_04', rot: [0, Math.PI, 0], },
++  { prefix: 'Car_08', y: 0,  rot: [Math.PI * .5, 0, Math.PI * .5], },
++  { prefix: 'CAR_03', y: 33, rot: [0, Math.PI, 0], },
++  { prefix: 'Car_04', y: 40, rot: [0, Math.PI, 0], },
+];
+
+root.updateMatrixWorld();
+for (const car of loadedCars.children.slice()) {
+  const fix = fixes.find(fix =&gt; car.name.startsWith(fix.prefix));
+  const obj = new THREE.Object3D();
+  car.getWorldPosition(obj.position);
+-  car.position.set(0, 0, 0);
++  car.position.set(0, fix.y, 0);
+  car.rotation.set(...fix.rot);
+  obj.add(car);
+  scene.add(obj);
+  cars.push(obj);
+}
+</pre>
+<p>And the result.</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-animated-cars.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/load-gltf-animated-cars.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>Not bad for a few minutes work.</p>
+<p>The last thing I wanted to do is turn on shadows.</p>
+<p>To do this I grabbed all the GUI code from the <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> shadows
+example in <a href="shadows.html">the article on shadows</a> and pasted it
+into our latest code.</p>
+<p>Then, after loading, we need to turn on shadows on all the objects.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const gltfLoader = new GLTFLoader();
+  gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
+    const root = gltf.scene;
+    scene.add(root);
+
++    root.traverse((obj) =&gt; {
++      if (obj.castShadow !== undefined) {
++        obj.castShadow = true;
++        obj.receiveShadow = true;
++      }
++    });
+</pre>
+<p>I then spent nearly 4 hours trying to figure out why the shadow helpers
+were not working. It was because I forgot to enable shadows with</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">renderer.shadowMap.enabled = true;
+</pre>
+<p>😭</p>
+<p>I then adjusted the values until our <code class="notranslate" translate="no">DirectionLight</code>'s shadow camera
+had a frustum that covered the entire scene. These are the settings
+I ended up with.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
+  const color = 0xFFFFFF;
+  const intensity = 1;
+  const light = new THREE.DirectionalLight(color, intensity);
++  light.castShadow = true;
+*  light.position.set(-250, 800, -850);
+*  light.target.position.set(-550, 40, -450);
+
++  light.shadow.bias = -0.004;
++  light.shadow.mapSize.width = 2048;
++  light.shadow.mapSize.height = 2048;
+
+  scene.add(light);
+  scene.add(light.target);
++  const cam = light.shadow.camera;
++  cam.near = 1;
++  cam.far = 2000;
++  cam.left = -1500;
++  cam.right = 1500;
++  cam.top = 1500;
++  cam.bottom = -1500;
+...
+</pre>
+<p>and I set the background color to light blue.</p>
+<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+-scene.background = new THREE.Color('black');
++scene.background = new THREE.Color('#DEFEFF');
+</pre>
+<p>And ... shadows</p>
+<p></p><div translate="no" class="threejs_example_container notranslate">
+  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-shadows.html"></iframe></div>
+  <a class="threejs_center" href="/manual/examples/load-gltf-shadows.html" target="_blank">click here to open in a separate window</a>
+</div>
+
+<p></p>
+<p>I hope walking through this project was useful and showed some
+good examples of working though some of the issues of loading
+a file with a scenegraph.</p>
+<p>One interesting thing is that comparing the .blend file to the .gltf
+file, the .blend file has several lights but they are not lights
+after being loaded into the scene. A .GLTF file is just a JSON
+file so you can easily look inside. It consists of several
+arrays of things and each item in an array is referenced by index
+else where. While there are extensions in the works they point
+to a problem with almost all 3d formats. <strong>They can never cover every
+case</strong>.</p>
+<p>There is always a need for more data. For example we manually exported
+a path for the cars to follow. Ideally that info could have been in
+the .GLTF file but to do that we'd need to write our own exporter
+and some how mark nodes for how we want them exported or use a
+naming scheme or something along those lines to get data from
+whatever tool we're using to create the data into our app.</p>
+<p>All of that is left as an exercise to the reader.</p>
+
+        </div>
+      </div>
+    </div>
+  
+  <script src="/manual/resources/prettify.js"></script>
+  <script src="/manual/resources/lesson.js"></script>
+
+
+
+
+</body></html>

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.