Mr.doob 3 years ago
parent
commit
3b0c795c36
100 changed files with 7643 additions and 3185 deletions
  1. 102 51
      build/three.js
  2. 0 0
      build/three.min.js
  3. 146 72
      build/three.module.js
  4. 1 2
      docs/api/ar/cameras/CubeCamera.html
  5. 1 2
      docs/api/en/cameras/CubeCamera.html
  6. 2 2
      docs/api/en/core/Raycaster.html
  7. 2 2
      docs/api/en/extras/ShapeUtils.html
  8. 0 7
      docs/api/en/extras/core/CurvePath.html
  9. 0 54
      docs/api/en/extras/core/Font.html
  10. 2 2
      docs/api/en/extras/core/Interpolations.html
  11. 1 2
      docs/api/en/extras/core/ShapePath.html
  12. 3 3
      docs/api/en/geometries/ExtrudeGeometry.html
  13. 1 1
      docs/api/en/geometries/LatheGeometry.html
  14. 1 1
      docs/api/en/geometries/ShapeGeometry.html
  15. 5 5
      docs/api/en/geometries/TubeGeometry.html
  16. 0 1
      docs/api/en/lights/AmbientLight.html
  17. 2 3
      docs/api/en/lights/HemisphereLight.html
  18. 5 0
      docs/api/en/materials/Material.html
  19. 11 1
      docs/api/en/materials/MeshPhysicalMaterial.html
  20. 0 1
      docs/api/en/materials/ShaderMaterial.html
  21. 5 0
      docs/api/en/math/Quaternion.html
  22. 10 0
      docs/api/en/math/Triangle.html
  23. 5 1
      docs/api/en/math/Vector2.html
  24. 10 1
      docs/api/en/math/Vector3.html
  25. 4 1
      docs/api/en/math/Vector4.html
  26. 1 2
      docs/api/ko/cameras/CubeCamera.html
  27. 2 2
      docs/api/ko/extras/core/Interpolations.html
  28. 2 3
      docs/api/ko/extras/core/ShapePath.html
  29. 1 2
      docs/api/zh/cameras/CubeCamera.html
  30. 34 43
      docs/api/zh/extras/core/Curve.html
  31. 0 7
      docs/api/zh/extras/core/CurvePath.html
  32. 0 55
      docs/api/zh/extras/core/Font.html
  33. 2 2
      docs/api/zh/extras/core/Interpolations.html
  34. 0 1
      docs/api/zh/extras/core/ShapePath.html
  35. 3 3
      docs/api/zh/geometries/ExtrudeGeometry.html
  36. 1 1
      docs/api/zh/geometries/LatheGeometry.html
  37. 1 1
      docs/api/zh/geometries/ShapeGeometry.html
  38. 1 1
      docs/api/zh/geometries/TubeGeometry.html
  39. 0 1
      docs/api/zh/lights/AmbientLight.html
  40. 2 3
      docs/api/zh/lights/HemisphereLight.html
  41. 4 0
      docs/api/zh/materials/Material.html
  42. 0 1
      docs/api/zh/materials/ShaderMaterial.html
  43. 11 0
      docs/api/zh/math/Triangle.html
  44. 4 0
      docs/api/zh/math/Vector2.html
  45. 3 0
      docs/api/zh/math/Vector3.html
  46. 3 0
      docs/api/zh/math/Vector4.html
  47. 266 0
      docs/examples/en/controls/ArcballControls.html
  48. 4 6
      docs/examples/en/geometries/ParametricGeometry.html
  49. 6 8
      docs/examples/en/geometries/TextGeometry.html
  50. 6 6
      docs/examples/en/loaders/FontLoader.html
  51. 4 6
      docs/examples/zh/geometries/ParametricGeometry.html
  52. 5 7
      docs/examples/zh/geometries/TextGeometry.html
  53. 5 5
      docs/examples/zh/loaders/FontLoader.html
  54. 27 1
      docs/index.html
  55. 9 10
      docs/list.json
  56. 3 3
      docs/manual/ko/buildTools/Testing-with-NPM.html
  57. 3 4
      docs/manual/ko/introduction/Animation-system.html
  58. 3 3
      docs/manual/ko/introduction/FAQ.html
  59. 4 4
      docs/manual/ko/introduction/How-to-dispose-of-objects.html
  60. 3 4
      docs/manual/ko/introduction/How-to-run-things-locally.html
  61. 3 5
      docs/manual/ko/introduction/Installation.html
  62. 1 1
      docs/manual/ko/introduction/Useful-links.html
  63. 0 100
      docs/scenes/geometry-browser.html
  64. 12 10
      editor/css/main.css
  65. 1 15
      editor/js/Menubar.Add.js
  66. 1 3
      editor/js/Menubar.File.js
  67. 6 0
      editor/js/Player.js
  68. 10 4
      editor/js/Resizer.js
  69. 10 4
      editor/js/libs/codemirror/addon/dialog.js
  70. 0 2
      editor/js/libs/codemirror/addon/show-hint.css
  71. 225 79
      editor/js/libs/codemirror/addon/show-hint.js
  72. 1 0
      editor/js/libs/codemirror/addon/tern.css
  73. 83 31
      editor/js/libs/codemirror/addon/tern.js
  74. 42 31
      editor/js/libs/codemirror/codemirror.css
  75. 1961 2174
      editor/js/libs/codemirror/codemirror.js
  76. 399 182
      editor/js/libs/codemirror/mode/javascript.js
  77. 6 1
      editor/js/libs/codemirror/theme/monokai.css
  78. 1 1
      editor/sw.js
  79. 5 3
      examples/files.json
  80. 97 36
      examples/games_fps.html
  81. 26 1
      examples/index.html
  82. 2995 0
      examples/js/controls/ArcballControls.js
  83. 0 21
      examples/js/controls/TransformControls.js
  84. 2 2
      examples/js/csm/CSMShader.js
  85. 1 1
      examples/js/csm/Frustum.js
  86. 11 1
      examples/js/exporters/USDZExporter.js
  87. 115 0
      examples/js/geometries/ParametricGeometry.js
  88. 49 0
      examples/js/geometries/TextGeometry.js
  89. 2 2
      examples/js/interactive/InteractiveGroup.js
  90. 183 0
      examples/js/loaders/FontLoader.js
  91. 1 1
      examples/js/loaders/GCodeLoader.js
  92. 89 42
      examples/js/loaders/GLTFLoader.js
  93. 545 0
      examples/js/loaders/KTX2Loader.js
  94. 5 4
      examples/js/loaders/RGBELoader.js
  95. 4 5
      examples/js/loaders/RGBMLoader.js
  96. 1 0
      examples/js/loaders/SVGLoader.js
  97. 6 5
      examples/js/postprocessing/SAOPass.js
  98. 0 3
      examples/js/postprocessing/SSRPass.js
  99. 1 4
      examples/js/postprocessing/SSRrPass.js
  100. 1 1
      examples/js/shaders/MMDToonShader.js

File diff suppressed because it is too large
+ 102 - 51
build/three.js


File diff suppressed because it is too large
+ 0 - 0
build/three.min.js


File diff suppressed because it is too large
+ 146 - 72
build/three.module.js


+ 1 - 2
docs/api/ar/cameras/CubeCamera.html

@@ -41,8 +41,7 @@
 		<h2>أمثلة (Examples)</h2>
 		<h2>أمثلة (Examples)</h2>
 
 
 		<p>
 		<p>
-			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]<br />
-			[example:webgl_shading_physical shading / physical ]
+			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
 		</p>
 		</p>
 
 
 		<h2>المنشئ (Constructor)</h2>
 		<h2>المنشئ (Constructor)</h2>

+ 1 - 2
docs/api/en/cameras/CubeCamera.html

@@ -41,8 +41,7 @@
 		<h2>Examples</h2>
 		<h2>Examples</h2>
 
 
 		<p>
 		<p>
-			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]<br />
-			[example:webgl_shading_physical shading / physical ]
+			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
 		</p>
 		</p>
 
 
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>

+ 2 - 2
docs/api/en/core/Raycaster.html

@@ -160,7 +160,7 @@
 		<h3>[method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )</h3>
 		<h3>[method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )</h3>
 		<p>
 		<p>
 		[page:Object3D object] — The object to check for intersection with the ray.<br />
 		[page:Object3D object] — The object to check for intersection with the ray.<br />
-		[page:Boolean recursive] — If true, it also checks all descendants. Otherwise it only checks intersection with the object. Default is false.<br />
+		[page:Boolean recursive] — If true, it also checks all descendants. Otherwise it only checks intersection with the object. Default is true.<br />
 		[page:Array optionalTarget] — (optional) target to set the result. Otherwise a new [page:Array] is instantiated. If set, you must clear this array prior to each call (i.e., array.length = 0;).
 		[page:Array optionalTarget] — (optional) target to set the result. Otherwise a new [page:Array] is instantiated. If set, you must clear this array prior to each call (i.e., array.length = 0;).
 		</p>
 		</p>
 		<p>
 		<p>
@@ -189,7 +189,7 @@
 		<h3>[method:Array intersectObjects]( [param:Array objects], [param:Boolean recursive], [param:Array optionalTarget] )</h3>
 		<h3>[method:Array intersectObjects]( [param:Array objects], [param:Boolean recursive], [param:Array optionalTarget] )</h3>
 		<p>
 		<p>
 		[page:Array objects] — The objects to check for intersection with the ray.<br />
 		[page:Array objects] — The objects to check for intersection with the ray.<br />
-		[page:Boolean recursive] — If true, it also checks all descendants of the objects. Otherwise it only checks intersection with the objects. Default is false.<br />
+		[page:Boolean recursive] — If true, it also checks all descendants of the objects. Otherwise it only checks intersection with the objects. Default is true.<br />
 		[page:Array optionalTarget] — (optional) target to set the result. Otherwise a new [page:Array] is instantiated. If set, you must clear this array prior to each call (i.e., array.length = 0;).
 		[page:Array optionalTarget] — (optional) target to set the result. Otherwise a new [page:Array] is instantiated. If set, you must clear this array prior to each call (i.e., array.length = 0;).
 		</p>
 		</p>
 		<p>
 		<p>

+ 2 - 2
docs/api/en/extras/ShapeUtils.html

@@ -39,8 +39,8 @@
 
 
 		<h3>[method:Array triangulateShape]( contour, holes )</h3>
 		<h3>[method:Array triangulateShape]( contour, holes )</h3>
 		<p>
 		<p>
-		contour -- 2D polygon.<br />
-		holes -- array of holes<br /><br />
+		contour -- 2D polygon. An array of [page:Vector2].<br />
+		holes -- An array that holds arrays of [page:Vector2]s. Each array represents a single hole definition.<br /><br />
 
 
 		Used internally by [page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeGeometry ShapeGeometry] to calculate faces in shapes with holes.
 		Used internally by [page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeGeometry ShapeGeometry] to calculate faces in shapes with holes.
 		</p>
 		</p>

+ 0 - 7
docs/api/en/extras/core/CurvePath.html

@@ -44,13 +44,6 @@
 		<h3>[method:Array getCurveLengths]()</h3>
 		<h3>[method:Array getCurveLengths]()</h3>
 		<p>Get list of cumulative curve lengths of the curves in the [page:.curves] array.</p>
 		<p>Get list of cumulative curve lengths of the curves in the [page:.curves] array.</p>
 
 
-		<h3>[method:Vector getPoint]( [param:Float t] )</h3>
-		<p>
-			[page:Float t] - A position on the curve. Must be in the range [ 0, 1 ]. <br><br />
-
-			Returns a vector for a given position on the curve path.
-		</p>
-
 		<h3>[method:Array getPoints]( [param:Integer divisions] )</h3>
 		<h3>[method:Array getPoints]( [param:Integer divisions] )</h3>
 		<p>
 		<p>
 			divisions -- number of pieces to divide the curve into. Default is *12*.<br /><br />
 			divisions -- number of pieces to divide the curve into. Default is *12*.<br /><br />

+ 0 - 54
docs/api/en/extras/core/Font.html

@@ -1,54 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<meta charset="utf-8" />
-		<base href="../../../../" />
-		<script src="page.js"></script>
-		<link type="text/css" rel="stylesheet" href="page.css" />
-	</head>
-	<body>
-		<h1>[name]</h1>
-
-		<p class="desc">
-		Create a set of [page:Shape Shapes] representing a font loaded in JSON format.<br /><br />
-
-		This is used internally by the [page:FontLoader].
-		</p>
-
-		<h2>Examples</h2>
-
-		<p>
-		[example:webgl_geometry_text_shapes geometry / text / shapes ]
-		</p>
-
-		<h2>Constructor</h2>
-
-		<h3>[name]( data )</h3>
-		<p>
-		data -- JSON data representing the font.<br /><br />
-
-		This constructor creates a new [name], which is an array of [page:Shape Shapes].
-		</p>
-
-		<h2>Properties</h2>
-
-		<h3>[property:Array data]</h3>
-		<p>The JSON data passed in the constructor.</p>
-
-		<h2>Methods</h2>
-
-		<h3>[method:null generateShapes]( [param:String text], [param:Float size] )</h3>
-		<p>
-			[page:String text] -- string of text.<br />
-			[page:Float size] -- (optional) scale for the [page:Shape Shapes]. Default is *100*.<br />
-
-			Creates an array of [page:Shape Shapes] representing the text in the font.
-		</p>
-
-		<h2>Source</h2>
-
-		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-		</p>
-	</body>
-</html>

+ 2 - 2
docs/api/en/extras/core/Interpolations.html

@@ -28,7 +28,7 @@
 		t -- interpolation weight.<br />
 		t -- interpolation weight.<br />
 		p0, p1, p2 -- the starting, control and end points defining the curve.<br /><br />
 		p0, p1, p2 -- the starting, control and end points defining the curve.<br /><br />
 
 
-		Used internally by [page:QuadraticBezierCurve3 QuadraticBezierCurve3], [page:QuadraticBezierCurve QuadraticBezierCurve] and [page:Font Font].
+		Used internally by [page:QuadraticBezierCurve3 QuadraticBezierCurve3] and [page:QuadraticBezierCurve QuadraticBezierCurve].
 		</p>
 		</p>
 
 
 		<h3>[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )</h3>
 		<h3>[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )</h3>
@@ -36,7 +36,7 @@
 		t -- interpolation weight.<br />
 		t -- interpolation weight.<br />
 		p0, p1, p2, p3 -- the starting, control(twice) and end points defining the curve.<br /><br />
 		p0, p1, p2, p3 -- the starting, control(twice) and end points defining the curve.<br /><br />
 
 
-		Used internally by [page:CubicBezierCurve3 CubicBezierCurve3], [page:CubicBezierCurve CubicBezierCurve] and [page:Font Font].
+		Used internally by [page:CubicBezierCurve3 CubicBezierCurve3] and [page:CubicBezierCurve CubicBezierCurve].
 		</p>
 		</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>

+ 1 - 2
docs/api/en/extras/core/ShapePath.html

@@ -11,8 +11,7 @@
 
 
 		<p class="desc">
 		<p class="desc">
 		This class is used to convert a series of shapes to an array of [page:Path]s, for example an SVG shape to a
 		This class is used to convert a series of shapes to an array of [page:Path]s, for example an SVG shape to a
-		path (see the example below). It is used internally by [page:Font] to convert a font in JSON format to a
-		series of paths.
+		path (see the example below).
 		</p>
 		</p>
 
 
 		<h2>Examples</h2>
 		<h2>Examples</h2>

+ 3 - 3
docs/api/en/geometries/ExtrudeGeometry.html

@@ -72,10 +72,10 @@
 			<ul>
 			<ul>
 				<li>curveSegments — int. Number of points on the curves. Default is 12.</li>
 				<li>curveSegments — int. Number of points on the curves. Default is 12.</li>
 				<li>steps — int. Number of points used for subdividing segments along the depth of the extruded spline. Default is 1.</li>
 				<li>steps — int. Number of points used for subdividing segments along the depth of the extruded spline. Default is 1.</li>
-				<li>depth — float. Depth to extrude the shape. Default is 100.</li>
+				<li>depth — float. Depth to extrude the shape. Default is 1.</li>
 				<li>bevelEnabled — bool. Apply beveling to the shape. Default is true.</li>
 				<li>bevelEnabled — bool. Apply beveling to the shape. Default is true.</li>
-				<li>bevelThickness — float. How deep into the original shape the bevel goes. Default is 6.</li>
-				<li>bevelSize — float. Distance from the shape outline that the bevel extends. Default is bevelThickness - 2.</li>
+				<li>bevelThickness — float. How deep into the original shape the bevel goes. Default is 0.2.</li>
+				<li>bevelSize — float. Distance from the shape outline that the bevel extends. Default is bevelThickness - 0.1.</li>
 				<li>bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0.</li>
 				<li>bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0.</li>
 				<li>bevelSegments — int. Number of bevel layers. Default is 3.</li>
 				<li>bevelSegments — int. Number of bevel layers. Default is 3.</li>
 				<li>extrudePath — THREE.Curve. A 3D spline path along which the shape should be extruded. Bevels not supported for path extrusion.</li>
 				<li>extrudePath — THREE.Curve. A 3D spline path along which the shape should be extruded. Bevels not supported for path extrusion.</li>

+ 1 - 1
docs/api/en/geometries/LatheGeometry.html

@@ -48,7 +48,7 @@
 
 
 		<h3>[name]([param:Array points], [param:Integer segments], [param:Float phiStart], [param:Float phiLength])</h3>
 		<h3>[name]([param:Array points], [param:Integer segments], [param:Float phiStart], [param:Float phiLength])</h3>
 		<p>
 		<p>
-		points — Array of Vector2s. The x-coordinate of each point must be greater than zero.<br />
+		points — Array of Vector2s. The x-coordinate of each point must be greater than zero. Default is an array with (0,0.5), (0.5,0) and (0,-0.5) which creates a simple diamond shape.<br />
 		segments — the number of circumference segments to generate. Default is 12.<br />
 		segments — the number of circumference segments to generate. Default is 12.<br />
 		phiStart — the starting angle in radians. Default is 0.<br />
 		phiStart — the starting angle in radians. Default is 0.<br />
 		phiLength — the radian (0 to 2PI) range of the lathed section 2PI is a closed lathe, less than 2PI is a portion. Default is 2PI.
 		phiLength — the radian (0 to 2PI) range of the lathed section 2PI is a closed lathe, less than 2PI is a portion. Default is 2PI.

+ 1 - 1
docs/api/en/geometries/ShapeGeometry.html

@@ -59,7 +59,7 @@
 
 
 		<h3>[name]([param:Array shapes], [param:Integer curveSegments])</h3>
 		<h3>[name]([param:Array shapes], [param:Integer curveSegments])</h3>
 		<p>
 		<p>
-		shapes — [page:Array] of shapes or a single [page:Shape shape].<br />
+		shapes — [page:Array] of shapes or a single [page:Shape shape]. Default is a single triangle shape.<br />
 		curveSegments - [page:Integer] - Number of segments per shape. Default is 12.
 		curveSegments - [page:Integer] - Number of segments per shape. Default is 12.
 		</p>
 		</p>
 
 

+ 5 - 5
docs/api/en/geometries/TubeGeometry.html

@@ -68,11 +68,11 @@
 
 
 		<h3>[name]([param:Curve path], [param:Integer tubularSegments], [param:Float radius], [param:Integer radialSegments], [param:Boolean closed])</h3>
 		<h3>[name]([param:Curve path], [param:Integer tubularSegments], [param:Float radius], [param:Integer radialSegments], [param:Boolean closed])</h3>
 		<p>
 		<p>
-		path — [page:Curve] - A 3D path that inherits from the [page:Curve] base class<br />
-		tubularSegments — [page:Integer] - The number of segments that make up the tube, default is 64<br />
-		radius — [page:Float] - The radius of the tube, default is 1<br />
-		radialSegments — [page:Integer] - The number of segments that make up the cross-section, default is 8 <br />
-		closed — [page:Boolean] Is the tube open or closed, default is false <br />
+		path — [page:Curve] - A 3D path that inherits from the [page:Curve] base class. Default is a quadratic bezier curve.<br />
+		tubularSegments — [page:Integer] - The number of segments that make up the tube. Default is *64*.<br />
+		radius — [page:Float] - The radius of the tube. Default is *1*.<br />
+		radialSegments — [page:Integer] - The number of segments that make up the cross-section. Default is *8*.<br />
+		closed — [page:Boolean] Is the tube open or closed. Default is *false*.<br />
 		</p>
 		</p>
 
 
 
 

+ 0 - 1
docs/api/en/lights/AmbientLight.html

@@ -26,7 +26,6 @@
 
 
 		<h2>Examples</h2>
 		<h2>Examples</h2>
 		<p>
 		<p>
-			[example:webgl_animation_cloth animation / cloth ]<br />
 			[example:webgl_animation_skinning_blending animation / skinning / blending ]
 			[example:webgl_animation_skinning_blending animation / skinning / blending ]
 		</p>
 		</p>
 
 

+ 2 - 3
docs/api/en/lights/HemisphereLight.html

@@ -28,12 +28,11 @@
 		<h2>Examples</h2>
 		<h2>Examples</h2>
 
 
 		<p>
 		<p>
+			[example:webgl_animation_skinning_blending animation / skinning / blending ]<br />
 			[example:webgl_lights_hemisphere lights / hemisphere ]<br />
 			[example:webgl_lights_hemisphere lights / hemisphere ]<br />
 			[example:misc_controls_pointerlock controls / pointerlock ]<br />
 			[example:misc_controls_pointerlock controls / pointerlock ]<br />
-			[example:webgl_decals decals ]<br />
 			[example:webgl_loader_collada_kinematics loader / collada / kinematics ]<br />
 			[example:webgl_loader_collada_kinematics loader / collada / kinematics ]<br />
-			[example:webgl_materials_lightmap materials / lightmap ]<br />
-			[example:webgl_shaders_ocean shaders / ocean ]
+			[example:webgl_loader_stl loader / stl ]
 		</p>
 		</p>
 
 
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>

+ 5 - 0
docs/api/en/materials/Material.html

@@ -128,6 +128,11 @@
 		When drawing 2D overlays it can be useful to disable the depth writing in order to layer several things together without creating z-index artifacts.
 		When drawing 2D overlays it can be useful to disable the depth writing in order to layer several things together without creating z-index artifacts.
 		</p>
 		</p>
 
 
+		<h3>[property:Number format]</h3>
+		<p>
+		When this property is set to [page:Textures THREE.RGBFormat], the material is considered to be opaque and alpha values are ignored. Default is [page:Textures THREE.RGBAFormat].
+		</p>
+
 		<h3>[property:Boolean stencilWrite]</h3>
 		<h3>[property:Boolean stencilWrite]</h3>
 		<p>
 		<p>
 		Whether stencil operations are performed against the stencil buffer. In order to perform writes or comparisons against the stencil buffer this value must be *true*. Default is *false*.
 		Whether stencil operations are performed against the stencil buffer. In order to perform writes or comparisons against the stencil buffer this value must be *true*. Default is *false*.

+ 11 - 1
docs/api/en/materials/MeshPhysicalMaterial.html

@@ -133,9 +133,19 @@
 			This models the reflectivity of non-metallic materials. It has no effect when [page:MeshStandardMaterial.metalness metalness] is *1.0*
 			This models the reflectivity of non-metallic materials. It has no effect when [page:MeshStandardMaterial.metalness metalness] is *1.0*
 		</p>
 		</p>
 
 
+		<h3>[property:Float sheen]</h3>
+		<p>
+			The intensity of the sheen layer, from *0.0* to *1.0*. Default is *0.0*.</p>
+		</p>
+
+		<h3>[property:Float sheenRoughness]</h3>
+		<p>
+			Roughness of the sheen layer, from *0.0* to *1.0*. Default is *1.0*.</p>
+		</p>
+
 		<h3>[property:Color sheenTint]</h3>
 		<h3>[property:Color sheenTint]</h3>
 		<p>
 		<p>
-			Used for rendering materials such as velvet. It has no effect when set to black (0x000000). Default is black.
+			The sheen tint. Default is *0xffffff*, white.
 		</p>
 		</p>
 
 
 		<h3>[property:Float transmission]</h3>
 		<h3>[property:Float transmission]</h3>

+ 0 - 1
docs/api/en/materials/ShaderMaterial.html

@@ -91,7 +91,6 @@
 		<h2>Examples</h2>
 		<h2>Examples</h2>
 
 
 		<p>
 		<p>
-			[example:webgl_animation_cloth webgl / animation / cloth ]<br />
 			[example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]<br />
 			[example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]<br />
 			[example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]<br />
 			[example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]<br />
 			[example:webgl_custom_attributes webgl / custom / attributes]<br />
 			[example:webgl_custom_attributes webgl / custom / attributes]<br />

+ 5 - 0
docs/api/en/math/Quaternion.html

@@ -138,6 +138,11 @@
 		<h3>[method:Quaternion premultiply]( [param:Quaternion q] )</h3>
 		<h3>[method:Quaternion premultiply]( [param:Quaternion q] )</h3>
 		<p>Pre-multiplies this quaternion by [page:Quaternion q].</p>
 		<p>Pre-multiplies this quaternion by [page:Quaternion q].</p>
 
 
+		<h3>[method:Quaternion random]()</h3>
+		<p>
+		Sets this quaternion to a uniformly random, normalized quaternion.
+		</p>
+
 		<h3>[method:Quaternion rotateTowards]( [param:Quaternion q], [param:Float step] )</h3>
 		<h3>[method:Quaternion rotateTowards]( [param:Quaternion q], [param:Float step] )</h3>
 		<p>
 		<p>
 			[page:Quaternion q] - The target quaternion.<br />
 			[page:Quaternion q] - The target quaternion.<br />

+ 10 - 0
docs/api/en/math/Triangle.html

@@ -126,6 +126,16 @@
 		Please note that this method only copies the values from the given objects.
 		Please note that this method only copies the values from the given objects.
 		</p>
 		</p>
 
 
+		<h3>[method:Triangle setFromAttributeAndIndices]( [param:BufferAttribute attribute], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) [param:Triangle this]</h3>
+		<p>
+		attribute - [page:BufferAttribute] of vertex data <br />
+		i0 - [page:Integer] index <br />
+		i1 - [page:Integer] index <br />
+		i2 - [page:Integer] index<br /><br />
+
+		Sets the triangle's vertices from the buffer attribute vertex data.
+		</p>
+
 		<h3>[method:Triangle setFromPointsAndIndices]( [param:Array points], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) [param:Triangle this]</h3>
 		<h3>[method:Triangle setFromPointsAndIndices]( [param:Array points], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) [param:Triangle this]</h3>
 		<p>
 		<p>
 		points - [page:Array] of [page:Vector3]s <br />
 		points - [page:Array] of [page:Vector3]s <br />

+ 5 - 1
docs/api/en/math/Vector2.html

@@ -36,6 +36,10 @@
 			vectors, complex numbers and so on,	however these are the most common uses in three.js.
 			vectors, complex numbers and so on,	however these are the most common uses in three.js.
 		</p>
 		</p>
 
 
+		<p>
+			Iterating through a Vector2 instance will yield its components (x, y) in the corresponding order.
+		</p>
+
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 
 
 		<code>
 		<code>
@@ -176,7 +180,7 @@
 		</p>
 		</p>
 
 
 		<h3>[method:Boolean equals]( [param:Vector2 v] )</h3>
 		<h3>[method:Boolean equals]( [param:Vector2 v] )</h3>
-		<p>Checks for strict equality of this vector and [page:Vector2 v].</p>
+		<p>Returns *true* if the components of this vector and [page:Vector2 v] are strictly equal; *false* otherwise.</p>
 
 
 		<h3>[method:this floor]()</h3>
 		<h3>[method:this floor]()</h3>
 		<p>The components of this vector are rounded down to the nearest integer value.</p>
 		<p>The components of this vector are rounded down to the nearest integer value.</p>

+ 10 - 1
docs/api/en/math/Vector3.html

@@ -35,6 +35,10 @@
 		vectors and so on, however these are the most common uses in three.js.
 		vectors and so on, however these are the most common uses in three.js.
 		</p>
 		</p>
 
 
+		<p>
+		Iterating through a Vector3 instance will yield its components (x, y, z) in the corresponding order.
+		</p>
+
 
 
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 
 
@@ -202,7 +206,7 @@
 		</p>
 		</p>
 
 
 		<h3>[method:Boolean equals]( [param:Vector3 v] )</h3>
 		<h3>[method:Boolean equals]( [param:Vector3 v] )</h3>
-		<p>Checks for strict equality of this vector and [page:Vector3 v].</p>
+		<p>Returns *true* if the components of this vector and [page:Vector3 v] are strictly equal; *false* otherwise.</p>
 
 
 		<h3>[method:this floor]()</h3>
 		<h3>[method:this floor]()</h3>
 		<p>The components of this vector are rounded down to the nearest integer value.</p>
 		<p>The components of this vector are rounded down to the nearest integer value.</p>
@@ -440,6 +444,11 @@
 			Sets each component of this vector to a pseudo-random value between 0 and 1, excluding 1.
 			Sets each component of this vector to a pseudo-random value between 0 and 1, excluding 1.
 		</p>
 		</p>
 
 
+		<h3>[method:this randomDirection]()</h3>
+		<p>
+		Sets this vector to a uniformly random point on a unit sphere.
+		</p>
+
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
 		<p>
 		<p>

+ 4 - 1
docs/api/en/math/Vector4.html

@@ -34,6 +34,9 @@
 		There are other things a 4D vector can be used to represent, however these are the most common uses in three.js.
 		There are other things a 4D vector can be used to represent, however these are the most common uses in three.js.
 		</p>
 		</p>
 
 
+		<p>
+		Iterating through a Vector4 instance will yield its components (x, y, z, w) in the corresponding order.
+		</p>
 
 
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 
 
@@ -152,7 +155,7 @@
 		</p>
 		</p>
 
 
 		<h3>[method:Boolean equals]( [param:Vector4 v] )</h3>
 		<h3>[method:Boolean equals]( [param:Vector4 v] )</h3>
-		<p>Checks for strict equality of this vector and [page:Vector4 v].</p>
+		<p>Returns *true* if the components of this vector and [page:Vector4 v] are strictly equal; *false* otherwise.</p>
 
 
 		<h3>[method:this floor]()</h3>
 		<h3>[method:this floor]()</h3>
 		<p>The components of this vector are rounded down to the nearest integer value.</p>
 		<p>The components of this vector are rounded down to the nearest integer value.</p>

+ 1 - 2
docs/api/ko/cameras/CubeCamera.html

@@ -41,8 +41,7 @@
 		<h2>예제</h2>
 		<h2>예제</h2>
 
 
 		<p>
 		<p>
-			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]<br />
-			[example:webgl_shading_physical shading / physical ]
+			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
 		</p>
 		</p>
 
 
 		<h2>생성자</h2>
 		<h2>생성자</h2>

+ 2 - 2
docs/api/ko/extras/core/Interpolations.html

@@ -28,7 +28,7 @@
 		t -- 보간 가중치.<br />
 		t -- 보간 가중치.<br />
 		p0, p1, p2 -- 곡선을 정의하는 시작, 조절, 끝 점입니다.<br /><br />
 		p0, p1, p2 -- 곡선을 정의하는 시작, 조절, 끝 점입니다.<br /><br />
 
 
-		[page:QuadraticBezierCurve3 QuadraticBezierCurve3], [page:QuadraticBezierCurve QuadraticBezierCurve] 및 [page:Font Font]에서 내부적으로 사용됩니다.
+		[page:QuadraticBezierCurve3 QuadraticBezierCurve3] 및 [page:QuadraticBezierCurve QuadraticBezierCurve]에서 내부적으로 사용됩니다.
 		</p>
 		</p>
 
 
 		<h3>[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )</h3>
 		<h3>[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )</h3>
@@ -36,7 +36,7 @@
 		t -- 보간 가중치.<br />
 		t -- 보간 가중치.<br />
 		p0, p1, p2, p3 -- 곡선을 정의하는 시작, 조절(두 번), 끝 점입니다.<br /><br />
 		p0, p1, p2, p3 -- 곡선을 정의하는 시작, 조절(두 번), 끝 점입니다.<br /><br />
 
 
-		[page:CubicBezierCurve3 CubicBezierCurve3], [page:CubicBezierCurve CubicBezierCurve] 및 [page:Font Font]에서 내부적으로 사용됩니다.
+		[page:CubicBezierCurve3 CubicBezierCurve3] 및 [page:CubicBezierCurve CubicBezierCurve]에서 내부적으로 사용됩니다.
 		</p>
 		</p>
 
 
 		<h2>소스코드</h2>
 		<h2>소스코드</h2>

+ 2 - 3
docs/api/ko/extras/core/ShapePath.html

@@ -10,8 +10,7 @@
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
 		<p class="desc">
 		<p class="desc">
-		This class is used to convert 이 클래스는 연속된 shape들을 [page:Path] 배열로 바꾸는데에 사용되며, 일례로 SVG shape를 path로 바꾸는 것이 있습니다.(아래 예제를 확인하세요). 
-		[page:Font]에서 내부적으로 JSON 포맷의 폰드를 path 시리즈로 만드는 데에 사용됩니다.
+		This class is used to convert 이 클래스는 연속된 shape들을 [page:Path] 배열로 바꾸는데에 사용되며, 일례로 SVG shape를 path로 바꾸는 것이 있습니다.(아래 예제를 확인하세요).
 		</p>
 		</p>
 
 
 		<h2>예제</h2>
 		<h2>예제</h2>
@@ -71,7 +70,7 @@
 		noHoles -- holes를 생성할지 안 할지를 설정합니다.
 		noHoles -- holes를 생성할지 안 할지를 설정합니다.
 		</p>
 		</p>
 		<p>
 		<p>
-		[page:ShapePath.subPaths subPaths] 배열을 Shapes 배열로 변환합니다. 기본값으로 solid shapes는 시계방향(CW)이고 holes는 반시계방향(CCW)입니다. 
+		[page:ShapePath.subPaths subPaths] 배열을 Shapes 배열로 변환합니다. 기본값으로 solid shapes는 시계방향(CW)이고 holes는 반시계방향(CCW)입니다.
 		isCCW가 true면, 이 값들이 반대가 됩니다.
 		isCCW가 true면, 이 값들이 반대가 됩니다.
 		noHoles 파라미터가 true면 모든 path들은 solid shapes로 설정되고 isCCW는 무시됩니다.
 		noHoles 파라미터가 true면 모든 path들은 solid shapes로 설정되고 isCCW는 무시됩니다.
 		<br/>
 		<br/>

+ 1 - 2
docs/api/zh/cameras/CubeCamera.html

@@ -41,8 +41,7 @@
 		<h2>例子</h2>
 		<h2>例子</h2>
 
 
 		<p>
 		<p>
-			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]<br />
-			[example:webgl_shading_physical shading / physical ]
+			[example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic ]
 		</p>
 		</p>
 
 
 		<h2>构造器</h2>
 		<h2>构造器</h2>

+ 34 - 43
docs/api/zh/extras/core/Curve.html

@@ -10,8 +10,8 @@
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
 		<p class="desc">
 		<p class="desc">
-		An abstract base class for creating a [name] object that contains methods for interpolation.
-		For an array of [name]s see [page:CurvePath].
+			用于创建包含插值方法的[name]对象的抽象基类。
+			有关[name]的数组,请参见[page:CurvePath]。
 		</p>
 		</p>
 
 
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>
@@ -19,102 +19,93 @@
 
 
 		<h3>[name]()</h3>
 		<h3>[name]()</h3>
 		<p>
 		<p>
-		This constructor creates a new [name].
+		创建一个 [name].
 		</p>
 		</p>
 
 
-		<h2>Properties</h2>
+		<h2>属性</h2>
 
 
 		<h3>[property:Integer arcLengthDivisions]</h3>
 		<h3>[property:Integer arcLengthDivisions]</h3>
-		<p>This value determines the amount of divisions when calculating the cumulative segment lengths of a curve via [page:.getLengths].
-			To ensure precision when using methods like [page:.getSpacedPoints], it is recommended to increase [page:.arcLengthDivisions] if the curve is very large. Default is 200.</p>
+		<p>确定[page:.GetLength]计算曲线的累积分段长度时的分段量。
+			为确保[page:.getSpacedPoints]等方法时的精度,如果曲线非常大,建议增加[page:.arcLengthDivisions]。默认值为200</p>
 
 
-		<h2>Methods</h2>
+		<h2>方法</h2>
 
 
 		<h3>[method:Vector getPoint]( [param:Float t], [param:Vector optionalTarget] )</h3>
 		<h3>[method:Vector getPoint]( [param:Float t], [param:Vector optionalTarget] )</h3>
 		<p>
 		<p>
-			[page:Float t] - A position on the curve. Must be in the range [ 0, 1 ]. <br>
-			[page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector,
-			otherwise a new Vector will be created. <br /><br />
+			[page:Float t] - 曲线上的位置。必须在[0,1]范围内 <br>
+			[page:Vector optionalTarget] — (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。 <br /><br />
 
 
-			Returns a vector for a given position on the curve.
+			返回曲线上给定位置的点。
 		</p>
 		</p>
 
 
 		<h3>[method:Vector getPointAt]( [param:Float u], [param:Vector optionalTarget] )</h3>
 		<h3>[method:Vector getPointAt]( [param:Float u], [param:Vector optionalTarget] )</h3>
 		<p>
 		<p>
-			[page:Float u] - A position on the curve according to the arc length. Must be in the range [ 0, 1 ]. <br>
-			[page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector,
-			otherwise a new Vector will be created. <br /><br />
+			[page:Float u] - 根据弧长在曲线上的位置。必须在范围[0,1]内。 <br>
+			[page:Vector optionalTarget] — (可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。 <br /><br />
 
 
-			Returns a vector for a given position on the curve according to the arc length.
+			根据弧长返回曲线上给定位置的点。
 		</p>
 		</p>
 
 
 		<h3>[method:Array getPoints]( [param:Integer divisions] )</h3>
 		<h3>[method:Array getPoints]( [param:Integer divisions] )</h3>
 		<p>
 		<p>
-			divisions -- number of pieces to divide the curve into. Default is *5*.<br /><br />
-
-			Returns a set of divisions + 1 points using getPoint( t ).
+			divisions -- 要将曲线划分为的分段数。默认是 *5*.<br /><br />
+			使用getPoint(t)返回一组divisions+1的点
 		</p>
 		</p>
 
 
 		<h3>[method:Array getSpacedPoints]( [param:Integer divisions] )</h3>
 		<h3>[method:Array getSpacedPoints]( [param:Integer divisions] )</h3>
 		<p>
 		<p>
-			divisions -- number of pieces to divide the curve into. Default is *5*.<br /><br />
+			divisions -- 要将曲线划分为的分段数。默认是 *5*.<br /><br />
 
 
-			Returns a set of divisions + 1 equi-spaced points using getPointAt( u ).
+			使用getPointAt(u)返回一个分段+1的等距点的数组。
 		</p>
 		</p>
 
 
 		<h3>[method:Float getLength]()</h3>
 		<h3>[method:Float getLength]()</h3>
-		<p>Get total curve arc length.</p>
+		<p>获取总曲线弧长。</p>
 
 
 		<h3>[method:Array getLengths]( [param:Integer divisions] )</h3>
 		<h3>[method:Array getLengths]( [param:Integer divisions] )</h3>
-		<p>Get list of cumulative segment lengths.</p>
+		<p>获取累积段长度的列表。</p>
 
 
 		<h3>[method:null updateArcLengths]()</h3>
 		<h3>[method:null updateArcLengths]()</h3>
-		<p>Update the cumlative segment distance cache.</p>
+		<p>更新累积段距离缓存。</p>
 
 
 		<h3>[method:Float getUtoTmapping]( [param:Float u], [param:Float distance] )</h3>
 		<h3>[method:Float getUtoTmapping]( [param:Float u], [param:Float distance] )</h3>
 		<p>
 		<p>
-			Given u in the range ( 0 .. 1 ), returns [page:Float t] also in the range ( 0 .. 1 ).
-			u and t can then be used to give you points which are equidistant from the ends of the curve,
-			using [page:.getPoint].
+			给定范围(0..1)内的u,返回范围(0..1)内的[page:Float t],
+			然后可以用t来使用 [page:.getPoint]给出与曲线末端等距的点。
 		</p>
 		</p>
 
 
 		<h3>[method:Vector getTangent]( [param:Float t], [param:Vector optionalTarget] )</h3>
 		<h3>[method:Vector getTangent]( [param:Float t], [param:Vector optionalTarget] )</h3>
 		<p>
 		<p>
-			[page:Float t] - A position on the curve. Must be in the range [ 0, 1 ]. <br>
-			[page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector,
-			otherwise a new Vector will be created. <br /><br />
+			[page:Float t] -在曲线上的点,必须在范围 [ 0, 1 ]. <br>
+			[page:Vector optionalTarget] — (可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。 <br /><br />
 
 
-			Returns a unit vector tangent at t. If the derived curve does not implement its
-			tangent derivation, two points a small delta apart will be used to find its gradient
-			which seems to give a reasonable approximation.
+			返回t处的单位向量切线。如果派生曲线未实现其
+			切线求导,将使用相距一个小三角形的两个点来求与其实际梯度的近似值
 		</p>
 		</p>
 
 
 		<h3>[method:Vector getTangentAt]( [param:Float u], [param:Vector optionalTarget] )</h3>
 		<h3>[method:Vector getTangentAt]( [param:Float u], [param:Vector optionalTarget] )</h3>
 		<p>
 		<p>
-			[page:Float u] - A position on the curve according to the arc length. Must be in the range [ 0, 1 ]. <br>
-			[page:Vector optionalTarget] — (optional) If specified, the result will be copied into this Vector,
-			otherwise a new Vector will be created. <br /><br />
-
-			Returns tangent at a point which is equidistant to the ends of the curve from the
-			point given in [page:.getTangent].
+			[page:Float u] - 根据弧长在曲线上的位置,必须在范围[ 0, 1 ]。 <br>
+			[page:Vector optionalTarget] —(可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。 <br /><br />
+			返回一个点处的切线,该点与 [page:.getTangent]中给定的曲线的端点距离相等
 		</p>
 		</p>
 
 
 		<h3>[method:Object computeFrenetFrames]( [param:Integer segments], [param:Boolean closed] )</h3>
 		<h3>[method:Object computeFrenetFrames]( [param:Integer segments], [param:Boolean closed] )</h3>
 		<p>
 		<p>
-		Generates the Frenet Frames. Requires a curve definition in 3D space. Used in geometries like [page:TubeGeometry] or [page:ExtrudeGeometry].
+			生成Frenet帧。需要三维空间中的曲线定义。用于[page:TubeGeometry]或[page:ExtradeGeometry]等几何图形。
 		</p>
 		</p>
 
 
 		<h3>[method:Curve clone]()</h3>
 		<h3>[method:Curve clone]()</h3>
-		<p>Creates a clone of this instance.</p>
+		<p>创建此实例的克隆。</p>
 
 
 		<h3>[method:Curve copy]( [param:Curve source] )</h3>
 		<h3>[method:Curve copy]( [param:Curve source] )</h3>
-		<p>Copies another [name] object to this instance.</p>
+		<p>将另一个[name]对象复制到此实例。</p>
 
 
 		<h3>[method:Object toJSON]()</h3>
 		<h3>[method:Object toJSON]()</h3>
-		<p>Returns a JSON object representation of this instance.</p>
+		<p>返回此实例的JSON对象表示形式。</p>
 
 
 		<h3>[method:Curve fromJSON]( [param:Object json] )</h3>
 		<h3>[method:Curve fromJSON]( [param:Object json] )</h3>
-		<p>Copies the data from the given JSON object to this instance.</p>
+		<p>将给定的JSON数据复制到此实例。</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 

+ 0 - 7
docs/api/zh/extras/core/CurvePath.html

@@ -51,13 +51,6 @@
 		<h3>[method:Array getCurveLengths]()</h3>
 		<h3>[method:Array getCurveLengths]()</h3>
 		<p>Get list of cumulative curve lengths of the curves in the [page:.curves] array.</p>
 		<p>Get list of cumulative curve lengths of the curves in the [page:.curves] array.</p>
 
 
-		<h3>[method:Vector getPoint]( [param:Float t] )</h3>
-		<p>
-			[page:Float t] - A position on the curve. Must be in the range [ 0, 1 ]. <br><br />
-
-			Returns a vector for a given position on the curve path.
-		</p>
-
 		<h3>[method:Array getPoints]( [param:Integer divisions] )</h3>
 		<h3>[method:Array getPoints]( [param:Integer divisions] )</h3>
 		<p>
 		<p>
 			divisions -- 曲线分段数量。默认值为*12*。<br /><br />
 			divisions -- 曲线分段数量。默认值为*12*。<br /><br />

+ 0 - 55
docs/api/zh/extras/core/Font.html

@@ -1,55 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh">
-	<head>
-		<meta charset="utf-8" />
-		<base href="../../../../" />
-		<script src="page.js"></script>
-		<link type="text/css" rel="stylesheet" href="page.css" />
-	</head>
-	<body>
-		<h1>字体([name])</h1>
-
-		<p class="desc">
-		以JSON格式,创建一系列的[page:Shape Shape](形状)来表示一个字体。
-		<br /><br />
-
-		该类在内部由[page:FontLoader]所使用。
-		</p>
-
-		<h2>例子</h2>
-
-		<p>
-		[example:webgl_geometry_text_shapes geometry / text / shapes ]
-		</p>
-
-		<h2>构造函数</h2>
-
-		<h3>[name]( data )</h3>
-		<p>
-		data -- 表示字体的JSON数据。<br /><br />
-
-		这一构造函数创建一个新的[name],它是一个[page:Shape Shapes]数组。
-		</p>
-
-		<h2>属性</h2>
-
-		<h3>[property:Array data]</h3>
-		<p>传入到构造函数的JSON数据。</p>
-
-		<h2>方法</h2>
-
-		<h3>[method:null generateShapes]( [param:String text], [param:Float size] )</h3>
-		<p>
-			[page:String text] -- 文本字符串。<br />
-			[page:Float size] -- (可选)[page:Shape Shapes]的缩放,默认值为*100*。<br />
-
-			创建一个[page:Shape Shapes]数组,表示使用字体的文本。
-		</p>
-
-		<h2>源代码</h2>
-
-		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-		</p>
-	</body>
-</html>

+ 2 - 2
docs/api/zh/extras/core/Interpolations.html

@@ -28,7 +28,7 @@
 		t -- 插值权重<br />
 		t -- 插值权重<br />
 		p0, p1, p2 -- 定义了该曲线的起始点、控制点和终止点。<br /><br />
 		p0, p1, p2 -- 定义了该曲线的起始点、控制点和终止点。<br /><br />
 
 
-		在内部由[page:QuadraticBezierCurve3 QuadraticBezierCurve3]、[page:QuadraticBezierCurve QuadraticBezierCurve]和[page:Font Font]所使用。
+		在内部由[page:QuadraticBezierCurve3 QuadraticBezierCurve3]和[page:QuadraticBezierCurve QuadraticBezierCurve]所使用。
 		</p>
 		</p>
 
 
 		<h3>[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )</h3>
 		<h3>[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )</h3>
@@ -36,7 +36,7 @@
 		t -- 插值权重<br />
 		t -- 插值权重<br />
 		p0, p1, p2, p3 -- 定义了该曲线的起始点、两个控制点和终止点。<br /><br />
 		p0, p1, p2, p3 -- 定义了该曲线的起始点、两个控制点和终止点。<br /><br />
 
 
-		在内部由[page:CubicBezierCurve3 CubicBezierCurve3]、[page:CubicBezierCurve CubicBezierCurve]和[page:Font Font]所使用。
+		在内部由[page:CubicBezierCurve3 CubicBezierCurve3]和[page:CubicBezierCurve CubicBezierCurve]所使用。
 		</p>
 		</p>
 
 
 		<h2>源代码</h2>
 		<h2>源代码</h2>

+ 0 - 1
docs/api/zh/extras/core/ShapePath.html

@@ -11,7 +11,6 @@
 
 
 		<p class="desc">
 		<p class="desc">
 		该类用于转换一系列的形状为一个[page:Path]数组,例如转换SVG中的Path为three.js中的Path(请参阅下方的example)。
 		该类用于转换一系列的形状为一个[page:Path]数组,例如转换SVG中的Path为three.js中的Path(请参阅下方的example)。
-		在内部它由[page:Font]所使用,用于将JSON字体转换为一系列路径。
 		</p>
 		</p>
 
 
 		<h2>例子</h2>
 		<h2>例子</h2>

+ 3 - 3
docs/api/zh/geometries/ExtrudeGeometry.html

@@ -72,10 +72,10 @@
 			<ul>
 			<ul>
 				<li>curveSegments — int,曲线上点的数量,默认值是12。</li>
 				<li>curveSegments — int,曲线上点的数量,默认值是12。</li>
 				<li>steps — int,用于沿着挤出样条的深度细分的点的数量,默认值为1。</li>
 				<li>steps — int,用于沿着挤出样条的深度细分的点的数量,默认值为1。</li>
-				<li>depth — float,挤出的形状的深度,默认值为100。</li>
+				<li>depth — float,挤出的形状的深度,默认值为1。</li>
 				<li>bevelEnabled — bool,对挤出的形状应用是否斜角,默认值为true。</li>
 				<li>bevelEnabled — bool,对挤出的形状应用是否斜角,默认值为true。</li>
-				<li>bevelThickness — float,设置原始形状上斜角的厚度。默认值为6。</li>
-				<li>bevelSize — float。斜角与原始形状轮廓之间的延伸距离,默认值为bevelThickness-2。</li>
+				<li>bevelThickness — float,设置原始形状上斜角的厚度。默认值为0.2。</li>
+				<li>bevelSize — float。斜角与原始形状轮廓之间的延伸距离,默认值为bevelThickness-0.1。</li>
 				<li>bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0.</li>
 				<li>bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0.</li>
 				<li>bevelSegments — int。斜角的分段层数,默认值为3。</li>
 				<li>bevelSegments — int。斜角的分段层数,默认值为3。</li>
 				<li>extrudePath — THREE.Curve对象。一条沿着被挤出形状的三维样条线。Bevels not supported for path extrusion.</li>
 				<li>extrudePath — THREE.Curve对象。一条沿着被挤出形状的三维样条线。Bevels not supported for path extrusion.</li>

+ 1 - 1
docs/api/zh/geometries/LatheGeometry.html

@@ -48,7 +48,7 @@
 
 
 		<h3>[name]([param:Array points], [param:Integer segments], [param:Float phiStart], [param:Float phiLength])</h3>
 		<h3>[name]([param:Array points], [param:Integer segments], [param:Float phiStart], [param:Float phiLength])</h3>
 		<p>
 		<p>
-		points — 一个Vector2对象数组。每个点的X坐标必须大于0。<br />
+		points — 一个Vector2对象数组。每个点的X坐标必须大于0。 Default is an array with (0,0.5), (0.5,0) and (0,-0.5) which creates a simple diamond shape.<br />
 		segments — 要生成的车削几何体圆周分段的数量,默认值是12。<br />
 		segments — 要生成的车削几何体圆周分段的数量,默认值是12。<br />
 		phiStart — 以弧度表示的起始角度,默认值为0。<br />
 		phiStart — 以弧度表示的起始角度,默认值为0。<br />
 		phiLength — 车削部分的弧度(0-2PI)范围,2PI将是一个完全闭合的、完整的车削几何体,小于2PI是部分的车削。默认值是2PI。
 		phiLength — 车削部分的弧度(0-2PI)范围,2PI将是一个完全闭合的、完整的车削几何体,小于2PI是部分的车削。默认值是2PI。

+ 1 - 1
docs/api/zh/geometries/ShapeGeometry.html

@@ -59,7 +59,7 @@
 
 
 		<h3>[name]([param:Array shapes], [param:Integer curveSegments])</h3>
 		<h3>[name]([param:Array shapes], [param:Integer curveSegments])</h3>
 		<p>
 		<p>
-		shapes — 一个单独的[page:Shape shape],或者一个包含形状的[page:Array]。<br />
+		shapes — 一个单独的[page:Shape shape],或者一个包含形状的[page:Array]。Default is a single triangle shape.<br />
 		curveSegments - [page:Integer] - 每一个形状的分段数,默认值为12。
 		curveSegments - [page:Integer] - 每一个形状的分段数,默认值为12。
 		</p>
 		</p>
 
 

+ 1 - 1
docs/api/zh/geometries/TubeGeometry.html

@@ -68,7 +68,7 @@
 
 
 		<h3>[name]([param:Curve path], [param:Integer tubularSegments], [param:Float radius], [param:Integer radialSegments], [param:Boolean closed])</h3>
 		<h3>[name]([param:Curve path], [param:Integer tubularSegments], [param:Float radius], [param:Integer radialSegments], [param:Boolean closed])</h3>
 		<p>
 		<p>
-				path — [page:Curve] - 一个由基类[page:Curve]继承而来的3D路径。<br />
+				path — [page:Curve] - 一个由基类[page:Curve]继承而来的3D路径。 Default is a quadratic bezier curve.<br />
 				tubularSegments — [page:Integer] - 组成这一管道的分段数,默认值为64。<br />
 				tubularSegments — [page:Integer] - 组成这一管道的分段数,默认值为64。<br />
 				radius — [page:Float] - 管道的半径,默认值为1。<br />
 				radius — [page:Float] - 管道的半径,默认值为1。<br />
 				radialSegments — [page:Integer] - 管道横截面的分段数目,默认值为8。<br />
 				radialSegments — [page:Integer] - 管道横截面的分段数目,默认值为8。<br />

+ 0 - 1
docs/api/zh/lights/AmbientLight.html

@@ -26,7 +26,6 @@
 
 
 		<h2>例子</h2>
 		<h2>例子</h2>
 		<p>
 		<p>
-			[example:webgl_animation_cloth animation / cloth ]<br />
 			[example:webgl_animation_skinning_blending animation / skinning / blending ]
 			[example:webgl_animation_skinning_blending animation / skinning / blending ]
 		</p>
 		</p>
 
 

+ 2 - 3
docs/api/zh/lights/HemisphereLight.html

@@ -28,12 +28,11 @@
 		<h2>例子</h2>
 		<h2>例子</h2>
 
 
 		<p>
 		<p>
+			[example:webgl_animation_skinning_blending animation / skinning / blending ]<br />
 			[example:webgl_lights_hemisphere lights / hemisphere ]<br />
 			[example:webgl_lights_hemisphere lights / hemisphere ]<br />
 			[example:misc_controls_pointerlock controls / pointerlock ]<br />
 			[example:misc_controls_pointerlock controls / pointerlock ]<br />
-			[example:webgl_decals decals ]<br />
 			[example:webgl_loader_collada_kinematics loader / collada / kinematics ]<br />
 			[example:webgl_loader_collada_kinematics loader / collada / kinematics ]<br />
-			[example:webgl_materials_lightmap materials / lightmap ]<br />
-			[example:webgl_shaders_ocean shaders / ocean ]
+			[example:webgl_loader_stl loader / stl ]
 		</p>
 		</p>
 
 
 		<h2>构造器(Constructor)</h2>
 		<h2>构造器(Constructor)</h2>

+ 4 - 0
docs/api/zh/materials/Material.html

@@ -107,6 +107,10 @@ Default is *false*.
 	在绘制2D叠加时,将多个事物分层在一起而不创建z-index时,禁用深度写入会很有用。
 	在绘制2D叠加时,将多个事物分层在一起而不创建z-index时,禁用深度写入会很有用。
 </p>
 </p>
 
 
+<h3>[property:Number format]</h3>
+<p>
+When this property is set to [page:Textures THREE.RGBFormat], the material is considered to be opaque and alpha values are ignored. Default is [page:Textures THREE.RGBAFormat].
+</p>
 
 
 <h3>[property:Boolean stencilWrite]</h3>
 <h3>[property:Boolean stencilWrite]</h3>
 <p>
 <p>

+ 0 - 1
docs/api/zh/materials/ShaderMaterial.html

@@ -81,7 +81,6 @@
 		<h2>例子</h2>
 		<h2>例子</h2>
 
 
 		<p>
 		<p>
-			[example:webgl_animation_cloth webgl / animation / cloth ]<br />
 			[example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]<br />
 			[example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]<br />
 			[example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]<br />
 			[example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]<br />
 			[example:webgl_custom_attributes webgl / custom / attributes]<br />
 			[example:webgl_custom_attributes webgl / custom / attributes]<br />

+ 11 - 0
docs/api/zh/math/Triangle.html

@@ -123,6 +123,17 @@
 			请注意,此方法仅复制给定对象的值。
 			请注意,此方法仅复制给定对象的值。
 		</p>
 		</p>
 
 
+		<h3>[method:Triangle setFromAttributeAndIndices]( [param:BufferAttribute attribute], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) [param:Triangle this]</h3>
+		<p>
+		attribute - [page:BufferAttribute] of vertex data <br />
+		i0 - [page:Integer] index <br />
+		i1 - [page:Integer] index <br />
+		i2 - [page:Integer] index<br /><br />
+
+		Sets the triangle's vertices from the buffer attribute vertex data.
+		</p>
+
+
 		<h3>[method:Triangle setFromPointsAndIndices]( [param:Array points], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) [param:Triangle this]</h3>
 		<h3>[method:Triangle setFromPointsAndIndices]( [param:Array points], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) [param:Triangle this]</h3>
 		<p>
 		<p>
 		points - [page:Vector3]数组([page:Array]) <br />
 		points - [page:Vector3]数组([page:Array]) <br />

+ 4 - 0
docs/api/zh/math/Vector2.html

@@ -33,6 +33,10 @@
 			其他的一些事物也可以使用二维向量进行表示,比如说动量矢量、复数等等;但以上这些是它在three.js中的常用用途。
 			其他的一些事物也可以使用二维向量进行表示,比如说动量矢量、复数等等;但以上这些是它在three.js中的常用用途。
 		</p>
 		</p>
 
 
+		<p>
+			Iterating through a Vector2 instance will yield its components (x, y) in the corresponding order.
+		</p>
+		
 		<h2>代码示例</h2>
 		<h2>代码示例</h2>
 
 
 		<code>
 		<code>

+ 3 - 0
docs/api/zh/math/Vector3.html

@@ -46,6 +46,9 @@
 		const d = a.distanceTo( b );
 		const d = a.distanceTo( b );
 		</code>
 		</code>
 
 
+		<p>
+			Iterating through a Vector3 instance will yield its components (x, y, z) in the corresponding order.
+		</p>
 
 
 		<h2>构造函数</h2>
 		<h2>构造函数</h2>
 
 

+ 3 - 0
docs/api/zh/math/Vector4.html

@@ -45,6 +45,9 @@
 		const d = a.dot( b );
 		const d = a.dot( b );
 		</code>
 		</code>
 
 
+		<p>
+			Iterating through a Vector4 instance will yield its components (x, y, z, w) in the corresponding order.
+		</p>
 
 
 		<h2>构造函数</h2>
 		<h2>构造函数</h2>
 
 

+ 266 - 0
docs/examples/en/controls/ArcballControls.html

@@ -0,0 +1,266 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<p class="desc">
+		Arcball controls allow the camera to be controlled by a virtual trackball with full touch support and advanced navigation functionality. <br>
+		Cursor/finger positions and movements are mapped over a virtual trackball surface
+		represented by a gizmo and mapped in intuitive and consistent camera movements. 
+		Dragging cursor/fingers will cause camera to orbit around the center of the trackball in a conservative way (returning to the starting point
+		will make the camera to return to its starting orientation).<br><br>
+		
+		In addition to supporting pan, zoom and pinch gestures, Arcball controls provide <i>focus</i> functionality with a double click/tap for
+		intuitively moving the object's point of interest in the center of the virtual trackball.   
+		Focus allows a much better inspection and navigation in complex environment. 
+		Moreover Arcball controls allow FOV manipulation (in a vertigo-style method) and z-rotation.
+		Saving and restoring of Camera State is supported also through clipboard 
+		(use ctrl+c and ctrl+v shortcuts for copy and paste the state).<br><br>
+		
+		Unlike [page:OrbitControls] and [page:TrackballControls], [name] doesn't require [page:.update] to be called externally in an animation loop when animations
+		are on.<br><br>
+
+
+		To use this, as with all files in the /examples directory, you will have to
+		include the file separately in your HTML.
+
+		</p>
+
+		<h2>Code Example</h2>
+
+		<code>
+		const renderer = new THREE.WebGLRenderer();
+		renderer.setSize( window.innerWidth, window.innerHeight );
+		document.body.appendChild( renderer.domElement );
+
+		const scene = new THREE.Scene();
+
+		const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
+
+		const controls = new ArcballControls( camera, renderer.domElement, scene );
+
+		controls.addEventListener( 'change', function () {
+
+			renderer.render( scene, camera );
+
+		} );
+
+		//controls.update() must be called after any manual changes to the camera's transform
+		camera.position.set( 0, 20, 100 );
+		controls.update();
+		</code>
+
+		<h2>Examples</h2>
+
+		<p>[example:misc_controls_arcball misc / controls / arcball ]</p>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [param:Camera camera], [param:HTMLDOMElement domElement], [param:Scene scene] )</h3>
+		<p>
+			[page:Camera camera]: (required) The camera to be controlled. The camera must not be a child of another object, unless that object is the scene itself.<br><br>
+
+			[page:HTMLDOMElement domElement]: The HTML element used for event listeners.<br><br>
+
+			[page:Scene scene]: The scene rendered by the camera. If not given, gizmos cannot be shown.
+		</p>
+
+		<h2>Events</h2>
+
+		<h3>change</h3>
+		<p>
+			Fires when the camera has been transformed by the controls.
+		</p>
+
+		<h3>start</h3>
+		<p>
+			Fires when an interaction was initiated.
+		</p>
+
+		<h3>end</h3>
+		<p>
+			Fires when an interaction has finished.
+		</p>
+
+		<h2>Properties</h2>
+
+		<h3>[property:Boolean adjustNearFar]</h3>
+		<p>
+			If true, camera's near and far values will be adjusted every time zoom is performed trying to mantain the same visible portion
+			given by initial near and far values ( [page:PerspectiveCamera] only ).
+			Default is false.
+		</p>
+
+		<h3>[property:Camera camera]</h3>
+		<p>
+			The camera being controlled.
+		</p>
+
+		<h3>[property:Boolean cursorZoom]</h3>
+		<p>
+			Set to true to make zoom become cursor centered.
+		</p>
+
+		<h3>
+			[property:Float dampingFactor]</h3>
+		<p>
+			The damping inertia used if [page:.enableAnimations] is set to true.
+		</p>
+
+		<h3>[property:HTMLDOMElement domElement]</h3>
+		<p>
+			The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will
+			not set up new event listeners.
+		</p>
+
+		<h3>[property:Boolean enabled]</h3>
+		<p>
+			When set to *false*, the controls will not respond to user input. Default is *true*.
+		</p>
+
+		<h3>[property:Boolean enableAnimations]</h3>
+		<p>
+			Set to true to enable animations for rotation (damping) and focus operation. Default is true.
+		</p>
+
+		<h3>[property:Boolean enableGrid]</h3>
+		<p>
+			When set to true, a grid will appear when panning operation is being performed (desktop interaction only). Default is false.
+		</p>
+
+		<h3>[property:Boolean enablePan]</h3>
+		<p>
+			Enable or disable camera panning. Default is true.
+		</p>
+
+		<h3>[property:Boolean enableRotate]</h3>
+		<p>
+			Enable or disable camera rotation. Default is true.
+		</p>
+
+		<h3>[property:Boolean enableZoom]</h3>
+		<p>
+			Enable or disable zooming of the camera.
+		</p>
+
+		<h3>[property:Float focusAnimationTime]</h3>
+		<p>
+			Duration time of focus animation.
+		</p>
+
+		<h3>[property:Float maxDistance]</h3>
+		<p>
+			How far you can dolly out ( [page:PerspectiveCamera] only ). Default is Infinity.
+		</p>
+
+		<h3>[property:Float maxZoom]</h3>
+		<p>
+			How far you can zoom out ( [page:OrthographicCamera] only ). Default is Infinity.
+		</p>
+
+		<h3>[property:Float minDistance]</h3>
+		<p>
+			 How far you can dolly in ( [page:PerspectiveCamera] only ). Default is 0.
+		</p>
+
+		<h3>[property:Float minZoom]</h3>
+		<p>
+			How far you can zoom in ( [page:OrthographicCamera] only ). Default is 0.
+		</p>
+
+		<h3>[property:Float scaleFactor]</h3>
+		<p>
+			The scaling factor used when performing zoom operation.
+		</p>
+
+		<h3>[property:Scene scene]</h3>
+		<p>
+			The scene rendered by the camera.
+		</p>
+
+		<h3>[property:Float wMax]</h3>
+		<p>
+			Maximum angular velocity allowed on rotation animation start.
+		</p>
+
+
+		<h2>Methods</h2>
+
+		<h3>[method:null activateGizmos] ( [param:Boolean isActive] )</h3>
+		<p>
+			Make gizmos more or less visible.
+		</p>
+
+		<h3>[method:null copyState] ()</h3>
+		<p>
+			Copy the current state to clipboard (as a readable JSON text).
+		</p>
+
+		<h3>[method:null dispose] ()</h3>
+		<p>
+			Remove all the event listeners, cancel any pending animation and clean the scene from gizmos and grid.
+		</p>
+
+		<h3>[method:null pasteState] ()</h3>
+		<p>
+			Set the controls state from the clipboard, assumes that the clipboard stores a JSON text as saved from [page:.copyState].
+		</p>
+
+		<h3>[method:null reset] ()</h3>
+		<p>
+			Reset the controls to their state from either the last time the [page:.saveState] was called, or the initial state.
+		</p>
+
+		<h3>[method:null saveState] ()</h3>
+		<p>
+			Save the current state of the controls. This can later be recovered with [page:.reset].
+		</p>
+
+		<h3>[method:null setCamera] ( [param:Camera camera] )</h3>
+		<p>
+			Set the camera to be controlled. Must be called in order to set a new camera to be controlled.
+		</p>
+
+		<h3>[method:null setGizmosVisible] ( [param:Boolean value] )</h3>
+		<p>
+			Set the visible property of gizmos.
+		</p>
+
+		<h3>[method:Boolean setMouseAction] ( [param:String operation], mouse, key )</h3>
+		<p>
+			Set a new mouse action by specifying the operation to be performed and a mouse/key combination. In case of conflict, replaces the existing one.<br><br>
+			Operations can be specified as 'ROTATE', 'PAN', 'FOV' or 'ZOOM'.<br>
+			Mouse inputs can be specified as mouse buttons 0, 1 and 2 or 'WHEEL' for wheel notches.<br>
+			Keyboard modifiers can be specified as 'CTRL', 'SHIFT' or null if not needed.
+		</p>
+
+		<h3>[method:null setTarget] ( [param:Float x], [param:Float y], [param:Float z] )</h3>
+		<p>
+			Set the trackball center point.
+		</p>
+
+		<h3>[method:Boolean unsetMouseAction] ( mouse, key )</h3>
+		<p>
+			Removes a mouse action by specifying its mouse/key combination.<br><br>
+			Mouse inputs can be specified as mouse buttons 0, 1 and 2 or 'WHEEL' for wheel notches.<br>
+			Keyboard modifiers can be specified as 'CTRL', 'SHIFT' or null if not needed.
+		</p>
+		
+		<h3>[method:null update] ()</h3>
+		<p>
+			Update the controls. Must be called after any manual changes to the camera's transform.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/OrbitControls.js examples/jsm/controls/ArcballControls.js]
+		</p>
+	</body>
+</html>

+ 4 - 6
docs/api/en/geometries/ParametricGeometry.html → docs/examples/en/geometries/ParametricGeometry.html

@@ -13,8 +13,6 @@
 
 
 		<p class="desc">Generate geometry representing a parametric surface.</p>
 		<p class="desc">Generate geometry representing a parametric surface.</p>
 
 
-		<iframe id="scene" src="scenes/geometry-browser.html#ParametricGeometry"></iframe>
-
 		<script>
 		<script>
 
 
 		// iOS iframe auto-resize workaround
 		// iOS iframe auto-resize workaround
@@ -46,9 +44,9 @@
 
 
 		<h3>[name]([param:Function func], [param:Integer slices], [param:Integer stacks])</h3>
 		<h3>[name]([param:Function func], [param:Integer slices], [param:Integer stacks])</h3>
 		<p>
 		<p>
-		func — A function that takes in a [page:Float u] and [page:Float v] value each between 0 and 1 and modifies a third [page:Vector3] argument<br />
-		slices — The count of slices to use for the parametric function <br />
-		stacks — The count of stacks to use for the parametric function
+		func — A function that takes in a [page:Float u] and [page:Float v] value each between 0 and 1 and modifies a third [page:Vector3] argument. Default is a function that generates a curved plane surface.<br />
+		slices — The count of slices to use for the parametric function. Default is *8*.<br />
+		stacks — The count of stacks to use for the parametric function. Default is *8*.
 		</p>
 		</p>
 
 
 		<h2>Properties</h2>
 		<h2>Properties</h2>
@@ -65,7 +63,7 @@
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
 		<p>
 		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/ParametricGeometry.js examples/jsm/geometries/ParametricGeometry.js]
 		</p>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

+ 6 - 8
docs/api/en/geometries/TextGeometry.html → docs/examples/en/geometries/TextGeometry.html

@@ -12,13 +12,11 @@
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
 		<p class="desc">
 		<p class="desc">
-			A class for generating text as a single geometry. It is constructed by providing a string of text, and a hash of
-			parameters consisting of a loaded [page:Font] and settings for the geometry's parent [page:ExtrudeGeometry].
-			See the [page:Font], [page:FontLoader] and [page:Creating_Text] pages for additional details.
+			A class for generating text as a single geometry. It is constructed by providing a string of text, and a set of
+			parameters consisting of a loaded font and settings for the geometry's parent [page:ExtrudeGeometry].
+			See the [page:FontLoader] page for additional details.
 		</p>
 		</p>
 
 
-		<iframe id="scene" src="scenes/geometry-browser.html#TextGeometry"></iframe>
-
 		<script>
 		<script>
 
 
 		// iOS iframe auto-resize workaround
 		// iOS iframe auto-resize workaround
@@ -38,11 +36,11 @@
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 
 
 		<code>
 		<code>
-		const loader = new THREE.FontLoader();
+		const loader = new FontLoader();
 
 
 		loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
 		loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
 
 
-			const geometry = new THREE.TextGeometry( 'Hello three.js!', {
+			const geometry = new TextGeometry( 'Hello three.js!', {
 				font: font,
 				font: font,
 				size: 80,
 				size: 80,
 				height: 5,
 				height: 5,
@@ -170,7 +168,7 @@
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
 		<p>
 		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/TextGeometry.js examples/jsm/geometries/TextGeometry.js]
 		</p>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

+ 6 - 6
docs/api/en/loaders/FontLoader.html → docs/examples/en/loaders/FontLoader.html

@@ -12,7 +12,7 @@
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
 		<p class="desc">
 		<p class="desc">
-		Class for loading a font in JSON format. Returns a [page:Font Font], which is an
+		Class for loading a font in JSON format. Returns a font, which is an
 		array of [page:Shape Shapes] representing the font.
 		array of [page:Shape Shapes] representing the font.
 		This uses the [page:FileLoader] internally for loading files. <br /><br />
 		This uses the [page:FileLoader] internally for loading files. <br /><br />
 
 
@@ -22,7 +22,7 @@
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 
 
 		<code>
 		<code>
-		const loader = new THREE.FontLoader();
+		const loader = new FontLoader();
 		const font = loader.load(
 		const font = loader.load(
 			// resource URL
 			// resource URL
 			'fonts/helvetiker_bold.typeface.json',
 			'fonts/helvetiker_bold.typeface.json',
@@ -30,7 +30,7 @@
 			// onLoad callback
 			// onLoad callback
 			function ( font ) {
 			function ( font ) {
 				// do something with the font
 				// do something with the font
-				scene.add( font );
+				console.log( font );
 			},
 			},
 
 
 			// onProgress callback
 			// onProgress callback
@@ -70,7 +70,7 @@
 		<p>
 		<p>
 		[page:String url] — the path or URL to the file. This can also be a
 		[page:String url] — the path or URL to the file. This can also be a
 			[link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].<br />
 			[link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].<br />
-		[page:Function onLoad] — Will be called when load completes. The argument will be the loaded [page:Font font].<br />
+		[page:Function onLoad] — Will be called when load completes. The argument will be the loaded font.<br />
 		[page:Function onProgress] — Will be called while load progresses. The argument will be the XMLHttpRequest instance, which contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
 		[page:Function onProgress] — Will be called while load progresses. The argument will be the XMLHttpRequest instance, which contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
 		[page:Function onError] — Will be called when load errors.<br /><br />
 		[page:Function onError] — Will be called when load errors.<br /><br />
 
 
@@ -80,13 +80,13 @@
 		<h3>[method:Font parse]( [param:Object json] )</h3>
 		<h3>[method:Font parse]( [param:Object json] )</h3>
 		<p>
 		<p>
 		[page:Object json] — The <em>JSON</em> structure to parse.<br /><br />
 		[page:Object json] — The <em>JSON</em> structure to parse.<br /><br />
-		Parse a <em>JSON</em> structure and return a [page:Font].
+		Parse a <em>JSON</em> structure and return a font.
 		</p>
 		</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
 		<p>
 		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/FontLoader.js examples/jsm/loaders/FontLoader.js]
 		</p>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

+ 4 - 6
docs/api/zh/geometries/ParametricGeometry.html → docs/examples/zh/geometries/ParametricGeometry.html

@@ -13,8 +13,6 @@
 
 
 		<p class="desc">生成由参数表示其表面的几何体。</p>
 		<p class="desc">生成由参数表示其表面的几何体。</p>
 
 
-		<iframe id="scene" src="scenes/geometry-browser.html#ParametricGeometry"></iframe>
-
 		<script>
 		<script>
 
 
 		// iOS iframe auto-resize workaround
 		// iOS iframe auto-resize workaround
@@ -46,9 +44,9 @@
 
 
 		<h3>[name]([param:Function func], [param:Integer slices], [param:Integer stacks])</h3>
 		<h3>[name]([param:Function func], [param:Integer slices], [param:Integer stacks])</h3>
 		<p>
 		<p>
-		func — A function that takes in a [page:Float u] and [page:Float v] value each between 0 and 1 and modifies a third [page:Vector3] argument<br />
-		slices — The count of slices to use for the parametric function <br />
-		stacks — The count of stacks to use for the parametric function
+		func — A function that takes in a [page:Float u] and [page:Float v] value each between 0 and 1 and modifies a third [page:Vector3] argument. Default is a function that generates a curved plane surface.<br />
+		slices — The count of slices to use for the parametric function. Default is *8*.<br />
+		stacks — The count of stacks to use for the parametric function. Default is *8*.
 		</p>
 		</p>
 
 
 		<h2>属性</h2>
 		<h2>属性</h2>
@@ -65,7 +63,7 @@
 		<h2>方法</h2>
 		<h2>方法</h2>
 
 
 		<p>
 		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/ParametricGeometry.js examples/jsm/geometries/ParametricGeometry.js]
 		</p>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

+ 5 - 7
docs/api/zh/geometries/TextGeometry.html → docs/examples/zh/geometries/TextGeometry.html

@@ -13,12 +13,10 @@
 
 
 		<p class="desc">
 		<p class="desc">
 				一个用于将文本生成为单一的几何体的类。
 				一个用于将文本生成为单一的几何体的类。
-				它是由一串给定的文本,以及由加载的[page:Font](字体)和该几何体[page:ExtrudeGeometry]父类中的设置所组成的参数来构造的。
-				请参阅[page:Font]、[page:FontLoader]和[page:Creating_Text]页面来查看更多详细信息。
+				它是由一串给定的文本,以及由加载的font(字体)和该几何体[page:ExtrudeGeometry]父类中的设置所组成的参数来构造的。
+				请参阅[page:FontLoader]页面来查看更多详细信息。
 			</p>
 			</p>
 
 
-		<iframe id="scene" src="scenes/geometry-browser.html#TextGeometry"></iframe>
-
 		<script>
 		<script>
 
 
 		// iOS iframe auto-resize workaround
 		// iOS iframe auto-resize workaround
@@ -38,11 +36,11 @@
 		<h2>代码示例</h2>
 		<h2>代码示例</h2>
 
 
 		<code>
 		<code>
-		const loader = new THREE.FontLoader();
+		const loader = new FontLoader();
 
 
 		loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
 		loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
 
 
-			const geometry = new THREE.TextGeometry( 'Hello three.js!', {
+			const geometry = new TextGeometry( 'Hello three.js!', {
 				font: font,
 				font: font,
 				size: 80,
 				size: 80,
 				height: 5,
 				height: 5,
@@ -168,7 +166,7 @@
 		<h2>源代码</h2>
 		<h2>源代码</h2>
 
 
 		<p>
 		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/TextGeometry.js examples/jsm/geometries/TextGeometry.js]
 		</p>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

+ 5 - 5
docs/api/zh/loaders/FontLoader.html → docs/examples/zh/loaders/FontLoader.html

@@ -12,7 +12,7 @@
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
 		<p class="desc">
 		<p class="desc">
-		使用JSON格式中加载字体的一个类。返回[page:Font Font], 返回值是表示字体的[page:Shape Shape]类型的数组。
+		使用JSON格式中加载字体的一个类。返回font, 返回值是表示字体的[page:Shape Shape]类型的数组。
 		其内部使用[page:FileLoader]来加载文件。 <br /><br />
 		其内部使用[page:FileLoader]来加载文件。 <br /><br />
 
 
 		你可以使用[link:https://gero3.github.io/facetype.js/ facetype.js]在线转换字体。
 		你可以使用[link:https://gero3.github.io/facetype.js/ facetype.js]在线转换字体。
@@ -21,7 +21,7 @@
 		<h2>代码示例</h2>
 		<h2>代码示例</h2>
 
 
 		<code>
 		<code>
-		const loader = new THREE.FontLoader();
+		const loader = new FontLoader();
 		const font = loader.load(
 		const font = loader.load(
 			// 资源URL
 			// 资源URL
 			'fonts/helvetiker_bold.typeface.json',
 			'fonts/helvetiker_bold.typeface.json',
@@ -29,7 +29,7 @@
 			// onLoad回调
 			// onLoad回调
 			function ( font ) {
 			function ( font ) {
 				// do something with the font
 				// do something with the font
-				scene.add( font );
+				console.log( font );
 			},
 			},
 
 
 			// onProgress回调
 			// onProgress回调
@@ -79,13 +79,13 @@
 		<h3>[method:Font parse]( [param:Object json] )</h3>
 		<h3>[method:Font parse]( [param:Object json] )</h3>
 		<p>
 		<p>
 		[page:Object json] — The <em>JSON</em> structure to parse.<br /><br />
 		[page:Object json] — The <em>JSON</em> structure to parse.<br /><br />
-		以<em>JSON</em>格式进行解析,并返回一个[page:Font].
+		以<em>JSON</em>格式进行解析,并返回一个font.
 		</p>
 		</p>
 
 
 		<h2>源</h2>
 		<h2>源</h2>
 
 
 		<p>
 		<p>
-			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/FontLoader.js examples/jsm/loaders/FontLoader.js]
 		</p>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

+ 27 - 1
docs/index.html

@@ -32,7 +32,7 @@
 
 
 				<div id="sections">
 				<div id="sections">
 					<span class="selected">docs</span>
 					<span class="selected">docs</span>
-					<a href="../examples/#webgl_animation_cloth">examples</a>
+					<a href="../examples/#webgl_animation_keyframes">examples</a>
 				</div>
 				</div>
 
 
 				<div id="expandButton"></div>
 				<div id="expandButton"></div>
@@ -69,6 +69,9 @@
 		const filterInput = document.getElementById( 'filterInput' );
 		const filterInput = document.getElementById( 'filterInput' );
 		let iframe = document.querySelector( 'iframe' );
 		let iframe = document.querySelector( 'iframe' );
 
 
+		const sectionLink = document.querySelector( '#sections > a' );
+		const sectionDefaultHref = sectionLink.href;
+
 		const pageProperties = {};
 		const pageProperties = {};
 		const titles = {};
 		const titles = {};
 		const categoryElements = [];
 		const categoryElements = [];
@@ -199,6 +202,10 @@
 
 
 				updateFilter();
 				updateFilter();
 
 
+			} else {
+
+				updateLink( '' );
+
 			}
 			}
 
 
 		}
 		}
@@ -401,6 +408,25 @@
 
 
 			displayFilteredPanel();
 			displayFilteredPanel();
 
 
+			updateLink( v );
+
+		}
+
+		function updateLink( search ) {
+
+			// update examples link
+
+			if ( search ) {
+
+				let link = sectionLink.href.split( /[?#]/ )[ 0 ];
+				sectionLink.href = `${link}?q=${search}`;
+
+			} else {
+
+				sectionLink.href = sectionDefaultHref;
+
+			}
+
 		}
 		}
 
 
 		function displayFilteredPanel() {
 		function displayFilteredPanel() {

+ 9 - 10
docs/list.json

@@ -113,7 +113,6 @@
 			"Extras / Core": {
 			"Extras / Core": {
 				"Curve": "api/en/extras/core/Curve",
 				"Curve": "api/en/extras/core/Curve",
 				"CurvePath": "api/en/extras/core/CurvePath",
 				"CurvePath": "api/en/extras/core/CurvePath",
-				"Font": "api/en/extras/core/Font",
 				"Interpolations": "api/en/extras/core/Interpolations",
 				"Interpolations": "api/en/extras/core/Interpolations",
 				"Path": "api/en/extras/core/Path",
 				"Path": "api/en/extras/core/Path",
 				"Shape": "api/en/extras/core/Shape",
 				"Shape": "api/en/extras/core/Shape",
@@ -148,14 +147,12 @@
 				"IcosahedronGeometry": "api/en/geometries/IcosahedronGeometry",
 				"IcosahedronGeometry": "api/en/geometries/IcosahedronGeometry",
 				"LatheGeometry": "api/en/geometries/LatheGeometry",
 				"LatheGeometry": "api/en/geometries/LatheGeometry",
 				"OctahedronGeometry": "api/en/geometries/OctahedronGeometry",
 				"OctahedronGeometry": "api/en/geometries/OctahedronGeometry",
-				"ParametricGeometry": "api/en/geometries/ParametricGeometry",
 				"PlaneGeometry": "api/en/geometries/PlaneGeometry",
 				"PlaneGeometry": "api/en/geometries/PlaneGeometry",
 				"PolyhedronGeometry": "api/en/geometries/PolyhedronGeometry",
 				"PolyhedronGeometry": "api/en/geometries/PolyhedronGeometry",
 				"RingGeometry": "api/en/geometries/RingGeometry",
 				"RingGeometry": "api/en/geometries/RingGeometry",
 				"ShapeGeometry": "api/en/geometries/ShapeGeometry",
 				"ShapeGeometry": "api/en/geometries/ShapeGeometry",
 				"SphereGeometry": "api/en/geometries/SphereGeometry",
 				"SphereGeometry": "api/en/geometries/SphereGeometry",
 				"TetrahedronGeometry": "api/en/geometries/TetrahedronGeometry",
 				"TetrahedronGeometry": "api/en/geometries/TetrahedronGeometry",
-				"TextGeometry": "api/en/geometries/TextGeometry",
 				"TorusGeometry": "api/en/geometries/TorusGeometry",
 				"TorusGeometry": "api/en/geometries/TorusGeometry",
 				"TorusKnotGeometry": "api/en/geometries/TorusKnotGeometry",
 				"TorusKnotGeometry": "api/en/geometries/TorusKnotGeometry",
 				"TubeGeometry": "api/en/geometries/TubeGeometry",
 				"TubeGeometry": "api/en/geometries/TubeGeometry",
@@ -207,7 +204,6 @@
 				"CubeTextureLoader": "api/en/loaders/CubeTextureLoader",
 				"CubeTextureLoader": "api/en/loaders/CubeTextureLoader",
 				"DataTextureLoader": "api/en/loaders/DataTextureLoader",
 				"DataTextureLoader": "api/en/loaders/DataTextureLoader",
 				"FileLoader": "api/en/loaders/FileLoader",
 				"FileLoader": "api/en/loaders/FileLoader",
-				"FontLoader": "api/en/loaders/FontLoader",
 				"ImageBitmapLoader": "api/en/loaders/ImageBitmapLoader",
 				"ImageBitmapLoader": "api/en/loaders/ImageBitmapLoader",
 				"ImageLoader": "api/en/loaders/ImageLoader",
 				"ImageLoader": "api/en/loaders/ImageLoader",
 				"Loader": "api/en/loaders/Loader",
 				"Loader": "api/en/loaders/Loader",
@@ -338,6 +334,7 @@
 			},
 			},
 
 
 			"Controls": {
 			"Controls": {
+				"ArcballControls": "examples/en/controls/ArcballControls",
 				"DeviceOrientationControls": "examples/en/controls/DeviceOrientationControls",
 				"DeviceOrientationControls": "examples/en/controls/DeviceOrientationControls",
 				"DragControls": "examples/en/controls/DragControls",
 				"DragControls": "examples/en/controls/DragControls",
 				"FirstPersonControls": "examples/en/controls/FirstPersonControls",
 				"FirstPersonControls": "examples/en/controls/FirstPersonControls",
@@ -350,7 +347,9 @@
 
 
 			"Geometries": {
 			"Geometries": {
 				"ConvexGeometry": "examples/en/geometries/ConvexGeometry",
 				"ConvexGeometry": "examples/en/geometries/ConvexGeometry",
-				"DecalGeometry": "examples/en/geometries/DecalGeometry"
+				"DecalGeometry": "examples/en/geometries/DecalGeometry",
+				"ParametricGeometry": "examples/en/geometries/ParametricGeometry",
+				"TextGeometry": "examples/en/geometries/TextGeometry"
 			},
 			},
 
 
 			"Helpers": {
 			"Helpers": {
@@ -369,6 +368,7 @@
 				"3DMLoader": "examples/en/loaders/3DMLoader",
 				"3DMLoader": "examples/en/loaders/3DMLoader",
 				"BasisTextureLoader": "examples/en/loaders/BasisTextureLoader",
 				"BasisTextureLoader": "examples/en/loaders/BasisTextureLoader",
 				"DRACOLoader": "examples/en/loaders/DRACOLoader",
 				"DRACOLoader": "examples/en/loaders/DRACOLoader",
+				"FontLoader": "examples/en/loaders/FontLoader",
 				"GLTFLoader": "examples/en/loaders/GLTFLoader",
 				"GLTFLoader": "examples/en/loaders/GLTFLoader",
 				"KTX2Loader": "examples/en/loaders/KTX2Loader",
 				"KTX2Loader": "examples/en/loaders/KTX2Loader",
 				"MMDLoader": "examples/en/loaders/MMDLoader",
 				"MMDLoader": "examples/en/loaders/MMDLoader",
@@ -622,7 +622,6 @@
 			"附件 / 核心": {
 			"附件 / 核心": {
 				"Curve": "api/zh/extras/core/Curve",
 				"Curve": "api/zh/extras/core/Curve",
 				"CurvePath": "api/zh/extras/core/CurvePath",
 				"CurvePath": "api/zh/extras/core/CurvePath",
-				"Font": "api/zh/extras/core/Font",
 				"Interpolations": "api/zh/extras/core/Interpolations",
 				"Interpolations": "api/zh/extras/core/Interpolations",
 				"Path": "api/zh/extras/core/Path",
 				"Path": "api/zh/extras/core/Path",
 				"Shape": "api/zh/extras/core/Shape",
 				"Shape": "api/zh/extras/core/Shape",
@@ -657,14 +656,12 @@
 				"IcosahedronGeometry": "api/zh/geometries/IcosahedronGeometry",
 				"IcosahedronGeometry": "api/zh/geometries/IcosahedronGeometry",
 				"LatheGeometry": "api/zh/geometries/LatheGeometry",
 				"LatheGeometry": "api/zh/geometries/LatheGeometry",
 				"OctahedronGeometry": "api/zh/geometries/OctahedronGeometry",
 				"OctahedronGeometry": "api/zh/geometries/OctahedronGeometry",
-				"ParametricGeometry": "api/zh/geometries/ParametricGeometry",
 				"PlaneGeometry": "api/zh/geometries/PlaneGeometry",
 				"PlaneGeometry": "api/zh/geometries/PlaneGeometry",
 				"PolyhedronGeometry": "api/zh/geometries/PolyhedronGeometry",
 				"PolyhedronGeometry": "api/zh/geometries/PolyhedronGeometry",
 				"RingGeometry": "api/zh/geometries/RingGeometry",
 				"RingGeometry": "api/zh/geometries/RingGeometry",
 				"ShapeGeometry": "api/zh/geometries/ShapeGeometry",
 				"ShapeGeometry": "api/zh/geometries/ShapeGeometry",
 				"SphereGeometry": "api/zh/geometries/SphereGeometry",
 				"SphereGeometry": "api/zh/geometries/SphereGeometry",
 				"TetrahedronGeometry": "api/zh/geometries/TetrahedronGeometry",
 				"TetrahedronGeometry": "api/zh/geometries/TetrahedronGeometry",
-				"TextGeometry": "api/zh/geometries/TextGeometry",
 				"TorusGeometry": "api/zh/geometries/TorusGeometry",
 				"TorusGeometry": "api/zh/geometries/TorusGeometry",
 				"TorusKnotGeometry": "api/zh/geometries/TorusKnotGeometry",
 				"TorusKnotGeometry": "api/zh/geometries/TorusKnotGeometry",
 				"TubeGeometry": "api/zh/geometries/TubeGeometry",
 				"TubeGeometry": "api/zh/geometries/TubeGeometry",
@@ -716,7 +713,6 @@
 				"CubeTextureLoader": "api/zh/loaders/CubeTextureLoader",
 				"CubeTextureLoader": "api/zh/loaders/CubeTextureLoader",
 				"DataTextureLoader": "api/zh/loaders/DataTextureLoader",
 				"DataTextureLoader": "api/zh/loaders/DataTextureLoader",
 				"FileLoader": "api/zh/loaders/FileLoader",
 				"FileLoader": "api/zh/loaders/FileLoader",
-				"FontLoader": "api/zh/loaders/FontLoader",
 				"ImageBitmapLoader": "api/zh/loaders/ImageBitmapLoader",
 				"ImageBitmapLoader": "api/zh/loaders/ImageBitmapLoader",
 				"ImageLoader": "api/zh/loaders/ImageLoader",
 				"ImageLoader": "api/zh/loaders/ImageLoader",
 				"Loader": "api/zh/loaders/Loader",
 				"Loader": "api/zh/loaders/Loader",
@@ -859,7 +855,9 @@
 
 
 			"几何体": {
 			"几何体": {
 				"ConvexGeometry": "examples/zh/geometries/ConvexGeometry",
 				"ConvexGeometry": "examples/zh/geometries/ConvexGeometry",
-				"DecalGeometry": "examples/zh/geometries/DecalGeometry"
+				"DecalGeometry": "examples/zh/geometries/DecalGeometry",
+				"ParametricGeometry": "examples/zh/geometries/ParametricGeometry",
+				"TextGeometry": "examples/zh/geometries/TextGeometry"
 			},
 			},
 
 
 			"辅助对象": {
 			"辅助对象": {
@@ -877,6 +875,7 @@
 			"加载器": {
 			"加载器": {
 				"BasisTextureLoader": "examples/zh/loaders/BasisTextureLoader",
 				"BasisTextureLoader": "examples/zh/loaders/BasisTextureLoader",
 				"DRACOLoader": "examples/zh/loaders/DRACOLoader",
 				"DRACOLoader": "examples/zh/loaders/DRACOLoader",
+				"FontLoader": "examples/zh/loaders/FontLoader",
 				"GLTFLoader": "examples/zh/loaders/GLTFLoader",
 				"GLTFLoader": "examples/zh/loaders/GLTFLoader",
 				"MMDLoader": "examples/zh/loaders/MMDLoader",
 				"MMDLoader": "examples/zh/loaders/MMDLoader",
 				"MTLLoader": "examples/zh/loaders/MTLLoader",
 				"MTLLoader": "examples/zh/loaders/MTLLoader",

+ 3 - 3
docs/manual/ko/buildTools/Testing-with-NPM.html

@@ -131,7 +131,7 @@ $ npm install three --save-dev
  $ npm install [email protected] --save
  $ npm install [email protected] --save
 							</code>
 							</code>
                             (이 예제에서는 0.84.0 버전을 사용했습니다.). --save 는 dev 설정이 아닌 이 프로젝트의 의존성으로 추가하는 명령어입니다.
                             (이 예제에서는 0.84.0 버전을 사용했습니다.). --save 는 dev 설정이 아닌 이 프로젝트의 의존성으로 추가하는 명령어입니다.
-                            [link:https://www.npmjs.org/doc/json.html 여기]에서 더 많은 내용을 확인하세요.
+                            여기([link:https://www.npmjs.org/doc/json.html link])에서 더 많은 내용을 확인하세요.
 						</li>
 						</li>
 					</ul>
 					</ul>
 				</li>
 				</li>
@@ -181,12 +181,12 @@ The THREE object should be able to construct a Vector3 with default of x=0: 0ms
 			<ol>
 			<ol>
 				<li>
 				<li>
 					자신의 코드의 예상 결과가 들어있는 예제를 만들어, test/ 폴더 안에 두세요.
 					자신의 코드의 예상 결과가 들어있는 예제를 만들어, test/ 폴더 안에 두세요.
-					[link:https://github.com/air/encounter/blob/master/test/Physics-test.js 여기]에서 진짜 프로젝트의 예제를 확인할 수 있습니다.
+					여기([link:https://github.com/air/encounter/blob/master/test/Physics-test.js link])에서 진짜 프로젝트의 예제를 확인할 수 있습니다.
 				</li>
 				</li>
 
 
 				<li>
 				<li>
                     nodeJS에서 알아볼 수 있는, require를 사용해 기능들을 내보내기 하세요. 
                     nodeJS에서 알아볼 수 있는, require를 사용해 기능들을 내보내기 하세요. 
-                    [link:https://github.com/air/encounter/blob/master/js/Physics.js 여기]를 참고하세요.
+                    여기([link:https://github.com/air/encounter/blob/master/js/Physics.js link])를 참고하세요.
 				</li>
 				</li>
 
 
 				<li>
 				<li>

+ 3 - 4
docs/manual/ko/introduction/Animation-system.html

@@ -13,15 +13,14 @@
 
 
 		<p class="desc">
 		<p class="desc">
             three.js 애니메이션 시스템에서는 모델의 다양한 속성을 설정할 수 있습니다:
             three.js 애니메이션 시스템에서는 모델의 다양한 속성을 설정할 수 있습니다:
-            [page:SkinnedMesh 스킨 및 리깅 모델], 모프타깃의 골자, 서로 다른 재질의 속성(색상, 불투명도, 참/거짓 연산),
+            [page:SkinnedMesh skinned and rigged model]의 뼈, 모프타깃, 서로 다른 재질의 속성(색상, 불투명도, 참/거짓 연산),
             가시성과 변환이 그 예입니다. 애니메이션의 속성은 페이드 아웃, 페이드 아웃, 크로스페이드, 랩이 있습니다.
             가시성과 변환이 그 예입니다. 애니메이션의 속성은 페이드 아웃, 페이드 아웃, 크로스페이드, 랩이 있습니다.
             한 오브젝트에 대한 동시에 일어나는 다른 확대 시간 및 가중치 조절이나, 서로 다른 오브젝트간의 애니메이션도 전부 개별로 변화시킬 수 있습니다.
             한 오브젝트에 대한 동시에 일어나는 다른 확대 시간 및 가중치 조절이나, 서로 다른 오브젝트간의 애니메이션도 전부 개별로 변화시킬 수 있습니다.
             같은, 혹은 서로 다른 오브젝트틀간의 다양한 애니메이션도 싱크를 맞출 수 있습니다.
             같은, 혹은 서로 다른 오브젝트틀간의 다양한 애니메이션도 싱크를 맞출 수 있습니다.
             <br /><br />
             <br /><br />
 
 
-            이를 한 시스템 안에 구현하기 위해서, three.js 애니메이션 시스템은
-			[link:https://github.com/mrdoob/three.js/issues/6881 2015년에 완전히 변경]
-            (지난 정보임에 주의하세요!)되었으며, 현재는 Unity/Unreal Engine 4와 유사한 구조를 가지고 있습니다.
+            이를 한 시스템 안에 구현하기 위해서, three.js 애니메이션 시스템은 2015년에 완전히 변경되었으며([link:https://github.com/mrdoob/three.js/issues/6881 Link])
+            되었으며(지난 정보에 주의하세요!), 현재는 Unity/Unreal Engine 4와 유사한 구조를 가지고 있습니다.
             이 페이지에서는 어떻게 시스템 메인 컴포넌트가 구성되고 동작되는지를 간단하게 알아보겠습니다.
             이 페이지에서는 어떻게 시스템 메인 컴포넌트가 구성되고 동작되는지를 간단하게 알아보겠습니다.
 
 
 		</p>
 		</p>

+ 3 - 3
docs/manual/ko/introduction/FAQ.html

@@ -15,7 +15,7 @@
 				불러오기 및 내보내기 용으로 추천되는 포맷은 glTF (GL Transmission Format)입니다. glTF는 런타임 에셋 딜리버리에 초점이 맞추어져 있기 때문에, 전송에 적합하고 로딩이 빠릅니다.
 				불러오기 및 내보내기 용으로 추천되는 포맷은 glTF (GL Transmission Format)입니다. glTF는 런타임 에셋 딜리버리에 초점이 맞추어져 있기 때문에, 전송에 적합하고 로딩이 빠릅니다.
 			</p>
 			</p>
 			<p>
 			<p>
-				three.js 널리 쓰이는 포맷인 FBX, Collada 나 OBJ 도 지원합니다. 하지만 첫 프로젝트에서는 glTF 기반의 워크플로우로 작업해야 합니다. 더 자세한 내용은, [link:#manual/introduction/Loading-3D-models 3D 모델 로딩]을 참고하세요.
+				three.js 널리 쓰이는 포맷인 FBX, Collada 나 OBJ 도 지원합니다. 하지만 첫 프로젝트에서는 glTF 기반의 워크플로우로 작업해야 합니다. 더 자세한 내용은, [link:#manual/introduction/Loading-3D-models loading 3D models]를 참고하세요.
 			</p>
 			</p>
 		</div>
 		</div>
 
 
@@ -41,8 +41,8 @@ visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_f
 			</code>
 			</code>
             화면 높이를 특정 비율로 늘리면, 모든 가시 높이와 거리가 같은 비율로 증가해야 합니다.
             화면 높이를 특정 비율로 늘리면, 모든 가시 높이와 거리가 같은 비율로 증가해야 합니다.
 
 
-			이는 카메라 위치를 변경하는 것으로는 불가능합니다. 대신에 카메라 field-of-view를 변경해야합니다..
-			[link:http://jsfiddle.net/Q4Jpu/ 예제].
+			이는 카메라 위치를 변경하는 것으로는 불가능합니다. 대신에 카메라 field-of-view를 변경해야합니다.
+			[link:http://jsfiddle.net/Q4Jpu/ Example].
 		</p>
 		</p>
 
 
 		<h2>왜 오브젝트 일부가 안 보일까요?</h2>
 		<h2>왜 오브젝트 일부가 안 보일까요?</h2>

+ 4 - 4
docs/manual/ko/introduction/How-to-dispose-of-objects.html

@@ -21,7 +21,7 @@
 	<h2>기하학</h2>
 	<h2>기하학</h2>
 
 
 	<p>
 	<p>
-        기하학은 속성 집합으로 정의된 꼭짓점 정보를 표시하는데, *three.js*는 속성마다 하나의 [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] 유형의 대상을 만들어 내부에 저장합니다.
+        기하학은 속성 집합으로 정의된 꼭짓점 정보를 표시하는데, *three.js*는 속성마다 하나의 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] 유형의 대상을 만들어 내부에 저장합니다.
         이러한 개체는 [page:BufferGeometry.dispose]를 호출할 때만 폐기됩니다.
         이러한 개체는 [page:BufferGeometry.dispose]를 호출할 때만 폐기됩니다.
         만약 애플리케이션에서 기하학을 더이상 사용하지 않는다면, 이 방법을 실행하여 모든 관련 자원을 폐기하세요.
         만약 애플리케이션에서 기하학을 더이상 사용하지 않는다면, 이 방법을 실행하여 모든 관련 자원을 폐기하세요.
 	</p>
 	</p>
@@ -38,15 +38,15 @@
 
 
 	<p>
 	<p>
         재질의 폐기는 텍스쳐에 영향을 미치지 않습니다. 이들은 분리되어 있어 하나의 텍스쳐를 여러 재질로 동시에 사용할 수 있습니다.
         재질의 폐기는 텍스쳐에 영향을 미치지 않습니다. 이들은 분리되어 있어 하나의 텍스쳐를 여러 재질로 동시에 사용할 수 있습니다.
-        [page:Texture] 인스턴스를 만들 때마다 three.js는 내부에서 [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] 인스턴스를 만듭니다.
+        [page:Texture] 인스턴스를 만들 때마다 three.js는 내부에서 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] 인스턴스를 만듭니다.
         buffer와 비슷하게, 이 오브젝트는 [page:Texture.dispose]() 호출로만 삭제가 가능합니다.
         buffer와 비슷하게, 이 오브젝트는 [page:Texture.dispose]() 호출로만 삭제가 가능합니다.
 	</p>
 	</p>
 
 
 	<h2>렌더링 대상</h2>
 	<h2>렌더링 대상</h2>
 
 
 	<p>
 	<p>
-        [page:WebGLRenderTarget] 타입의 오브젝트는 [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]의 인스턴스가 할당되어 있을 뿐만 아니라,
-        [link: https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]와 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] 도 할당되어, 
+        [page:WebGLRenderTarget] 타입의 오브젝트는 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]의 인스턴스가 할당되어 있을 뿐만 아니라,
+        [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]와 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] 도 할당되어, 
         커스텀 렌더링 목표를 실체화합니다. 이러한 오브젝트는 [page:WebGLRenderTarget.dispose]()를 실행해야만 폐기할 수 있습니다.
         커스텀 렌더링 목표를 실체화합니다. 이러한 오브젝트는 [page:WebGLRenderTarget.dispose]()를 실행해야만 폐기할 수 있습니다.
 	</p>
 	</p>
 
 

+ 3 - 4
docs/manual/ko/introduction/How-to-run-things-locally.html

@@ -43,8 +43,7 @@
     <h2>로컬 서버에서 실행</h2>
     <h2>로컬 서버에서 실행</h2>
     <div>
     <div>
         <p>
         <p>
-            많은 프로그래밍 언어는 기본적으로 간단한 HTTP 서버가 설치되어 있습니다. [link:https://www.apache.org/ Apache]나 [link:https://nginx.org
-            NGINX]같은 프로덕션용 정도로 갖추어져 있지는 않지만, three.js를 테스트해보기에는 충분합니다.
+            많은 프로그래밍 언어는 기본적으로 간단한 HTTP 서버가 설치되어 있습니다. [link:https://www.apache.org/ Apache]나 [link:https://nginx.org NGINX]같은 프로덕션용 정도로 갖추어져 있지는 않지만, three.js를 테스트해보기에는 충분합니다.
         </p>
         </p>
 
 
         <h3>유명 코드 에디터 관련 플러그인</h3>
         <h3>유명 코드 에디터 관련 플러그인</h3>
@@ -120,7 +119,7 @@ ruby -r webrick -e "s = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot =>
                 </li>
                 </li>
                 <li>
                 <li>
                     웹서버에서 실행하고자 하는 디렉토리에 lighttpd.conf라는 설정파일을 만듭니다.
                     웹서버에서 실행하고자 하는 디렉토리에 lighttpd.conf라는 설정파일을 만듭니다.
-                    예제는 [link:http://redmine.lighttpd.net/projects/lighttpd/wiki/TutorialConfiguration 여기]에서 확인할 수 있습니다.
+                    예제는 여기([link:http://redmine.lighttpd.net/projects/lighttpd/wiki/TutorialConfiguration link])에서 확인할 수 있습니다.
                 </li>
                 </li>
                 <li>
                 <li>
                     설정 파일에서, server.document-root를 서버로 쓰고자 하는 디렉토리로 설정합니다.
                     설정 파일에서, server.document-root를 서버로 쓰고자 하는 디렉토리로 설정합니다.
@@ -141,7 +140,7 @@ ruby -r webrick -e "s = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot =>
             <p>기본적으로 IIS는 .fbx, .obj 파일의 다운로드를 막아 놓았습니다. IIS에서 이러한 파일들이 다운로드 될 수 있도록 설정해야 합니다.</p>
             <p>기본적으로 IIS는 .fbx, .obj 파일의 다운로드를 막아 놓았습니다. IIS에서 이러한 파일들이 다운로드 될 수 있도록 설정해야 합니다.</p>
         </div>
         </div>
         <p>
         <p>
-            다른 간단한 방법으로는 Stack Overflow의 [link:http://stackoverflow.com/q/12905426/24874 이곳에서 토론]을 확인해 보세요.
+            다른 간단한 방법으로는 Stack Overflow에서 논의된 내용([link:http://stackoverflow.com/q/12905426/24874 link])을 확인해 보세요.
         </p>
         </p>
     </div>
     </div>
 
 

+ 3 - 5
docs/manual/ko/introduction/Installation.html

@@ -141,8 +141,7 @@
 
 
     <p>
     <p>
         대부분의 자바스크립트 번들러는 이제 ES 모듈을 기본적으로 지원하지만, 오래된 빌드 도구들은 그렇지 않을 수 있습니다.
         대부분의 자바스크립트 번들러는 이제 ES 모듈을 기본적으로 지원하지만, 오래된 빌드 도구들은 그렇지 않을 수 있습니다.
-        이 경우에, 번들러에 ES 모듈을 인식할 수 있도록 설정해줄 수 있습니다. 예를들어 [link:http://browserify.org/
-        Browserify] 는 [link:https://github.com/babel/babelify babelify] 플러그인을 불러오기만 하면 됩니다.
+        이 경우에, 번들러에 ES 모듈을 인식할 수 있도록 설정해줄 수 있습니다. 예를들어 [link:http://browserify.org/ Browserify] 는 [link:https://github.com/babel/babelify babelify] 플러그인을 불러오기만 하면 됩니다.
     </p>
     </p>
 
 
     <h3>maps 불러오기</h3>
     <h3>maps 불러오기</h3>
@@ -159,7 +158,7 @@
         이는 npm 패키지에서 주로 쓰이는 경로 작성법과 일치하고, 두 사용자군 모두에게 파일을 불러오는 데에 동일한 코드를 사용할 수 있게 해 줄 것입니다.
         이는 npm 패키지에서 주로 쓰이는 경로 작성법과 일치하고, 두 사용자군 모두에게 파일을 불러오는 데에 동일한 코드를 사용할 수 있게 해 줄 것입니다.
         빌드 도구를 사용하지 않는 것을 선호하는 사용자들에게도, 간단한 JSON 맵핑을 통해 CDN이나 직접 파일 폴더에서 불러오는 것을 가능하게 해 줄 것입니다.
         빌드 도구를 사용하지 않는 것을 선호하는 사용자들에게도, 간단한 JSON 맵핑을 통해 CDN이나 직접 파일 폴더에서 불러오는 것을 가능하게 해 줄 것입니다.
         실험적 방법으로, [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map
         실험적 방법으로, [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map
-        예제]처럼 map polyfill과 함께 import 해서 더 간단하게 사용해볼 수도 있습니다.
+        example]처럼 map polyfill과 함께 import 해서 더 간단하게 사용해볼 수도 있습니다.
     </p>
     </p>
 
 
     <h3>Node.js</h3>
     <h3>Node.js</h3>
@@ -170,8 +169,7 @@
 
 
     <p>
     <p>
         첫 번째로, three.js는 웹을 목적으로 만들어졌기때문에, Node.js에서 항상 활용 가능하다고 보증할 수 없는 브라우저와 DOM API에 의존하고 있는 까닭입니다.
         첫 번째로, three.js는 웹을 목적으로 만들어졌기때문에, Node.js에서 항상 활용 가능하다고 보증할 수 없는 브라우저와 DOM API에 의존하고 있는 까닭입니다.
-        이러한 문제들은 shims를 통해 [link:https://github.com/stackgl/headless-gl
-        headless-gl]처럼 해결하거나, [page:TextureLoader] 같은 컴포넌트를 커스터마이징 해서 해결 가능합니다. 다른 DOM API는 관련된 코드가 더 복잡하게 연관되어 있어, 수정하기 더 까다롭습니다.
+        이러한 문제들은 shims를 통해 [link:https://github.com/stackgl/headless-gl headless-gl]처럼 해결하거나, [page:TextureLoader] 같은 컴포넌트를 커스터마이징 해서 해결 가능합니다. 다른 DOM API는 관련된 코드가 더 복잡하게 연관되어 있어, 수정하기 더 까다롭습니다.
         Node.js 지원을 향상시키기 위한 더 간단하고, 유지보수 가능한 pull 요청은 언제든지 환영이지만, 본인의 작업을 위한 issue 생성을 더 권장합니다.
         Node.js 지원을 향상시키기 위한 더 간단하고, 유지보수 가능한 pull 요청은 언제든지 환영이지만, 본인의 작업을 위한 issue 생성을 더 권장합니다.
     </p>
     </p>
 
 

+ 1 - 1
docs/manual/ko/introduction/Useful-links.html

@@ -20,7 +20,7 @@
 
 
 		<h2>도움이 되는 포럼</h2>
 		<h2>도움이 되는 포럼</h2>
 		<p>
 		<p>
-			Three.js는 공식적으로[link:https://discourse.threejs.org/ 포럼] 과 [link:http://stackoverflow.com/tags/three.js/info Stack Overflow]에서 지원 요청을 받고 있습니다.
+			Three.js는 공식적으로 [link:https://discourse.threejs.org/ forum]과 [link:http://stackoverflow.com/tags/three.js/info Stack Overflow]에서 지원 요청을 받고 있습니다.
 			도움이 필요하다면, 저기로 가면 됩니다. 깃허브에서 도움 요청 이슈를 생성하지 마세요.
 			도움이 필요하다면, 저기로 가면 됩니다. 깃허브에서 도움 요청 이슈를 생성하지 마세요.
 		</p>
 		</p>
 
 

+ 0 - 100
docs/scenes/geometry-browser.html

@@ -38,7 +38,6 @@
 				DoubleSide,
 				DoubleSide,
 				ExtrudeGeometry,
 				ExtrudeGeometry,
 				Float32BufferAttribute,
 				Float32BufferAttribute,
-				FontLoader,
 				Group,
 				Group,
 				IcosahedronGeometry,
 				IcosahedronGeometry,
 				LatheGeometry,
 				LatheGeometry,
@@ -47,7 +46,6 @@
 				Mesh,
 				Mesh,
 				MeshPhongMaterial,
 				MeshPhongMaterial,
 				OctahedronGeometry,
 				OctahedronGeometry,
-				ParametricGeometry,
 				PerspectiveCamera,
 				PerspectiveCamera,
 				PlaneGeometry,
 				PlaneGeometry,
 				PointLight,
 				PointLight,
@@ -57,7 +55,6 @@
 				ShapeGeometry,
 				ShapeGeometry,
 				SphereGeometry,
 				SphereGeometry,
 				TetrahedronGeometry,
 				TetrahedronGeometry,
-				TextGeometry,
 				TorusGeometry,
 				TorusGeometry,
 				TorusKnotGeometry,
 				TorusKnotGeometry,
 				TubeGeometry,
 				TubeGeometry,
@@ -69,7 +66,6 @@
 
 
 			import { GUI } from '../../examples/jsm/libs/dat.gui.module.js';
 			import { GUI } from '../../examples/jsm/libs/dat.gui.module.js';
 			import { OrbitControls } from '../../examples/jsm/controls/OrbitControls.js';
 			import { OrbitControls } from '../../examples/jsm/controls/OrbitControls.js';
-			import { ParametricGeometries } from '../../examples/jsm/geometries/ParametricGeometries.js';
 
 
 			const twoPi = Math.PI * 2;
 			const twoPi = Math.PI * 2;
 
 
@@ -516,78 +512,6 @@
 
 
 				},
 				},
 
 
-				TextGeometry: function ( mesh ) {
-
-					const data = {
-						text: 'TextGeometry',
-						size: 5,
-						height: 2,
-						curveSegments: 12,
-						font: 'helvetiker',
-						weight: 'regular',
-						bevelEnabled: false,
-						bevelThickness: 1,
-						bevelSize: 0.5,
-						bevelOffset: 0.0,
-						bevelSegments: 3
-					};
-
-					const fonts = [
-						'helvetiker',
-						'optimer',
-						'gentilis',
-						'droid/droid_serif'
-					];
-
-					const weights = [
-						'regular', 'bold'
-					];
-
-					function generateGeometry() {
-
-						const loader = new FontLoader();
-						loader.load( '../../examples/fonts/' + data.font + '_' + data.weight + '.typeface.json', function ( font ) {
-
-							const geometry = new TextGeometry( data.text, {
-								font: font,
-								size: data.size,
-								height: data.height,
-								curveSegments: data.curveSegments,
-								bevelEnabled: data.bevelEnabled,
-								bevelThickness: data.bevelThickness,
-								bevelSize: data.bevelSize,
-								bevelOffset: data.bevelOffset,
-								bevelSegments: data.bevelSegments
-							} );
-							geometry.center();
-
-							updateGroupGeometry( mesh, geometry );
-
-						} );
-
-					}
-
-					//Hide the wireframe
-					mesh.children[ 0 ].visible = false;
-
-					const folder = gui.addFolder( 'THREE.TextGeometry' );
-
-					folder.add( data, 'text' ).onChange( generateGeometry );
-					folder.add( data, 'size', 1, 30 ).onChange( generateGeometry );
-					folder.add( data, 'height', 1, 20 ).onChange( generateGeometry );
-					folder.add( data, 'curveSegments', 1, 20 ).step( 1 ).onChange( generateGeometry );
-					folder.add( data, 'font', fonts ).onChange( generateGeometry );
-					folder.add( data, 'weight', weights ).onChange( generateGeometry );
-					folder.add( data, 'bevelEnabled' ).onChange( generateGeometry );
-					folder.add( data, 'bevelThickness', 0.1, 3 ).onChange( generateGeometry );
-					folder.add( data, 'bevelSize', 0, 3 ).onChange( generateGeometry );
-					folder.add( data, 'bevelOffset', - 0.5, 1.5 ).onChange( generateGeometry );
-					folder.add( data, 'bevelSegments', 0, 8 ).step( 1 ).onChange( generateGeometry );
-
-					generateGeometry();
-
-				},
-
 				TorusGeometry: function ( mesh ) {
 				TorusGeometry: function ( mesh ) {
 
 
 					const data = {
 					const data = {
@@ -655,30 +579,6 @@
 
 
 				},
 				},
 
 
-				ParametricGeometry: function ( mesh ) {
-
-					const data = {
-						slices: 25,
-						stacks: 25
-					};
-
-					function generateGeometry() {
-
-						updateGroupGeometry( mesh,
-							new ParametricGeometry( ParametricGeometries.klein, data.slices, data.stacks )
-						);
-
-					}
-
-					const folder = gui.addFolder( 'THREE.ParametricGeometry' );
-
-					folder.add( data, 'slices', 1, 100 ).step( 1 ).onChange( generateGeometry );
-					folder.add( data, 'stacks', 1, 100 ).step( 1 ).onChange( generateGeometry );
-
-					generateGeometry();
-
-				},
-
 				TubeGeometry: function ( mesh ) {
 				TubeGeometry: function ( mesh ) {
 
 
 					const data = {
 					const data = {

+ 12 - 10
editor/css/main.css

@@ -74,19 +74,21 @@ textarea, input { outline: none; } /* osx */
 	position: relative;
 	position: relative;
 	display: block;
 	display: block;
 	width: 100%;
 	width: 100%;
+	min-width: 300px;
 }
 }
 
 
-.TabbedPanel .Tabs .Tab {
-	padding: 10px;
-	text-transform: uppercase;
-}
+	.TabbedPanel .Tabs .Tab {
+		padding: 10px;
+		text-transform: uppercase;
+	}
 
 
-.TabbedPanel .Tabs .Panels {
-	position: relative;
-	display: block;
-	width: 100%;
-	height: 100%;
-}
+	.TabbedPanel .Panels {
+		position: relative;
+		display: block;
+		width: 100%;
+		height: 100%;
+		min-width: 300px;
+	}
 
 
 /* Listbox */
 /* Listbox */
 .Listbox {
 .Listbox {

+ 1 - 15
editor/js/Menubar.Add.js

@@ -126,21 +126,7 @@ function MenubarAdd( editor ) {
 	option.setTextContent( strings.getKey( 'menubar/add/lathe' ) );
 	option.setTextContent( strings.getKey( 'menubar/add/lathe' ) );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var points = [
-			new THREE.Vector2( 0, 0 ),
-			new THREE.Vector2( 0.4, 0 ),
-			new THREE.Vector2( 0.35, 0.05 ),
-			new THREE.Vector2( 0.1, 0.075 ),
-			new THREE.Vector2( 0.08, 0.1 ),
-			new THREE.Vector2( 0.08, 0.4 ),
-			new THREE.Vector2( 0.1, 0.42 ),
-			new THREE.Vector2( 0.14, 0.48 ),
-			new THREE.Vector2( 0.2, 0.5 ),
-			new THREE.Vector2( 0.25, 0.54 ),
-			new THREE.Vector2( 0.3, 1.2 )
-		];
-
-		var geometry = new THREE.LatheGeometry( points, 12, 0, Math.PI * 2 );
+		var geometry = new THREE.LatheGeometry();
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial( { side: THREE.DoubleSide } ) );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial( { side: THREE.DoubleSide } ) );
 		mesh.name = 'Lathe';
 		mesh.name = 'Lathe';
 
 

+ 1 - 3
editor/js/Menubar.File.js

@@ -447,19 +447,17 @@ function MenubarFile( editor ) {
 			if ( config.getKey( 'project/editable' ) ) {
 			if ( config.getKey( 'project/editable' ) ) {
 
 
 				editButton = [
 				editButton = [
-					'',
 					'			var button = document.createElement( \'a\' );',
 					'			var button = document.createElement( \'a\' );',
 					'			button.href = \'https://threejs.org/editor/#file=\' + location.href.split( \'/\' ).slice( 0, - 1 ).join( \'/\' ) + \'/app.json\';',
 					'			button.href = \'https://threejs.org/editor/#file=\' + location.href.split( \'/\' ).slice( 0, - 1 ).join( \'/\' ) + \'/app.json\';',
 					'			button.style.cssText = \'position: absolute; bottom: 20px; right: 20px; padding: 10px 16px; color: #fff; border: 1px solid #fff; border-radius: 20px; text-decoration: none;\';',
 					'			button.style.cssText = \'position: absolute; bottom: 20px; right: 20px; padding: 10px 16px; color: #fff; border: 1px solid #fff; border-radius: 20px; text-decoration: none;\';',
 					'			button.target = \'_blank\';',
 					'			button.target = \'_blank\';',
 					'			button.textContent = \'EDIT\';',
 					'			button.textContent = \'EDIT\';',
 					'			document.body.appendChild( button );',
 					'			document.body.appendChild( button );',
-					''
 				].join( '\n' );
 				].join( '\n' );
 
 
 			}
 			}
 
 
-			content = content.replace( '\n\t\t\t/* edit button */\n', editButton );
+			content = content.replace( '\t\t\t/* edit button */', editButton );
 
 
 			toZip[ 'index.html' ] = strToU8( content );
 			toZip[ 'index.html' ] = strToU8( content );
 
 

+ 6 - 0
editor/js/Player.js

@@ -21,6 +21,12 @@ function Player( editor ) {
 
 
 	} );
 	} );
 
 
+	signals.windowResize.add( function () {
+
+		player.setSize( container.dom.clientWidth, container.dom.clientHeight );
+
+	} );
+
 	signals.startPlayer.add( function () {
 	signals.startPlayer.add( function () {
 
 
 		container.setDisplay( '' );
 		container.setDisplay( '' );

+ 10 - 4
editor/js/Resizer.js

@@ -31,13 +31,19 @@ function Resizer( editor ) {
 
 
 		if ( event.isPrimary === false ) return;
 		if ( event.isPrimary === false ) return;
 
 
-		const rect = dom.getBoundingClientRect();
-		const x = ( document.body.offsetWidth - rect.right ) - event.movementX;
+		const offsetWidth = document.body.offsetWidth;
+		const clientX = event.clientX;
+
+		const cX = clientX < 0 ? 0 : clientX > offsetWidth ? offsetWidth : clientX;
+
+		const x = offsetWidth - cX;
 
 
 		dom.style.right = x + 'px';
 		dom.style.right = x + 'px';
 
 
-		document.getElementById( 'sidebar' ).style.width = ( x + rect.width ) + 'px';
-		document.getElementById( 'viewport' ).style.right = ( x + rect.width ) + 'px';
+		document.getElementById( 'sidebar' ).style.width = x + 'px';
+		document.getElementById( 'player' ).style.right = x + 'px';
+		document.getElementById( 'script' ).style.right = x + 'px';
+		document.getElementById( 'viewport' ).style.right = x + 'px';
 
 
 		signals.windowResize.dispatch();
 		signals.windowResize.dispatch();
 
 

+ 10 - 4
editor/js/libs/codemirror/addon/dialog.js

@@ -1,5 +1,5 @@
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
 
 
 // Open simple dialogs on top of an editor. Relies on dialog.css.
 // Open simple dialogs on top of an editor. Relies on dialog.css.
 
 
@@ -25,6 +25,7 @@
     } else { // Assuming it's a detached DOM element.
     } else { // Assuming it's a detached DOM element.
       dialog.appendChild(template);
       dialog.appendChild(template);
     }
     }
+    CodeMirror.addClass(wrap, 'dialog-opened');
     return dialog;
     return dialog;
   }
   }
 
 
@@ -47,6 +48,7 @@
       } else {
       } else {
         if (closed) return;
         if (closed) return;
         closed = true;
         closed = true;
+        CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
         dialog.parentNode.removeChild(dialog);
         dialog.parentNode.removeChild(dialog);
         me.focus();
         me.focus();
 
 
@@ -56,6 +58,8 @@
 
 
     var inp = dialog.getElementsByTagName("input")[0], button;
     var inp = dialog.getElementsByTagName("input")[0], button;
     if (inp) {
     if (inp) {
+      inp.focus();
+
       if (options.value) {
       if (options.value) {
         inp.value = options.value;
         inp.value = options.value;
         if (options.selectValueOnOpen !== false) {
         if (options.selectValueOnOpen !== false) {
@@ -78,9 +82,9 @@
         if (e.keyCode == 13) callback(inp.value, e);
         if (e.keyCode == 13) callback(inp.value, e);
       });
       });
 
 
-      if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
-
-      inp.focus();
+      if (options.closeOnBlur !== false) CodeMirror.on(dialog, "focusout", function (evt) {
+        if (evt.relatedTarget !== null) close();
+      });
     } else if (button = dialog.getElementsByTagName("button")[0]) {
     } else if (button = dialog.getElementsByTagName("button")[0]) {
       CodeMirror.on(button, "click", function() {
       CodeMirror.on(button, "click", function() {
         close();
         close();
@@ -102,6 +106,7 @@
     function close() {
     function close() {
       if (closed) return;
       if (closed) return;
       closed = true;
       closed = true;
+      CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
       dialog.parentNode.removeChild(dialog);
       dialog.parentNode.removeChild(dialog);
       me.focus();
       me.focus();
     }
     }
@@ -141,6 +146,7 @@
       if (closed) return;
       if (closed) return;
       closed = true;
       closed = true;
       clearTimeout(doneTimer);
       clearTimeout(doneTimer);
+      CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
       dialog.parentNode.removeChild(dialog);
       dialog.parentNode.removeChild(dialog);
     }
     }
 
 

+ 0 - 2
editor/js/libs/codemirror/addon/show-hint.css

@@ -25,8 +25,6 @@
   margin: 0;
   margin: 0;
   padding: 0 4px;
   padding: 0 4px;
   border-radius: 2px;
   border-radius: 2px;
-  max-width: 19em;
-  overflow: hidden;
   white-space: pre;
   white-space: pre;
   color: black;
   color: black;
   cursor: pointer;
   cursor: pointer;

+ 225 - 79
editor/js/libs/codemirror/addon/show-hint.js

@@ -1,5 +1,7 @@
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// declare global: DOMRect
 
 
 (function(mod) {
 (function(mod) {
   if (typeof exports == "object" && typeof module == "object") // CommonJS
   if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -25,8 +27,18 @@
   };
   };
 
 
   CodeMirror.defineExtension("showHint", function(options) {
   CodeMirror.defineExtension("showHint", function(options) {
-    // We want a single cursor position.
-    if (this.listSelections().length > 1 || this.somethingSelected()) return;
+    options = parseOptions(this, this.getCursor("start"), options);
+    var selections = this.listSelections()
+    if (selections.length > 1) return;
+    // By default, don't allow completion when something is selected.
+    // A hint function can have a `supportsSelection` property to
+    // indicate that it can handle selections.
+    if (this.somethingSelected()) {
+      if (!options.hint.supportsSelection) return;
+      // Don't try with cross-line selections
+      for (var i = 0; i < selections.length; i++)
+        if (selections[i].head.line != selections[i].anchor.line) return;
+    }
 
 
     if (this.state.completionActive) this.state.completionActive.close();
     if (this.state.completionActive) this.state.completionActive.close();
     var completion = this.state.completionActive = new Completion(this, options);
     var completion = this.state.completionActive = new Completion(this, options);
@@ -36,17 +48,23 @@
     completion.update(true);
     completion.update(true);
   });
   });
 
 
+  CodeMirror.defineExtension("closeHint", function() {
+    if (this.state.completionActive) this.state.completionActive.close()
+  })
+
   function Completion(cm, options) {
   function Completion(cm, options) {
     this.cm = cm;
     this.cm = cm;
-    this.options = this.buildOptions(options);
+    this.options = options;
     this.widget = null;
     this.widget = null;
     this.debounce = 0;
     this.debounce = 0;
     this.tick = 0;
     this.tick = 0;
-    this.startPos = this.cm.getCursor();
-    this.startLen = this.cm.getLine(this.startPos.line).length;
+    this.startPos = this.cm.getCursor("start");
+    this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
 
 
-    var self = this;
-    cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
+    if (this.options.updateOnCursorActivity) {
+      var self = this;
+      cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
+    }
   }
   }
 
 
   var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
   var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
@@ -59,7 +77,9 @@
       if (!this.active()) return;
       if (!this.active()) return;
       this.cm.state.completionActive = null;
       this.cm.state.completionActive = null;
       this.tick = null;
       this.tick = null;
-      this.cm.off("cursorActivity", this.activityFunc);
+      if (this.options.updateOnCursorActivity) {
+        this.cm.off("cursorActivity", this.activityFunc);
+      }
 
 
       if (this.widget && this.data) CodeMirror.signal(this.data, "close");
       if (this.widget && this.data) CodeMirror.signal(this.data, "close");
       if (this.widget) this.widget.close();
       if (this.widget) this.widget.close();
@@ -71,12 +91,19 @@
     },
     },
 
 
     pick: function(data, i) {
     pick: function(data, i) {
-      var completion = data.list[i];
-      if (completion.hint) completion.hint(this.cm, data, completion);
-      else this.cm.replaceRange(getText(completion), completion.from || data.from,
-                                completion.to || data.to, "complete");
-      CodeMirror.signal(data, "pick", completion);
-      this.close();
+      var completion = data.list[i], self = this;
+      this.cm.operation(function() {
+        if (completion.hint)
+          completion.hint(self.cm, data, completion);
+        else
+          self.cm.replaceRange(getText(completion), completion.from || data.from,
+                               completion.to || data.to, "complete");
+        CodeMirror.signal(data, "pick", completion);
+        self.cm.scrollIntoView();
+      });
+      if (this.options.closeOnPick) {
+        this.close();
+      }
     },
     },
 
 
     cursorActivity: function() {
     cursorActivity: function() {
@@ -85,10 +112,15 @@
         this.debounce = 0;
         this.debounce = 0;
       }
       }
 
 
+      var identStart = this.startPos;
+      if(this.data) {
+        identStart = this.data.from;
+      }
+
       var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
       var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
       if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
       if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
-          pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
-          (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
+          pos.ch < identStart.ch || this.cm.somethingSelected() ||
+          (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
         this.close();
         this.close();
       } else {
       } else {
         var self = this;
         var self = this;
@@ -98,23 +130,21 @@
     },
     },
 
 
     update: function(first) {
     update: function(first) {
-      if (this.tick == null) return;
-      if (this.data) CodeMirror.signal(this.data, "update");
-      if (!this.options.hint.async) {
-        this.finishUpdate(this.options.hint(this.cm, this.options), first);
-      } else {
-        var myTick = ++this.tick, self = this;
-        this.options.hint(this.cm, function(data) {
-          if (self.tick == myTick) self.finishUpdate(data, first);
-        }, this.options);
-      }
+      if (this.tick == null) return
+      var self = this, myTick = ++this.tick
+      fetchHints(this.options.hint, this.cm, this.options, function(data) {
+        if (self.tick == myTick) self.finishUpdate(data, first)
+      })
     },
     },
 
 
     finishUpdate: function(data, first) {
     finishUpdate: function(data, first) {
-      this.data = data;
+      if (this.data) CodeMirror.signal(this.data, "update");
 
 
       var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
       var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
       if (this.widget) this.widget.close();
       if (this.widget) this.widget.close();
+
+      this.data = data;
+
       if (data && data.list.length) {
       if (data && data.list.length) {
         if (picked && data.list.length == 1) {
         if (picked && data.list.length == 1) {
           this.pick(data, 0);
           this.pick(data, 0);
@@ -123,20 +153,21 @@
           CodeMirror.signal(data, "shown");
           CodeMirror.signal(data, "shown");
         }
         }
       }
       }
-    },
-
-    buildOptions: function(options) {
-      var editor = this.cm.options.hintOptions;
-      var out = {};
-      for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
-      if (editor) for (var prop in editor)
-        if (editor[prop] !== undefined) out[prop] = editor[prop];
-      if (options) for (var prop in options)
-        if (options[prop] !== undefined) out[prop] = options[prop];
-      return out;
     }
     }
   };
   };
 
 
+  function parseOptions(cm, pos, options) {
+    var editor = cm.options.hintOptions;
+    var out = {};
+    for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
+    if (editor) for (var prop in editor)
+      if (editor[prop] !== undefined) out[prop] = editor[prop];
+    if (options) for (var prop in options)
+      if (options[prop] !== undefined) out[prop] = options[prop];
+    if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
+    return out;
+  }
+
   function getText(completion) {
   function getText(completion) {
     if (typeof completion == "string") return completion;
     if (typeof completion == "string") return completion;
     else return completion.text;
     else return completion.text;
@@ -154,6 +185,14 @@
       Tab: handle.pick,
       Tab: handle.pick,
       Esc: handle.close
       Esc: handle.close
     };
     };
+
+    var mac = /Mac/.test(navigator.platform);
+
+    if (mac) {
+      baseMap["Ctrl-P"] = function() {handle.moveFocus(-1);};
+      baseMap["Ctrl-N"] = function() {handle.moveFocus(1);};
+    }
+
     var custom = completion.options.customKeys;
     var custom = completion.options.customKeys;
     var ourMap = custom ? {} : baseMap;
     var ourMap = custom ? {} : baseMap;
     function addBinding(key, val) {
     function addBinding(key, val) {
@@ -185,59 +224,95 @@
   }
   }
 
 
   function Widget(completion, data) {
   function Widget(completion, data) {
+    this.id = "cm-complete-" + Math.floor(Math.random(1e6))
     this.completion = completion;
     this.completion = completion;
     this.data = data;
     this.data = data;
     this.picked = false;
     this.picked = false;
     var widget = this, cm = completion.cm;
     var widget = this, cm = completion.cm;
-
-    var hints = this.hints = document.createElement("ul");
-    hints.className = "CodeMirror-hints";
+    var ownerDocument = cm.getInputField().ownerDocument;
+    var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
+
+    var hints = this.hints = ownerDocument.createElement("ul");
+    hints.setAttribute("role", "listbox")
+    hints.setAttribute("aria-expanded", "true")
+    hints.id = this.id
+    var theme = completion.cm.options.theme;
+    hints.className = "CodeMirror-hints " + theme;
     this.selectedHint = data.selectedHint || 0;
     this.selectedHint = data.selectedHint || 0;
 
 
     var completions = data.list;
     var completions = data.list;
     for (var i = 0; i < completions.length; ++i) {
     for (var i = 0; i < completions.length; ++i) {
-      var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
+      var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
       var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
       var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
       if (cur.className != null) className = cur.className + " " + className;
       if (cur.className != null) className = cur.className + " " + className;
       elt.className = className;
       elt.className = className;
+      if (i == this.selectedHint) elt.setAttribute("aria-selected", "true")
+      elt.id = this.id + "-" + i
+      elt.setAttribute("role", "option")
       if (cur.render) cur.render(elt, data, cur);
       if (cur.render) cur.render(elt, data, cur);
-      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+      else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
       elt.hintId = i;
       elt.hintId = i;
     }
     }
 
 
+    var container = completion.options.container || ownerDocument.body;
     var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
     var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
     var left = pos.left, top = pos.bottom, below = true;
     var left = pos.left, top = pos.bottom, below = true;
-    hints.style.left = left + "px";
-    hints.style.top = top + "px";
+    var offsetLeft = 0, offsetTop = 0;
+    if (container !== ownerDocument.body) {
+      // We offset the cursor position because left and top are relative to the offsetParent's top left corner.
+      var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
+      var offsetParent = isContainerPositioned ? container : container.offsetParent;
+      var offsetParentPosition = offsetParent.getBoundingClientRect();
+      var bodyPosition = ownerDocument.body.getBoundingClientRect();
+      offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);
+      offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);
+    }
+    hints.style.left = (left - offsetLeft) + "px";
+    hints.style.top = (top - offsetTop) + "px";
+
     // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
     // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
-    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
-    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
-    (completion.options.container || document.body).appendChild(hints);
-    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+    var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
+    var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
+    container.appendChild(hints);
+    cm.getInputField().setAttribute("aria-autocomplete", "list")
+    cm.getInputField().setAttribute("aria-owns", this.id)
+    cm.getInputField().setAttribute("aria-activedescendant", this.id + "-" + this.selectedHint)
+
+    var box = completion.options.moveOnOverlap ? hints.getBoundingClientRect() : new DOMRect();
+    var scrolls = completion.options.paddingForScrollbar ? hints.scrollHeight > hints.clientHeight + 1 : false;
+
+    // Compute in the timeout to avoid reflow on init
+    var startScroll;
+    setTimeout(function() { startScroll = cm.getScrollInfo(); });
+
+    var overlapY = box.bottom - winH;
     if (overlapY > 0) {
     if (overlapY > 0) {
       var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
       var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
       if (curTop - height > 0) { // Fits above cursor
       if (curTop - height > 0) { // Fits above cursor
-        hints.style.top = (top = pos.top - height) + "px";
+        hints.style.top = (top = pos.top - height - offsetTop) + "px";
         below = false;
         below = false;
       } else if (height > winH) {
       } else if (height > winH) {
         hints.style.height = (winH - 5) + "px";
         hints.style.height = (winH - 5) + "px";
-        hints.style.top = (top = pos.bottom - box.top) + "px";
+        hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px";
         var cursor = cm.getCursor();
         var cursor = cm.getCursor();
         if (data.from.ch != cursor.ch) {
         if (data.from.ch != cursor.ch) {
           pos = cm.cursorCoords(cursor);
           pos = cm.cursorCoords(cursor);
-          hints.style.left = (left = pos.left) + "px";
+          hints.style.left = (left = pos.left - offsetLeft) + "px";
           box = hints.getBoundingClientRect();
           box = hints.getBoundingClientRect();
         }
         }
       }
       }
     }
     }
     var overlapX = box.right - winW;
     var overlapX = box.right - winW;
+    if (scrolls) overlapX += cm.display.nativeBarWidth;
     if (overlapX > 0) {
     if (overlapX > 0) {
       if (box.right - box.left > winW) {
       if (box.right - box.left > winW) {
         hints.style.width = (winW - 5) + "px";
         hints.style.width = (winW - 5) + "px";
         overlapX -= (box.right - box.left) - winW;
         overlapX -= (box.right - box.left) - winW;
       }
       }
-      hints.style.left = (left = pos.left - overlapX) + "px";
+      hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px";
     }
     }
+    if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
+      node.style.paddingRight = cm.display.nativeBarWidth + "px"
 
 
     cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
     cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
       moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
       moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
@@ -255,11 +330,11 @@
       cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
       cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
     }
     }
 
 
-    var startScroll = cm.getScrollInfo();
     cm.on("scroll", this.onScroll = function() {
     cm.on("scroll", this.onScroll = function() {
       var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
       var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
+      if (!startScroll) startScroll = cm.getScrollInfo();
       var newTop = top + startScroll.top - curScroll.top;
       var newTop = top + startScroll.top - curScroll.top;
-      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+      var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
       if (!below) point += hints.offsetHeight;
       if (!below) point += hints.offsetHeight;
       if (point <= editor.top || point >= editor.bottom) return completion.close();
       if (point <= editor.top || point >= editor.bottom) return completion.close();
       hints.style.top = newTop + "px";
       hints.style.top = newTop + "px";
@@ -283,7 +358,13 @@
       setTimeout(function(){cm.focus();}, 20);
       setTimeout(function(){cm.focus();}, 20);
     });
     });
 
 
-    CodeMirror.signal(data, "select", completions[0], hints.firstChild);
+    // The first hint doesn't need to be scrolled to on init
+    var selectedHintRange = this.getSelectedHintRange();
+    if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) {
+      this.scrollToActive();
+    }
+
+    CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
     return true;
     return true;
   }
   }
 
 
@@ -291,8 +372,11 @@
     close: function() {
     close: function() {
       if (this.completion.widget != this) return;
       if (this.completion.widget != this) return;
       this.completion.widget = null;
       this.completion.widget = null;
-      this.hints.parentNode.removeChild(this.hints);
+      if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints);
       this.completion.cm.removeKeyMap(this.keyMap);
       this.completion.cm.removeKeyMap(this.keyMap);
+      var input = this.completion.cm.getInputField()
+      input.removeAttribute("aria-activedescendant")
+      input.removeAttribute("aria-owns")
 
 
       var cm = this.completion.cm;
       var cm = this.completion.cm;
       if (this.completion.options.closeOnUnfocus) {
       if (this.completion.options.closeOnUnfocus) {
@@ -320,49 +404,107 @@
         i = avoidWrap ? 0  : this.data.list.length - 1;
         i = avoidWrap ? 0  : this.data.list.length - 1;
       if (this.selectedHint == i) return;
       if (this.selectedHint == i) return;
       var node = this.hints.childNodes[this.selectedHint];
       var node = this.hints.childNodes[this.selectedHint];
-      node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+      if (node) {
+        node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+        node.removeAttribute("aria-selected")
+      }
       node = this.hints.childNodes[this.selectedHint = i];
       node = this.hints.childNodes[this.selectedHint = i];
       node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
       node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
-      if (node.offsetTop < this.hints.scrollTop)
-        this.hints.scrollTop = node.offsetTop - 3;
-      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
-        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
+      node.setAttribute("aria-selected", "true")
+      this.completion.cm.getInputField().setAttribute("aria-activedescendant", node.id)
+      this.scrollToActive()
       CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
       CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
     },
     },
 
 
+    scrollToActive: function() {
+      var selectedHintRange = this.getSelectedHintRange();
+      var node1 = this.hints.childNodes[selectedHintRange.from];
+      var node2 = this.hints.childNodes[selectedHintRange.to];
+      var firstNode = this.hints.firstChild;
+      if (node1.offsetTop < this.hints.scrollTop)
+        this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;
+      else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
+        this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;
+    },
+
     screenAmount: function() {
     screenAmount: function() {
       return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
       return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
+    },
+
+    getSelectedHintRange: function() {
+      var margin = this.completion.options.scrollMargin || 0;
+      return {
+        from: Math.max(0, this.selectedHint - margin),
+        to: Math.min(this.data.list.length - 1, this.selectedHint + margin),
+      };
     }
     }
   };
   };
 
 
-  CodeMirror.registerHelper("hint", "auto", function(cm, options) {
-    var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
+  function applicableHelpers(cm, helpers) {
+    if (!cm.somethingSelected()) return helpers
+    var result = []
+    for (var i = 0; i < helpers.length; i++)
+      if (helpers[i].supportsSelection) result.push(helpers[i])
+    return result
+  }
+
+  function fetchHints(hint, cm, options, callback) {
+    if (hint.async) {
+      hint(cm, callback, options)
+    } else {
+      var result = hint(cm, options)
+      if (result && result.then) result.then(callback)
+      else callback(result)
+    }
+  }
+
+  function resolveAutoHints(cm, pos) {
+    var helpers = cm.getHelpers(pos, "hint"), words
     if (helpers.length) {
     if (helpers.length) {
-      for (var i = 0; i < helpers.length; i++) {
-        var cur = helpers[i](cm, options);
-        if (cur && cur.list.length) return cur;
+      var resolved = function(cm, callback, options) {
+        var app = applicableHelpers(cm, helpers);
+        function run(i) {
+          if (i == app.length) return callback(null)
+          fetchHints(app[i], cm, options, function(result) {
+            if (result && result.list.length > 0) callback(result)
+            else run(i + 1)
+          })
+        }
+        run(0)
       }
       }
+      resolved.async = true
+      resolved.supportsSelection = true
+      return resolved
     } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
     } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
-      if (words) return CodeMirror.hint.fromList(cm, {words: words});
+      return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
     } else if (CodeMirror.hint.anyword) {
     } else if (CodeMirror.hint.anyword) {
-      return CodeMirror.hint.anyword(cm, options);
+      return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
+    } else {
+      return function() {}
     }
     }
+  }
+
+  CodeMirror.registerHelper("hint", "auto", {
+    resolve: resolveAutoHints
   });
   });
 
 
   CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
   CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
-    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+    var cur = cm.getCursor(), token = cm.getTokenAt(cur)
+    var term, from = CodeMirror.Pos(cur.line, token.start), to = cur
+    if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) {
+      term = token.string.substr(0, cur.ch - token.start)
+    } else {
+      term = ""
+      from = cur
+    }
     var found = [];
     var found = [];
     for (var i = 0; i < options.words.length; i++) {
     for (var i = 0; i < options.words.length; i++) {
       var word = options.words[i];
       var word = options.words[i];
-      if (word.slice(0, token.string.length) == token.string)
+      if (word.slice(0, term.length) == term)
         found.push(word);
         found.push(word);
     }
     }
 
 
-    if (found.length) return {
-      list: found,
-      from: CodeMirror.Pos(cur.line, token.start),
-            to: CodeMirror.Pos(cur.line, token.end)
-    };
+    if (found.length) return {list: found, from: from, to: to};
   });
   });
 
 
   CodeMirror.commands.autocomplete = CodeMirror.showHint;
   CodeMirror.commands.autocomplete = CodeMirror.showHint;
@@ -372,11 +514,15 @@
     completeSingle: true,
     completeSingle: true,
     alignWithWord: true,
     alignWithWord: true,
     closeCharacters: /[\s()\[\]{};:>,]/,
     closeCharacters: /[\s()\[\]{};:>,]/,
+    closeOnPick: true,
     closeOnUnfocus: true,
     closeOnUnfocus: true,
-    completeOnSingleClick: false,
+    updateOnCursorActivity: true,
+    completeOnSingleClick: true,
     container: null,
     container: null,
     customKeys: null,
     customKeys: null,
-    extraKeys: null
+    extraKeys: null,
+    paddingForScrollbar: true,
+    moveOnOverlap: true,
   };
   };
 
 
   CodeMirror.defineOption("hintOptions", null);
   CodeMirror.defineOption("hintOptions", null);

+ 1 - 0
editor/js/libs/codemirror/addon/tern.css

@@ -1,6 +1,7 @@
 .CodeMirror-Tern-completion {
 .CodeMirror-Tern-completion {
   padding-left: 22px;
   padding-left: 22px;
   position: relative;
   position: relative;
+  line-height: 1.5;
 }
 }
 .CodeMirror-Tern-completion:before {
 .CodeMirror-Tern-completion:before {
   position: absolute;
   position: absolute;

+ 83 - 31
editor/js/libs/codemirror/addon/tern.js

@@ -1,5 +1,5 @@
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
 
 
 // Glue code between CodeMirror and Tern.
 // Glue code between CodeMirror and Tern.
 //
 //
@@ -29,7 +29,6 @@
 //   queries.
 //   queries.
 // * responseFilter: A function(doc, query, request, error, data) that
 // * responseFilter: A function(doc, query, request, error, data) that
 //   will be applied to the Tern responses before treating them
 //   will be applied to the Tern responses before treating them
-// * caseInsensitive: boolean to send case insensitive querys to tern
 //
 //
 //
 //
 // It is possible to run the Tern server in a web worker by specifying
 // It is possible to run the Tern server in a web worker by specifying
@@ -60,6 +59,7 @@
     this.options = options || {};
     this.options = options || {};
     var plugins = this.options.plugins || (this.options.plugins = {});
     var plugins = this.options.plugins || (this.options.plugins = {});
     if (!plugins.doc_comment) plugins.doc_comment = true;
     if (!plugins.doc_comment) plugins.doc_comment = true;
+    this.docs = Object.create(null);
     if (this.options.useWorker) {
     if (this.options.useWorker) {
       this.server = new WorkerServer(this);
       this.server = new WorkerServer(this);
     } else {
     } else {
@@ -70,7 +70,6 @@
         plugins: plugins
         plugins: plugins
       });
       });
     }
     }
-    this.docs = Object.create(null);
     this.trackChange = function(doc, change) { trackChange(self, doc, change); };
     this.trackChange = function(doc, change) { trackChange(self, doc, change); };
 
 
     this.cachedArgHints = null;
     this.cachedArgHints = null;
@@ -125,6 +124,8 @@
       var self = this;
       var self = this;
       var doc = findDoc(this, cm.getDoc());
       var doc = findDoc(this, cm.getDoc());
       var request = buildRequest(this, doc, query, pos);
       var request = buildRequest(this, doc, query, pos);
+      var extraOptions = request.query && this.options.queryOptions && this.options.queryOptions[request.query.type]
+      if (extraOptions) for (var prop in extraOptions) request.query[prop] = extraOptions[prop];
 
 
       this.server.request(request, function (error, data) {
       this.server.request(request, function (error, data) {
         if (!error && self.options.responseFilter)
         if (!error && self.options.responseFilter)
@@ -134,6 +135,7 @@
     },
     },
 
 
     destroy: function () {
     destroy: function () {
+      closeArgHints(this)
       if (this.worker) {
       if (this.worker) {
         this.worker.terminate();
         this.worker.terminate();
         this.worker = null;
         this.worker = null;
@@ -177,7 +179,7 @@
     var data = findDoc(ts, doc);
     var data = findDoc(ts, doc);
 
 
     var argHints = ts.cachedArgHints;
     var argHints = ts.cachedArgHints;
-    if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
+    if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) >= 0)
       ts.cachedArgHints = null;
       ts.cachedArgHints = null;
 
 
     var changed = data.changed;
     var changed = data.changed;
@@ -203,7 +205,7 @@
   // Completion
   // Completion
 
 
   function hint(ts, cm, c) {
   function hint(ts, cm, c) {
-    ts.request(cm, {type: "completions", types: true, docs: true, urls: true, caseInsensitive: ts.options.caseInsensitive}, function(error, data) {
+    ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) {
       if (error) return showError(ts, cm, error);
       if (error) return showError(ts, cm, error);
       var completions = [], after = "";
       var completions = [], after = "";
       var from = data.start, to = data.end;
       var from = data.start, to = data.end;
@@ -215,7 +217,7 @@
         var completion = data.completions[i], className = typeToIcon(completion.type);
         var completion = data.completions[i], className = typeToIcon(completion.type);
         if (data.guess) className += " " + cls + "guess";
         if (data.guess) className += " " + cls + "guess";
         completions.push({text: completion.name + after,
         completions.push({text: completion.name + after,
-                          displayText: completion.name,
+                          displayText: completion.displayName || completion.name,
                           className: className,
                           className: className,
                           data: completion});
                           data: completion});
       }
       }
@@ -229,8 +231,7 @@
         var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
         var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
         if (content) {
         if (content) {
           tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
           tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
-                                node.getBoundingClientRect().top + window.pageYOffset, content);
-          tooltip.className += " " + cls + "hint-doc";
+                                node.getBoundingClientRect().top + window.pageYOffset, content, cm, cls + "hint-doc");
         }
         }
       });
       });
       c(obj);
       c(obj);
@@ -265,7 +266,7 @@
           child.target = "_blank";
           child.target = "_blank";
         }
         }
       }
       }
-      tempTooltip(cm, tip);
+      tempTooltip(cm, tip, ts);
       if (c) c();
       if (c) c();
     }, pos);
     }, pos);
   }
   }
@@ -304,7 +305,7 @@
     ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
     ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
       if (error || !data.type || !(/^fn\(/).test(data.type)) return;
       if (error || !data.type || !(/^fn\(/).test(data.type)) return;
       ts.cachedArgHints = {
       ts.cachedArgHints = {
-        start: pos,
+        start: start,
         type: parseFnType(data.type),
         type: parseFnType(data.type),
         name: data.exprName || data.name || "fn",
         name: data.exprName || data.name || "fn",
         guess: data.guess,
         guess: data.guess,
@@ -332,7 +333,11 @@
     tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
     tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
     if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
     if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
     var place = cm.cursorCoords(null, "page");
     var place = cm.cursorCoords(null, "page");
-    ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
+    var tooltip = ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip, cm)
+    setTimeout(function() {
+      tooltip.clear = onEditorActivity(cm, function() {
+        if (ts.activeArgHints == tooltip) closeArgHints(ts) })
+    }, 20)
   }
   }
 
 
   function parseFnType(text) {
   function parseFnType(text) {
@@ -443,7 +448,7 @@
 
 
   function atInterestingExpression(cm) {
   function atInterestingExpression(cm) {
     var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
     var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
-    if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
+    if (tok.start < pos.ch && tok.type == "comment") return false;
     return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
     return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
   }
   }
 
 
@@ -465,11 +470,12 @@
     ts.request(cm, {type: "refs"}, function(error, data) {
     ts.request(cm, {type: "refs"}, function(error, data) {
       if (error) return showError(ts, cm, error);
       if (error) return showError(ts, cm, error);
       var ranges = [], cur = 0;
       var ranges = [], cur = 0;
+      var curPos = cm.getCursor();
       for (var i = 0; i < data.refs.length; i++) {
       for (var i = 0; i < data.refs.length; i++) {
         var ref = data.refs[i];
         var ref = data.refs[i];
         if (ref.file == name) {
         if (ref.file == name) {
           ranges.push({anchor: ref.start, head: ref.end});
           ranges.push({anchor: ref.start, head: ref.end});
-          if (cmpPos(cur, ref.start) >= 0 && cmpPos(cur, ref.end) <= 0)
+          if (cmpPos(curPos, ref.start) >= 0 && cmpPos(curPos, ref.end) <= 0)
             cur = ranges.length - 1;
             cur = ranges.length - 1;
         }
         }
       }
       }
@@ -564,7 +570,7 @@
     return {type: "part",
     return {type: "part",
             name: data.name,
             name: data.name,
             offsetLines: from.line,
             offsetLines: from.line,
-            text: doc.getRange(from, Pos(endLine, 0))};
+            text: doc.getRange(from, Pos(endLine, end.line == endLine ? null : 0))};
   }
   }
 
 
   // Generic utilities
   // Generic utilities
@@ -591,41 +597,83 @@
 
 
   // Tooltips
   // Tooltips
 
 
-  function tempTooltip(cm, content) {
+  function tempTooltip(cm, content, ts) {
     if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
     if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
     var where = cm.cursorCoords();
     var where = cm.cursorCoords();
-    var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
+    var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content, cm);
     function maybeClear() {
     function maybeClear() {
       old = true;
       old = true;
       if (!mouseOnTip) clear();
       if (!mouseOnTip) clear();
     }
     }
     function clear() {
     function clear() {
       cm.state.ternTooltip = null;
       cm.state.ternTooltip = null;
-      if (!tip.parentNode) return;
-      cm.off("cursorActivity", clear);
-      cm.off('blur', clear);
-      cm.off('scroll', clear);
-      fadeOut(tip);
+      if (tip.parentNode) fadeOut(tip)
+      clearActivity()
     }
     }
     var mouseOnTip = false, old = false;
     var mouseOnTip = false, old = false;
     CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
     CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
     CodeMirror.on(tip, "mouseout", function(e) {
     CodeMirror.on(tip, "mouseout", function(e) {
-      if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
+      var related = e.relatedTarget || e.toElement
+      if (!related || !CodeMirror.contains(tip, related)) {
         if (old) clear();
         if (old) clear();
         else mouseOnTip = false;
         else mouseOnTip = false;
       }
       }
     });
     });
-    setTimeout(maybeClear, 1700);
-    cm.on("cursorActivity", clear);
-    cm.on('blur', clear);
-    cm.on('scroll', clear);
+    setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700);
+    var clearActivity = onEditorActivity(cm, clear)
+  }
+
+  function onEditorActivity(cm, f) {
+    cm.on("cursorActivity", f)
+    cm.on("blur", f)
+    cm.on("scroll", f)
+    cm.on("setDoc", f)
+    return function() {
+      cm.off("cursorActivity", f)
+      cm.off("blur", f)
+      cm.off("scroll", f)
+      cm.off("setDoc", f)
+    }
   }
   }
 
 
-  function makeTooltip(x, y, content) {
-    var node = elt("div", cls + "tooltip", content);
+  function makeTooltip(x, y, content, cm, className) {
+    var node = elt("div", cls + "tooltip" + " " + (className || ""), content);
     node.style.left = x + "px";
     node.style.left = x + "px";
     node.style.top = y + "px";
     node.style.top = y + "px";
-    document.body.appendChild(node);
+    var container = ((cm.options || {}).hintOptions || {}).container || document.body;
+    container.appendChild(node);
+
+    var pos = cm.cursorCoords();
+    var winW = window.innerWidth;
+    var winH = window.innerHeight;
+    var box = node.getBoundingClientRect();
+    var hints = document.querySelector(".CodeMirror-hints");
+    var overlapY = box.bottom - winH;
+    var overlapX = box.right - winW;
+
+    if (hints && overlapX > 0) {
+      node.style.left = 0;
+      var box = node.getBoundingClientRect();
+      node.style.left = (x = x - hints.offsetWidth - box.width) + "px";
+      overlapX = box.right - winW;
+    }
+    if (overlapY > 0) {
+      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
+      if (curTop - height > 0) { // Fits above cursor
+        node.style.top = (pos.top - height) + "px";
+      } else if (height > winH) {
+        node.style.height = (winH - 5) + "px";
+        node.style.top = (pos.bottom - box.top) + "px";
+      }
+    }
+    if (overlapX > 0) {
+      if (box.right - box.left > winW) {
+        node.style.width = (winW - 5) + "px";
+        overlapX -= (box.right - box.left) - winW;
+      }
+      node.style.left = (x - overlapX) + "px";
+    }
+
     return node;
     return node;
   }
   }
 
 
@@ -643,11 +691,15 @@
     if (ts.options.showError)
     if (ts.options.showError)
       ts.options.showError(cm, msg);
       ts.options.showError(cm, msg);
     else
     else
-      tempTooltip(cm, String(msg));
+      tempTooltip(cm, String(msg), ts);
   }
   }
 
 
   function closeArgHints(ts) {
   function closeArgHints(ts) {
-    if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+    if (ts.activeArgHints) {
+      if (ts.activeArgHints.clear) ts.activeArgHints.clear()
+      remove(ts.activeArgHints)
+      ts.activeArgHints = null
+    }
   }
   }
 
 
   function docValue(ts, doc) {
   function docValue(ts, doc) {

+ 42 - 31
editor/js/libs/codemirror/codemirror.css

@@ -5,6 +5,7 @@
   font-family: monospace;
   font-family: monospace;
   height: 300px;
   height: 300px;
   color: black;
   color: black;
+  direction: ltr;
 }
 }
 
 
 /* PADDING */
 /* PADDING */
@@ -12,7 +13,8 @@
 .CodeMirror-lines {
 .CodeMirror-lines {
   padding: 4px 0; /* Vertical padding around content */
   padding: 4px 0; /* Vertical padding around content */
 }
 }
-.CodeMirror pre {
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
   padding: 0 4px; /* Horizontal padding of content */
   padding: 0 4px; /* Horizontal padding of content */
 }
 }
 
 
@@ -52,16 +54,20 @@
 }
 }
 .cm-fat-cursor .CodeMirror-cursor {
 .cm-fat-cursor .CodeMirror-cursor {
   width: auto;
   width: auto;
-  border: 0;
+  border: 0 !important;
   background: #7e7;
   background: #7e7;
 }
 }
 .cm-fat-cursor div.CodeMirror-cursors {
 .cm-fat-cursor div.CodeMirror-cursors {
   z-index: 1;
   z-index: 1;
 }
 }
-
+.cm-fat-cursor-mark {
+  background-color: rgba(20, 255, 20, 0.5);
+  -webkit-animation: blink 1.06s steps(1) infinite;
+  -moz-animation: blink 1.06s steps(1) infinite;
+  animation: blink 1.06s steps(1) infinite;
+}
 .cm-animate-fat-cursor {
 .cm-animate-fat-cursor {
   width: auto;
   width: auto;
-  border: 0;
   -webkit-animation: blink 1.06s steps(1) infinite;
   -webkit-animation: blink 1.06s steps(1) infinite;
   -moz-animation: blink 1.06s steps(1) infinite;
   -moz-animation: blink 1.06s steps(1) infinite;
   animation: blink 1.06s steps(1) infinite;
   animation: blink 1.06s steps(1) infinite;
@@ -88,8 +94,14 @@
 
 
 .cm-tab { display: inline-block; text-decoration: inherit; }
 .cm-tab { display: inline-block; text-decoration: inherit; }
 
 
+.CodeMirror-rulers {
+  position: absolute;
+  left: 0; right: 0; top: -50px; bottom: 0;
+  overflow: hidden;
+}
 .CodeMirror-ruler {
 .CodeMirror-ruler {
   border-left: 1px solid #ccc;
   border-left: 1px solid #ccc;
+  top: 0; bottom: 0;
   position: absolute;
   position: absolute;
 }
 }
 
 
@@ -113,7 +125,7 @@
 .cm-s-default .cm-property,
 .cm-s-default .cm-property,
 .cm-s-default .cm-operator {}
 .cm-s-default .cm-operator {}
 .cm-s-default .cm-variable-2 {color: #05a;}
 .cm-s-default .cm-variable-2 {color: #05a;}
-.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
 .cm-s-default .cm-comment {color: #a50;}
 .cm-s-default .cm-comment {color: #a50;}
 .cm-s-default .cm-string {color: #a11;}
 .cm-s-default .cm-string {color: #a11;}
 .cm-s-default .cm-string-2 {color: #f50;}
 .cm-s-default .cm-string-2 {color: #f50;}
@@ -133,8 +145,8 @@
 
 
 /* Default styles for common addons */
 /* Default styles for common addons */
 
 
-div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
 .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
 .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
 .CodeMirror-activeline-background {background: #e8f2ff;}
 .CodeMirror-activeline-background {background: #e8f2ff;}
 
 
@@ -151,17 +163,17 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
 
 
 .CodeMirror-scroll {
 .CodeMirror-scroll {
   overflow: scroll !important; /* Things will break if this is overridden */
   overflow: scroll !important; /* Things will break if this is overridden */
-  /* 30px is the magic margin used to hide the element's real scrollbars */
+  /* 50px is the magic margin used to hide the element's real scrollbars */
   /* See overflow: hidden in .CodeMirror */
   /* See overflow: hidden in .CodeMirror */
-  margin-bottom: -30px; margin-right: -30px;
-  padding-bottom: 30px;
+  margin-bottom: -50px; margin-right: -50px;
+  padding-bottom: 50px;
   height: 100%;
   height: 100%;
   outline: none; /* Prevent dragging from highlighting the element */
   outline: none; /* Prevent dragging from highlighting the element */
   position: relative;
   position: relative;
 }
 }
 .CodeMirror-sizer {
 .CodeMirror-sizer {
   position: relative;
   position: relative;
-  border-right: 30px solid transparent;
+  border-right: 50px solid transparent;
 }
 }
 
 
 /* The fake, visible scrollbars. Used to force redraw during scrolling
 /* The fake, visible scrollbars. Used to force redraw during scrolling
@@ -171,6 +183,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
   position: absolute;
   position: absolute;
   z-index: 6;
   z-index: 6;
   display: none;
   display: none;
+  outline: none;
 }
 }
 .CodeMirror-vscrollbar {
 .CodeMirror-vscrollbar {
   right: 0; top: 0;
   right: 0; top: 0;
@@ -199,10 +212,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
   height: 100%;
   height: 100%;
   display: inline-block;
   display: inline-block;
   vertical-align: top;
   vertical-align: top;
-  margin-bottom: -30px;
-  /* Hack to make IE7 behave */
-  *zoom:1;
-  *display:inline;
+  margin-bottom: -50px;
 }
 }
 .CodeMirror-gutter-wrapper {
 .CodeMirror-gutter-wrapper {
   position: absolute;
   position: absolute;
@@ -220,17 +230,15 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
   cursor: default;
   cursor: default;
   z-index: 4;
   z-index: 4;
 }
 }
-.CodeMirror-gutter-wrapper {
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  user-select: none;
-}
+.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
+.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
 
 
 .CodeMirror-lines {
 .CodeMirror-lines {
   cursor: text;
   cursor: text;
   min-height: 1px; /* prevents collapsing before first draw */
   min-height: 1px; /* prevents collapsing before first draw */
 }
 }
-.CodeMirror pre {
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
   /* Reset some styles that the rest of the page might have set */
   /* Reset some styles that the rest of the page might have set */
   -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
   -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
   border-width: 0;
   border-width: 0;
@@ -246,10 +254,11 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
   position: relative;
   position: relative;
   overflow: visible;
   overflow: visible;
   -webkit-tap-highlight-color: transparent;
   -webkit-tap-highlight-color: transparent;
-  -webkit-font-variant-ligatures: none;
-  font-variant-ligatures: none;
+  -webkit-font-variant-ligatures: contextual;
+  font-variant-ligatures: contextual;
 }
 }
-.CodeMirror-wrap pre {
+.CodeMirror-wrap pre.CodeMirror-line,
+.CodeMirror-wrap pre.CodeMirror-line-like {
   word-wrap: break-word;
   word-wrap: break-word;
   white-space: pre-wrap;
   white-space: pre-wrap;
   word-break: normal;
   word-break: normal;
@@ -264,11 +273,13 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
 .CodeMirror-linewidget {
 .CodeMirror-linewidget {
   position: relative;
   position: relative;
   z-index: 2;
   z-index: 2;
-  overflow: auto;
+  padding: 0.1px; /* Force widget margins to stay inside of the container */
 }
 }
 
 
 .CodeMirror-widget {}
 .CodeMirror-widget {}
 
 
+.CodeMirror-rtl pre { direction: rtl; }
+
 .CodeMirror-code {
 .CodeMirror-code {
   outline: none;
   outline: none;
 }
 }
@@ -291,7 +302,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
   visibility: hidden;
   visibility: hidden;
 }
 }
 
 
-.CodeMirror-cursor { position: absolute; }
+.CodeMirror-cursor {
+  position: absolute;
+  pointer-events: none;
+}
 .CodeMirror-measure pre { position: static; }
 .CodeMirror-measure pre { position: static; }
 
 
 div.CodeMirror-cursors {
 div.CodeMirror-cursors {
@@ -314,13 +328,10 @@ div.CodeMirror-dragcursors {
 .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
 .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
 
 
 .cm-searching {
 .cm-searching {
-  background: #ffa;
-  background: rgba(255, 255, 0, .4);
+  background-color: #ffa;
+  background-color: rgba(255, 255, 0, .4);
 }
 }
 
 
-/* IE7 hack to prevent it from returning funny offsetTops on the spans */
-.CodeMirror span { *vertical-align: text-bottom; }
-
 /* Used to force a border model for a node */
 /* Used to force a border model for a node */
 .cm-force-border { padding-right: .1px; }
 .cm-force-border { padding-right: .1px; }
 
 

File diff suppressed because it is too large
+ 1961 - 2174
editor/js/libs/codemirror/codemirror.js


+ 399 - 182
editor/js/libs/codemirror/mode/javascript.js

@@ -1,7 +1,5 @@
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
 // CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// TODO actually recognize syntax of TypeScript constructs
+// Distributed under an MIT license: https://codemirror.net/LICENSE
 
 
 (function(mod) {
 (function(mod) {
   if (typeof exports == "object" && typeof module == "object") // CommonJS
   if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -13,16 +11,12 @@
 })(function(CodeMirror) {
 })(function(CodeMirror) {
 "use strict";
 "use strict";
 
 
-function expressionAllowed(stream, state, backUp) {
-  return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
-    (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
-}
-
 CodeMirror.defineMode("javascript", function(config, parserConfig) {
 CodeMirror.defineMode("javascript", function(config, parserConfig) {
   var indentUnit = config.indentUnit;
   var indentUnit = config.indentUnit;
   var statementIndent = parserConfig.statementIndent;
   var statementIndent = parserConfig.statementIndent;
   var jsonldMode = parserConfig.jsonld;
   var jsonldMode = parserConfig.jsonld;
   var jsonMode = parserConfig.json || jsonldMode;
   var jsonMode = parserConfig.json || jsonldMode;
+  var trackScope = parserConfig.trackScope !== false
   var isTS = parserConfig.typescript;
   var isTS = parserConfig.typescript;
   var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
   var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
 
 
@@ -30,54 +24,24 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
 
 
   var keywords = function(){
   var keywords = function(){
     function kw(type) {return {type: type, style: "keyword"};}
     function kw(type) {return {type: type, style: "keyword"};}
-    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
     var operator = kw("operator"), atom = {type: "atom", style: "atom"};
     var operator = kw("operator"), atom = {type: "atom", style: "atom"};
 
 
-    var jsKeywords = {
+    return {
       "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
       "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
-      "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
-      "var": kw("var"), "const": kw("var"), "let": kw("var"),
+      "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
+      "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
       "function": kw("function"), "catch": kw("catch"),
       "function": kw("function"), "catch": kw("catch"),
       "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
       "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
       "in": operator, "typeof": operator, "instanceof": operator,
       "in": operator, "typeof": operator, "instanceof": operator,
       "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
       "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
       "this": kw("this"), "class": kw("class"), "super": kw("atom"),
       "this": kw("this"), "class": kw("class"), "super": kw("atom"),
-      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
+      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
+      "await": C
     };
     };
-
-    // Extend the 'normal' keywords with the TypeScript language extensions
-    if (isTS) {
-      var type = {type: "variable", style: "variable-3"};
-      var tsKeywords = {
-        // object-like things
-        "interface": kw("class"),
-        "implements": C,
-        "namespace": C,
-        "module": kw("module"),
-        "enum": kw("module"),
-
-        // scope modifiers
-        "public": kw("modifier"),
-        "private": kw("modifier"),
-        "protected": kw("modifier"),
-        "abstract": kw("modifier"),
-
-        // operators
-        "as": operator,
-
-        // types
-        "string": type, "number": type, "boolean": type, "any": type
-      };
-
-      for (var attr in tsKeywords) {
-        jsKeywords[attr] = tsKeywords[attr];
-      }
-    }
-
-    return jsKeywords;
   }();
   }();
 
 
-  var isOperatorChar = /[+\-*&%=<>!?|~^]/;
+  var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
   var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
   var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
 
 
   function readRegexp(stream) {
   function readRegexp(stream) {
@@ -104,7 +68,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (ch == '"' || ch == "'") {
     if (ch == '"' || ch == "'") {
       state.tokenize = tokenString(ch);
       state.tokenize = tokenString(ch);
       return state.tokenize(stream, state);
       return state.tokenize(stream, state);
-    } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
+    } else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
       return ret("number", "number");
       return ret("number", "number");
     } else if (ch == "." && stream.match("..")) {
     } else if (ch == "." && stream.match("..")) {
       return ret("spread", "meta");
       return ret("spread", "meta");
@@ -112,17 +76,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
       return ret(ch);
       return ret(ch);
     } else if (ch == "=" && stream.eat(">")) {
     } else if (ch == "=" && stream.eat(">")) {
       return ret("=>", "operator");
       return ret("=>", "operator");
-    } else if (ch == "0" && stream.eat(/x/i)) {
-      stream.eatWhile(/[\da-f]/i);
-      return ret("number", "number");
-    } else if (ch == "0" && stream.eat(/o/i)) {
-      stream.eatWhile(/[0-7]/i);
-      return ret("number", "number");
-    } else if (ch == "0" && stream.eat(/b/i)) {
-      stream.eatWhile(/[01]/i);
+    } else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
       return ret("number", "number");
       return ret("number", "number");
     } else if (/\d/.test(ch)) {
     } else if (/\d/.test(ch)) {
-      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
+      stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
       return ret("number", "number");
       return ret("number", "number");
     } else if (ch == "/") {
     } else if (ch == "/") {
       if (stream.eat("*")) {
       if (stream.eat("*")) {
@@ -133,26 +90,47 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
         return ret("comment", "comment");
         return ret("comment", "comment");
       } else if (expressionAllowed(stream, state, 1)) {
       } else if (expressionAllowed(stream, state, 1)) {
         readRegexp(stream);
         readRegexp(stream);
-        stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
+        stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
         return ret("regexp", "string-2");
         return ret("regexp", "string-2");
       } else {
       } else {
-        stream.eatWhile(isOperatorChar);
+        stream.eat("=");
         return ret("operator", "operator", stream.current());
         return ret("operator", "operator", stream.current());
       }
       }
     } else if (ch == "`") {
     } else if (ch == "`") {
       state.tokenize = tokenQuasi;
       state.tokenize = tokenQuasi;
       return tokenQuasi(stream, state);
       return tokenQuasi(stream, state);
-    } else if (ch == "#") {
+    } else if (ch == "#" && stream.peek() == "!") {
       stream.skipToEnd();
       stream.skipToEnd();
-      return ret("error", "error");
+      return ret("meta", "meta");
+    } else if (ch == "#" && stream.eatWhile(wordRE)) {
+      return ret("variable", "property")
+    } else if (ch == "<" && stream.match("!--") ||
+               (ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
+      stream.skipToEnd()
+      return ret("comment", "comment")
     } else if (isOperatorChar.test(ch)) {
     } else if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
+      if (ch != ">" || !state.lexical || state.lexical.type != ">") {
+        if (stream.eat("=")) {
+          if (ch == "!" || ch == "=") stream.eat("=")
+        } else if (/[<>*+\-|&?]/.test(ch)) {
+          stream.eat(ch)
+          if (ch == ">") stream.eat(ch)
+        }
+      }
+      if (ch == "?" && stream.eat(".")) return ret(".")
       return ret("operator", "operator", stream.current());
       return ret("operator", "operator", stream.current());
     } else if (wordRE.test(ch)) {
     } else if (wordRE.test(ch)) {
       stream.eatWhile(wordRE);
       stream.eatWhile(wordRE);
-      var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
-      return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
-                     ret("variable", "variable", word);
+      var word = stream.current()
+      if (state.lastType != ".") {
+        if (keywords.propertyIsEnumerable(word)) {
+          var kw = keywords[word]
+          return ret(kw.type, kw.style, word)
+        }
+        if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
+          return ret("async", "keyword", word)
+      }
+      return ret("variable", "variable", word)
     }
     }
   }
   }
 
 
@@ -209,19 +187,28 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     var arrow = stream.string.indexOf("=>", stream.start);
     var arrow = stream.string.indexOf("=>", stream.start);
     if (arrow < 0) return;
     if (arrow < 0) return;
 
 
+    if (isTS) { // Try to skip TypeScript return type declarations after the arguments
+      var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
+      if (m) arrow = m.index
+    }
+
     var depth = 0, sawSomething = false;
     var depth = 0, sawSomething = false;
     for (var pos = arrow - 1; pos >= 0; --pos) {
     for (var pos = arrow - 1; pos >= 0; --pos) {
       var ch = stream.string.charAt(pos);
       var ch = stream.string.charAt(pos);
       var bracket = brackets.indexOf(ch);
       var bracket = brackets.indexOf(ch);
       if (bracket >= 0 && bracket < 3) {
       if (bracket >= 0 && bracket < 3) {
         if (!depth) { ++pos; break; }
         if (!depth) { ++pos; break; }
-        if (--depth == 0) break;
+        if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
       } else if (bracket >= 3 && bracket < 6) {
       } else if (bracket >= 3 && bracket < 6) {
         ++depth;
         ++depth;
       } else if (wordRE.test(ch)) {
       } else if (wordRE.test(ch)) {
         sawSomething = true;
         sawSomething = true;
-      } else if (/["'\/]/.test(ch)) {
-        return;
+      } else if (/["'\/`]/.test(ch)) {
+        for (;; --pos) {
+          if (pos == 0) return
+          var next = stream.string.charAt(pos - 1)
+          if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
+        }
       } else if (sawSomething && !depth) {
       } else if (sawSomething && !depth) {
         ++pos;
         ++pos;
         break;
         break;
@@ -232,7 +219,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
 
 
   // Parser
   // Parser
 
 
-  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
+  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
+                     "regexp": true, "this": true, "import": true, "jsonld-keyword": true};
 
 
   function JSLexical(indented, column, type, align, prev, info) {
   function JSLexical(indented, column, type, align, prev, info) {
     this.indented = indented;
     this.indented = indented;
@@ -244,6 +232,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
   }
   }
 
 
   function inScope(state, varname) {
   function inScope(state, varname) {
+    if (!trackScope) return false
     for (var v = state.localVars; v; v = v.next)
     for (var v = state.localVars; v; v = v.next)
       if (v.name == varname) return true;
       if (v.name == varname) return true;
     for (var cx = state.context; cx; cx = cx.prev) {
     for (var cx = state.context; cx; cx = cx.prev) {
@@ -283,35 +272,69 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     pass.apply(null, arguments);
     pass.apply(null, arguments);
     return true;
     return true;
   }
   }
+  function inList(name, list) {
+    for (var v = list; v; v = v.next) if (v.name == name) return true
+    return false;
+  }
   function register(varname) {
   function register(varname) {
-    function inList(list) {
-      for (var v = list; v; v = v.next)
-        if (v.name == varname) return true;
-      return false;
-    }
     var state = cx.state;
     var state = cx.state;
     cx.marked = "def";
     cx.marked = "def";
+    if (!trackScope) return
     if (state.context) {
     if (state.context) {
-      if (inList(state.localVars)) return;
-      state.localVars = {name: varname, next: state.localVars};
+      if (state.lexical.info == "var" && state.context && state.context.block) {
+        // FIXME function decls are also not block scoped
+        var newContext = registerVarScoped(varname, state.context)
+        if (newContext != null) {
+          state.context = newContext
+          return
+        }
+      } else if (!inList(varname, state.localVars)) {
+        state.localVars = new Var(varname, state.localVars)
+        return
+      }
+    }
+    // Fall through means this is global
+    if (parserConfig.globalVars && !inList(varname, state.globalVars))
+      state.globalVars = new Var(varname, state.globalVars)
+  }
+  function registerVarScoped(varname, context) {
+    if (!context) {
+      return null
+    } else if (context.block) {
+      var inner = registerVarScoped(varname, context.prev)
+      if (!inner) return null
+      if (inner == context.prev) return context
+      return new Context(inner, context.vars, true)
+    } else if (inList(varname, context.vars)) {
+      return context
     } else {
     } else {
-      if (inList(state.globalVars)) return;
-      if (parserConfig.globalVars)
-        state.globalVars = {name: varname, next: state.globalVars};
+      return new Context(context.prev, new Var(varname, context.vars), false)
     }
     }
   }
   }
 
 
+  function isModifier(name) {
+    return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
+  }
+
   // Combinators
   // Combinators
 
 
-  var defaultVars = {name: "this", next: {name: "arguments"}};
+  function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
+  function Var(name, next) { this.name = name; this.next = next }
+
+  var defaultVars = new Var("this", new Var("arguments", null))
   function pushcontext() {
   function pushcontext() {
-    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
-    cx.state.localVars = defaultVars;
+    cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
+    cx.state.localVars = defaultVars
+  }
+  function pushblockcontext() {
+    cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
+    cx.state.localVars = null
   }
   }
   function popcontext() {
   function popcontext() {
-    cx.state.localVars = cx.state.context.vars;
-    cx.state.context = cx.state.context.prev;
+    cx.state.localVars = cx.state.context.vars
+    cx.state.context = cx.state.context.prev
   }
   }
+  popcontext.lex = true
   function pushlex(type, info) {
   function pushlex(type, info) {
     var result = function() {
     var result = function() {
       var state = cx.state, indent = state.indented;
       var state = cx.state, indent = state.indented;
@@ -336,56 +359,87 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
   function expect(wanted) {
   function expect(wanted) {
     function exp(type) {
     function exp(type) {
       if (type == wanted) return cont();
       if (type == wanted) return cont();
-      else if (wanted == ";") return pass();
+      else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
       else return cont(exp);
       else return cont(exp);
     };
     };
     return exp;
     return exp;
   }
   }
 
 
   function statement(type, value) {
   function statement(type, value) {
-    if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
-    if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
+    if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
+    if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
     if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
     if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
-    if (type == "{") return cont(pushlex("}"), block, poplex);
+    if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
+    if (type == "debugger") return cont(expect(";"));
+    if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
     if (type == ";") return cont();
     if (type == ";") return cont();
     if (type == "if") {
     if (type == "if") {
       if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
       if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
         cx.state.cc.pop()();
         cx.state.cc.pop()();
-      return cont(pushlex("form"), expression, statement, poplex, maybeelse);
+      return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
     }
     }
     if (type == "function") return cont(functiondef);
     if (type == "function") return cont(functiondef);
-    if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
-    if (type == "variable") return cont(pushlex("stat"), maybelabel);
-    if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
-                                      block, poplex, poplex);
+    if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
+    if (type == "class" || (isTS && value == "interface")) {
+      cx.marked = "keyword"
+      return cont(pushlex("form", type == "class" ? type : value), className, poplex)
+    }
+    if (type == "variable") {
+      if (isTS && value == "declare") {
+        cx.marked = "keyword"
+        return cont(statement)
+      } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
+        cx.marked = "keyword"
+        if (value == "enum") return cont(enumdef);
+        else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
+        else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
+      } else if (isTS && value == "namespace") {
+        cx.marked = "keyword"
+        return cont(pushlex("form"), expression, statement, poplex)
+      } else if (isTS && value == "abstract") {
+        cx.marked = "keyword"
+        return cont(statement)
+      } else {
+        return cont(pushlex("stat"), maybelabel);
+      }
+    }
+    if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
+                                      block, poplex, poplex, popcontext);
     if (type == "case") return cont(expression, expect(":"));
     if (type == "case") return cont(expression, expect(":"));
     if (type == "default") return cont(expect(":"));
     if (type == "default") return cont(expect(":"));
-    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
-                                     statement, poplex, popcontext);
-    if (type == "class") return cont(pushlex("form"), className, poplex);
+    if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
     if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
     if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
     if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
     if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
-    if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex);
+    if (type == "async") return cont(statement)
+    if (value == "@") return cont(expression, statement)
     return pass(pushlex("stat"), expression, expect(";"), poplex);
     return pass(pushlex("stat"), expression, expect(";"), poplex);
   }
   }
-  function expression(type) {
-    return expressionInner(type, false);
+  function maybeCatchBinding(type) {
+    if (type == "(") return cont(funarg, expect(")"))
+  }
+  function expression(type, value) {
+    return expressionInner(type, value, false);
   }
   }
-  function expressionNoComma(type) {
-    return expressionInner(type, true);
+  function expressionNoComma(type, value) {
+    return expressionInner(type, value, true);
   }
   }
-  function expressionInner(type, noComma) {
+  function parenExpr(type) {
+    if (type != "(") return pass()
+    return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
+  }
+  function expressionInner(type, value, noComma) {
     if (cx.state.fatArrowAt == cx.stream.start) {
     if (cx.state.fatArrowAt == cx.stream.start) {
       var body = noComma ? arrowBodyNoComma : arrowBody;
       var body = noComma ? arrowBodyNoComma : arrowBody;
-      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
+      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
       else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
       else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
     }
     }
 
 
     var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
     var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
     if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
     if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
     if (type == "function") return cont(functiondef, maybeop);
     if (type == "function") return cont(functiondef, maybeop);
-    if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
-    if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
+    if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
+    if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
+    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
     if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
     if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
     if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
     if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
     if (type == "{") return contCommasep(objprop, "}", null, maybeop);
     if (type == "{") return contCommasep(objprop, "}", null, maybeop);
@@ -397,13 +451,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type.match(/[;\}\)\],]/)) return pass();
     if (type.match(/[;\}\)\],]/)) return pass();
     return pass(expression);
     return pass(expression);
   }
   }
-  function maybeexpressionNoComma(type) {
-    if (type.match(/[;\}\)\],]/)) return pass();
-    return pass(expressionNoComma);
-  }
 
 
   function maybeoperatorComma(type, value) {
   function maybeoperatorComma(type, value) {
-    if (type == ",") return cont(expression);
+    if (type == ",") return cont(maybeexpression);
     return maybeoperatorNoComma(type, value, false);
     return maybeoperatorNoComma(type, value, false);
   }
   }
   function maybeoperatorNoComma(type, value, noComma) {
   function maybeoperatorNoComma(type, value, noComma) {
@@ -411,7 +461,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     var expr = noComma == false ? expression : expressionNoComma;
     var expr = noComma == false ? expression : expressionNoComma;
     if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
     if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
     if (type == "operator") {
     if (type == "operator") {
-      if (/\+\+|--/.test(value)) return cont(me);
+      if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
+      if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
+        return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
       if (value == "?") return cont(expression, expect(":"), expr);
       if (value == "?") return cont(expression, expect(":"), expr);
       return cont(expr);
       return cont(expr);
     }
     }
@@ -420,11 +472,17 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
     if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
     if (type == ".") return cont(property, me);
     if (type == ".") return cont(property, me);
     if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
     if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
+    if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
+    if (type == "regexp") {
+      cx.state.lastType = cx.marked = "operator"
+      cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
+      return cont(expr)
+    }
   }
   }
   function quasi(type, value) {
   function quasi(type, value) {
     if (type != "quasi") return pass();
     if (type != "quasi") return pass();
     if (value.slice(value.length - 2) != "${") return cont(quasi);
     if (value.slice(value.length - 2) != "${") return cont(quasi);
-    return cont(expression, continueQuasi);
+    return cont(maybeexpression, continueQuasi);
   }
   }
   function continueQuasi(type) {
   function continueQuasi(type) {
     if (type == "}") {
     if (type == "}") {
@@ -444,6 +502,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
   function maybeTarget(noComma) {
   function maybeTarget(noComma) {
     return function(type) {
     return function(type) {
       if (type == ".") return cont(noComma ? targetNoComma : target);
       if (type == ".") return cont(noComma ? targetNoComma : target);
+      else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
       else return pass(noComma ? expressionNoComma : expression);
       else return pass(noComma ? expressionNoComma : expression);
     };
     };
   }
   }
@@ -461,21 +520,33 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "variable") {cx.marked = "property"; return cont();}
     if (type == "variable") {cx.marked = "property"; return cont();}
   }
   }
   function objprop(type, value) {
   function objprop(type, value) {
-    if (type == "variable" || cx.style == "keyword") {
+    if (type == "async") {
+      cx.marked = "property";
+      return cont(objprop);
+    } else if (type == "variable" || cx.style == "keyword") {
       cx.marked = "property";
       cx.marked = "property";
       if (value == "get" || value == "set") return cont(getterSetter);
       if (value == "get" || value == "set") return cont(getterSetter);
+      var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
+      if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
+        cx.state.fatArrowAt = cx.stream.pos + m[0].length
       return cont(afterprop);
       return cont(afterprop);
     } else if (type == "number" || type == "string") {
     } else if (type == "number" || type == "string") {
       cx.marked = jsonldMode ? "property" : (cx.style + " property");
       cx.marked = jsonldMode ? "property" : (cx.style + " property");
       return cont(afterprop);
       return cont(afterprop);
     } else if (type == "jsonld-keyword") {
     } else if (type == "jsonld-keyword") {
       return cont(afterprop);
       return cont(afterprop);
-    } else if (type == "modifier") {
+    } else if (isTS && isModifier(value)) {
+      cx.marked = "keyword"
       return cont(objprop)
       return cont(objprop)
     } else if (type == "[") {
     } else if (type == "[") {
-      return cont(expression, expect("]"), afterprop);
+      return cont(expression, maybetype, expect("]"), afterprop);
     } else if (type == "spread") {
     } else if (type == "spread") {
-      return cont(expression);
+      return cont(expressionNoComma, afterprop);
+    } else if (value == "*") {
+      cx.marked = "keyword";
+      return cont(objprop);
+    } else if (type == ":") {
+      return pass(afterprop)
     }
     }
   }
   }
   function getterSetter(type) {
   function getterSetter(type) {
@@ -487,18 +558,22 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == ":") return cont(expressionNoComma);
     if (type == ":") return cont(expressionNoComma);
     if (type == "(") return pass(functiondef);
     if (type == "(") return pass(functiondef);
   }
   }
-  function commasep(what, end) {
-    function proceed(type) {
-      if (type == ",") {
+  function commasep(what, end, sep) {
+    function proceed(type, value) {
+      if (sep ? sep.indexOf(type) > -1 : type == ",") {
         var lex = cx.state.lexical;
         var lex = cx.state.lexical;
         if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
         if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
-        return cont(what, proceed);
+        return cont(function(type, value) {
+          if (type == end || value == end) return pass()
+          return pass(what)
+        }, proceed);
       }
       }
-      if (type == end) return cont();
+      if (type == end || value == end) return cont();
+      if (sep && sep.indexOf(";") > -1) return pass(what)
       return cont(expect(end));
       return cont(expect(end));
     }
     }
-    return function(type) {
-      if (type == end) return cont();
+    return function(type, value) {
+      if (type == end || value == end) return cont();
       return pass(what, proceed);
       return pass(what, proceed);
     };
     };
   }
   }
@@ -511,23 +586,111 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "}") return cont();
     if (type == "}") return cont();
     return pass(statement, block);
     return pass(statement, block);
   }
   }
-  function maybetype(type) {
-    if (isTS && type == ":") return cont(typedef);
+  function maybetype(type, value) {
+    if (isTS) {
+      if (type == ":") return cont(typeexpr);
+      if (value == "?") return cont(maybetype);
+    }
   }
   }
-  function maybedefault(_, value) {
-    if (value == "=") return cont(expressionNoComma);
+  function maybetypeOrIn(type, value) {
+    if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
   }
   }
-  function typedef(type) {
-    if (type == "variable") {cx.marked = "variable-3"; return cont();}
+  function mayberettype(type) {
+    if (isTS && type == ":") {
+      if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
+      else return cont(typeexpr)
+    }
   }
   }
-  function vardef() {
+  function isKW(_, value) {
+    if (value == "is") {
+      cx.marked = "keyword"
+      return cont()
+    }
+  }
+  function typeexpr(type, value) {
+    if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
+      cx.marked = "keyword"
+      return cont(value == "typeof" ? expressionNoComma : typeexpr)
+    }
+    if (type == "variable" || value == "void") {
+      cx.marked = "type"
+      return cont(afterType)
+    }
+    if (value == "|" || value == "&") return cont(typeexpr)
+    if (type == "string" || type == "number" || type == "atom") return cont(afterType);
+    if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
+    if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
+    if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
+    if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
+    if (type == "quasi") { return pass(quasiType, afterType); }
+  }
+  function maybeReturnType(type) {
+    if (type == "=>") return cont(typeexpr)
+  }
+  function typeprops(type) {
+    if (type.match(/[\}\)\]]/)) return cont()
+    if (type == "," || type == ";") return cont(typeprops)
+    return pass(typeprop, typeprops)
+  }
+  function typeprop(type, value) {
+    if (type == "variable" || cx.style == "keyword") {
+      cx.marked = "property"
+      return cont(typeprop)
+    } else if (value == "?" || type == "number" || type == "string") {
+      return cont(typeprop)
+    } else if (type == ":") {
+      return cont(typeexpr)
+    } else if (type == "[") {
+      return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
+    } else if (type == "(") {
+      return pass(functiondecl, typeprop)
+    } else if (!type.match(/[;\}\)\],]/)) {
+      return cont()
+    }
+  }
+  function quasiType(type, value) {
+    if (type != "quasi") return pass();
+    if (value.slice(value.length - 2) != "${") return cont(quasiType);
+    return cont(typeexpr, continueQuasiType);
+  }
+  function continueQuasiType(type) {
+    if (type == "}") {
+      cx.marked = "string-2";
+      cx.state.tokenize = tokenQuasi;
+      return cont(quasiType);
+    }
+  }
+  function typearg(type, value) {
+    if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
+    if (type == ":") return cont(typeexpr)
+    if (type == "spread") return cont(typearg)
+    return pass(typeexpr)
+  }
+  function afterType(type, value) {
+    if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
+    if (value == "|" || type == "." || value == "&") return cont(typeexpr)
+    if (type == "[") return cont(typeexpr, expect("]"), afterType)
+    if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
+    if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
+  }
+  function maybeTypeArgs(_, value) {
+    if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
+  }
+  function typeparam() {
+    return pass(typeexpr, maybeTypeDefault)
+  }
+  function maybeTypeDefault(_, value) {
+    if (value == "=") return cont(typeexpr)
+  }
+  function vardef(_, value) {
+    if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
     return pass(pattern, maybetype, maybeAssign, vardefCont);
     return pass(pattern, maybetype, maybeAssign, vardefCont);
   }
   }
   function pattern(type, value) {
   function pattern(type, value) {
-    if (type == "modifier") return cont(pattern)
+    if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
     if (type == "variable") { register(value); return cont(); }
     if (type == "variable") { register(value); return cont(); }
     if (type == "spread") return cont(pattern);
     if (type == "spread") return cont(pattern);
-    if (type == "[") return contCommasep(pattern, "]");
+    if (type == "[") return contCommasep(eltpattern, "]");
     if (type == "{") return contCommasep(proppattern, "}");
     if (type == "{") return contCommasep(proppattern, "}");
   }
   }
   function proppattern(type, value) {
   function proppattern(type, value) {
@@ -538,8 +701,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "variable") cx.marked = "property";
     if (type == "variable") cx.marked = "property";
     if (type == "spread") return cont(pattern);
     if (type == "spread") return cont(pattern);
     if (type == "}") return pass();
     if (type == "}") return pass();
+    if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
     return cont(expect(":"), pattern, maybeAssign);
     return cont(expect(":"), pattern, maybeAssign);
   }
   }
+  function eltpattern() {
+    return pass(pattern, maybeAssign)
+  }
   function maybeAssign(_type, value) {
   function maybeAssign(_type, value) {
     if (value == "=") return cont(expressionNoComma);
     if (value == "=") return cont(expressionNoComma);
   }
   }
@@ -549,73 +716,111 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
   function maybeelse(type, value) {
   function maybeelse(type, value) {
     if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
     if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
   }
   }
-  function forspec(type) {
-    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
+  function forspec(type, value) {
+    if (value == "await") return cont(forspec);
+    if (type == "(") return cont(pushlex(")"), forspec1, poplex);
   }
   }
   function forspec1(type) {
   function forspec1(type) {
-    if (type == "var") return cont(vardef, expect(";"), forspec2);
-    if (type == ";") return cont(forspec2);
-    if (type == "variable") return cont(formaybeinof);
-    return pass(expression, expect(";"), forspec2);
-  }
-  function formaybeinof(_type, value) {
-    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
-    return cont(maybeoperatorComma, forspec2);
+    if (type == "var") return cont(vardef, forspec2);
+    if (type == "variable") return cont(forspec2);
+    return pass(forspec2)
   }
   }
   function forspec2(type, value) {
   function forspec2(type, value) {
-    if (type == ";") return cont(forspec3);
-    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
-    return pass(expression, expect(";"), forspec3);
-  }
-  function forspec3(type) {
-    if (type != ")") cont(expression);
+    if (type == ")") return cont()
+    if (type == ";") return cont(forspec2)
+    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
+    return pass(expression, forspec2)
   }
   }
   function functiondef(type, value) {
   function functiondef(type, value) {
     if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
     if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
     if (type == "variable") {register(value); return cont(functiondef);}
     if (type == "variable") {register(value); return cont(functiondef);}
-    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
+    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
+    if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
+  }
+  function functiondecl(type, value) {
+    if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
+    if (type == "variable") {register(value); return cont(functiondecl);}
+    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
+    if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
+  }
+  function typename(type, value) {
+    if (type == "keyword" || type == "variable") {
+      cx.marked = "type"
+      return cont(typename)
+    } else if (value == "<") {
+      return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
+    }
   }
   }
-  function funarg(type) {
+  function funarg(type, value) {
+    if (value == "@") cont(expression, funarg)
     if (type == "spread") return cont(funarg);
     if (type == "spread") return cont(funarg);
-    return pass(pattern, maybetype, maybedefault);
+    if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
+    if (isTS && type == "this") return cont(maybetype, maybeAssign)
+    return pass(pattern, maybetype, maybeAssign);
+  }
+  function classExpression(type, value) {
+    // Class expressions may have an optional name.
+    if (type == "variable") return className(type, value);
+    return classNameAfter(type, value);
   }
   }
   function className(type, value) {
   function className(type, value) {
     if (type == "variable") {register(value); return cont(classNameAfter);}
     if (type == "variable") {register(value); return cont(classNameAfter);}
   }
   }
   function classNameAfter(type, value) {
   function classNameAfter(type, value) {
-    if (value == "extends") return cont(expression, classNameAfter);
+    if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
+    if (value == "extends" || value == "implements" || (isTS && type == ",")) {
+      if (value == "implements") cx.marked = "keyword";
+      return cont(isTS ? typeexpr : expression, classNameAfter);
+    }
     if (type == "{") return cont(pushlex("}"), classBody, poplex);
     if (type == "{") return cont(pushlex("}"), classBody, poplex);
   }
   }
   function classBody(type, value) {
   function classBody(type, value) {
+    if (type == "async" ||
+        (type == "variable" &&
+         (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
+         cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
+      cx.marked = "keyword";
+      return cont(classBody);
+    }
     if (type == "variable" || cx.style == "keyword") {
     if (type == "variable" || cx.style == "keyword") {
-      if (value == "static") {
-        cx.marked = "keyword";
-        return cont(classBody);
-      }
       cx.marked = "property";
       cx.marked = "property";
-      if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
-      return cont(functiondef, classBody);
+      return cont(classfield, classBody);
     }
     }
+    if (type == "number" || type == "string") return cont(classfield, classBody);
+    if (type == "[")
+      return cont(expression, maybetype, expect("]"), classfield, classBody)
     if (value == "*") {
     if (value == "*") {
       cx.marked = "keyword";
       cx.marked = "keyword";
       return cont(classBody);
       return cont(classBody);
     }
     }
-    if (type == ";") return cont(classBody);
+    if (isTS && type == "(") return pass(functiondecl, classBody)
+    if (type == ";" || type == ",") return cont(classBody);
     if (type == "}") return cont();
     if (type == "}") return cont();
+    if (value == "@") return cont(expression, classBody)
   }
   }
-  function classGetterSetter(type) {
-    if (type != "variable") return pass();
-    cx.marked = "property";
-    return cont();
+  function classfield(type, value) {
+    if (value == "!") return cont(classfield)
+    if (value == "?") return cont(classfield)
+    if (type == ":") return cont(typeexpr, maybeAssign)
+    if (value == "=") return cont(expressionNoComma)
+    var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
+    return pass(isInterface ? functiondecl : functiondef)
   }
   }
-  function afterExport(_type, value) {
+  function afterExport(type, value) {
     if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
     if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
     if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
     if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
+    if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
     return pass(statement);
     return pass(statement);
   }
   }
+  function exportField(type, value) {
+    if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
+    if (type == "variable") return pass(expressionNoComma, exportField);
+  }
   function afterImport(type) {
   function afterImport(type) {
     if (type == "string") return cont();
     if (type == "string") return cont();
-    return pass(importSpec, maybeFrom);
+    if (type == "(") return pass(expression);
+    if (type == ".") return pass(maybeoperatorComma);
+    return pass(importSpec, maybeMoreImports, maybeFrom);
   }
   }
   function importSpec(type, value) {
   function importSpec(type, value) {
     if (type == "{") return contCommasep(importSpec, "}");
     if (type == "{") return contCommasep(importSpec, "}");
@@ -623,6 +828,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (value == "*") cx.marked = "keyword";
     if (value == "*") cx.marked = "keyword";
     return cont(maybeAs);
     return cont(maybeAs);
   }
   }
+  function maybeMoreImports(type) {
+    if (type == ",") return cont(importSpec, maybeMoreImports)
+  }
   function maybeAs(_type, value) {
   function maybeAs(_type, value) {
     if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
     if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
   }
   }
@@ -631,16 +839,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
   }
   }
   function arrayLiteral(type) {
   function arrayLiteral(type) {
     if (type == "]") return cont();
     if (type == "]") return cont();
-    return pass(expressionNoComma, maybeArrayComprehension);
-  }
-  function maybeArrayComprehension(type) {
-    if (type == "for") return pass(comprehension, expect("]"));
-    if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
     return pass(commasep(expressionNoComma, "]"));
     return pass(commasep(expressionNoComma, "]"));
   }
   }
-  function comprehension(type) {
-    if (type == "for") return cont(forspec, comprehension);
-    if (type == "if") return cont(expression, comprehension);
+  function enumdef() {
+    return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
+  }
+  function enummember() {
+    return pass(pattern, maybeAssign);
   }
   }
 
 
   function isContinuedStatement(state, textAfter) {
   function isContinuedStatement(state, textAfter) {
@@ -649,6 +854,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
       /[,.]/.test(textAfter.charAt(0));
       /[,.]/.test(textAfter.charAt(0));
   }
   }
 
 
+  function expressionAllowed(stream, state, backUp) {
+    return state.tokenize == tokenBase &&
+      /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
+      (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
+  }
+
   // Interface
   // Interface
 
 
   return {
   return {
@@ -659,7 +870,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
         cc: [],
         cc: [],
         lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
         lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
         localVars: parserConfig.localVars,
         localVars: parserConfig.localVars,
-        context: parserConfig.localVars && {vars: parserConfig.localVars},
+        context: parserConfig.localVars && new Context(null, null, false),
         indented: basecolumn || 0
         indented: basecolumn || 0
       };
       };
       if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
       if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
@@ -682,21 +893,25 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     },
     },
 
 
     indent: function(state, textAfter) {
     indent: function(state, textAfter) {
-      if (state.tokenize == tokenComment) return CodeMirror.Pass;
+      if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
       if (state.tokenize != tokenBase) return 0;
       if (state.tokenize != tokenBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
+      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
       // Kludge to prevent 'maybelse' from blocking lexical scope pops
       // Kludge to prevent 'maybelse' from blocking lexical scope pops
       if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
       if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
         var c = state.cc[i];
         var c = state.cc[i];
         if (c == poplex) lexical = lexical.prev;
         if (c == poplex) lexical = lexical.prev;
-        else if (c != maybeelse) break;
+        else if (c != maybeelse && c != popcontext) break;
       }
       }
-      if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
+      while ((lexical.type == "stat" || lexical.type == "form") &&
+             (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
+                                   (top == maybeoperatorComma || top == maybeoperatorNoComma) &&
+                                   !/^[,\.=+\-*:?[\(]/.test(textAfter))))
+        lexical = lexical.prev;
       if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
       if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
         lexical = lexical.prev;
         lexical = lexical.prev;
       var type = lexical.type, closing = firstChar == type;
       var type = lexical.type, closing = firstChar == type;
 
 
-      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
+      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
       else if (type == "form" && firstChar == "{") return lexical.indented;
       else if (type == "form" && firstChar == "{") return lexical.indented;
       else if (type == "form") return lexical.indented + indentUnit;
       else if (type == "form") return lexical.indented + indentUnit;
       else if (type == "stat")
       else if (type == "stat")
@@ -710,6 +925,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
     electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
     blockCommentStart: jsonMode ? null : "/*",
     blockCommentStart: jsonMode ? null : "/*",
     blockCommentEnd: jsonMode ? null : "*/",
     blockCommentEnd: jsonMode ? null : "*/",
+    blockCommentContinue: jsonMode ? null : " * ",
     lineComment: jsonMode ? null : "//",
     lineComment: jsonMode ? null : "//",
     fold: "brace",
     fold: "brace",
     closeBrackets: "()[]{}''\"\"``",
     closeBrackets: "()[]{}''\"\"``",
@@ -719,9 +935,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     jsonMode: jsonMode,
     jsonMode: jsonMode,
 
 
     expressionAllowed: expressionAllowed,
     expressionAllowed: expressionAllowed,
+
     skipExpression: function(state) {
     skipExpression: function(state) {
-      var top = state.cc[state.cc.length - 1]
-      if (top == expression || top == expressionNoComma) state.cc.pop()
+      parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
     }
     }
   };
   };
 });
 });
@@ -733,9 +949,10 @@ CodeMirror.defineMIME("text/ecmascript", "javascript");
 CodeMirror.defineMIME("application/javascript", "javascript");
 CodeMirror.defineMIME("application/javascript", "javascript");
 CodeMirror.defineMIME("application/x-javascript", "javascript");
 CodeMirror.defineMIME("application/x-javascript", "javascript");
 CodeMirror.defineMIME("application/ecmascript", "javascript");
 CodeMirror.defineMIME("application/ecmascript", "javascript");
-CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
-CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
-CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
+CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
+CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
+CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
+CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
 CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
 CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
 CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
 CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
 
 

+ 6 - 1
editor/js/libs/codemirror/theme/monokai.css

@@ -14,6 +14,11 @@
 .cm-s-monokai span.cm-atom { color: #ae81ff; }
 .cm-s-monokai span.cm-atom { color: #ae81ff; }
 .cm-s-monokai span.cm-number { color: #ae81ff; }
 .cm-s-monokai span.cm-number { color: #ae81ff; }
 
 
+.cm-s-monokai span.cm-comment.cm-attribute { color: #97b757; }
+.cm-s-monokai span.cm-comment.cm-def { color: #bc9262; }
+.cm-s-monokai span.cm-comment.cm-tag { color: #bc6283; }
+.cm-s-monokai span.cm-comment.cm-type { color: #5998a6; }
+
 .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
 .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
 .cm-s-monokai span.cm-keyword { color: #f92672; }
 .cm-s-monokai span.cm-keyword { color: #f92672; }
 .cm-s-monokai span.cm-builtin { color: #66d9ef; }
 .cm-s-monokai span.cm-builtin { color: #66d9ef; }
@@ -21,7 +26,7 @@
 
 
 .cm-s-monokai span.cm-variable { color: #f8f8f2; }
 .cm-s-monokai span.cm-variable { color: #f8f8f2; }
 .cm-s-monokai span.cm-variable-2 { color: #9effff; }
 .cm-s-monokai span.cm-variable-2 { color: #9effff; }
-.cm-s-monokai span.cm-variable-3 { color: #66d9ef; }
+.cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; }
 .cm-s-monokai span.cm-def { color: #fd971f; }
 .cm-s-monokai span.cm-def { color: #fd971f; }
 .cm-s-monokai span.cm-bracket { color: #f8f8f2; }
 .cm-s-monokai span.cm-bracket { color: #f8f8f2; }
 .cm-s-monokai span.cm-tag { color: #f92672; }
 .cm-s-monokai span.cm-tag { color: #f92672; }

+ 1 - 1
editor/sw.js

@@ -1,4 +1,4 @@
-// r132.1
+// r133
 
 
 const cacheName = 'threejs-editor';
 const cacheName = 'threejs-editor';
 
 

+ 5 - 3
examples/files.json

@@ -1,6 +1,5 @@
- {
+{
 	"webgl": [
 	"webgl": [
-		"webgl_animation_cloth",
 		"webgl_animation_keyframes",
 		"webgl_animation_keyframes",
 		"webgl_animation_skinning_blending",
 		"webgl_animation_skinning_blending",
 		"webgl_animation_skinning_additive_blending",
 		"webgl_animation_skinning_additive_blending",
@@ -175,6 +174,7 @@
 		"webgl_modifier_simplifier",
 		"webgl_modifier_simplifier",
 		"webgl_modifier_tessellation",
 		"webgl_modifier_tessellation",
 		"webgl_morphtargets",
 		"webgl_morphtargets",
+		"webgl_morphtargets_face",
 		"webgl_morphtargets_horse",
 		"webgl_morphtargets_horse",
 		"webgl_morphtargets_sphere",
 		"webgl_morphtargets_sphere",
 		"webgl_multiple_canvases_circle",
 		"webgl_multiple_canvases_circle",
@@ -205,7 +205,6 @@
 		"webgl_shaders_ocean",
 		"webgl_shaders_ocean",
 		"webgl_shaders_sky",
 		"webgl_shaders_sky",
 		"webgl_shaders_tonemapping",
 		"webgl_shaders_tonemapping",
-		"webgl_shading_physical",
 		"webgl_shadow_contact",
 		"webgl_shadow_contact",
 		"webgl_shadowmap",
 		"webgl_shadowmap",
 		"webgl_shadowmap_performance",
 		"webgl_shadowmap_performance",
@@ -229,10 +228,12 @@
 		"webgl_materials_compile",
 		"webgl_materials_compile",
 		"webgl_materials_envmaps_hdr_nodes",
 		"webgl_materials_envmaps_hdr_nodes",
 		"webgl_materials_envmaps_pmrem_nodes",
 		"webgl_materials_envmaps_pmrem_nodes",
+		"webgl_materials_instance_uniform_nodes",
 		"webgl_materials_nodes",
 		"webgl_materials_nodes",
 		"webgl_materials_standard_nodes",
 		"webgl_materials_standard_nodes",
 		"webgl_mirror_nodes",
 		"webgl_mirror_nodes",
 		"webgl_performance_nodes",
 		"webgl_performance_nodes",
+		"webgl_points_nodes",
 		"webgl_postprocessing_nodes",
 		"webgl_postprocessing_nodes",
 		"webgl_postprocessing_nodes_pass",
 		"webgl_postprocessing_nodes_pass",
 		"webgl_sprites_nodes"
 		"webgl_sprites_nodes"
@@ -369,6 +370,7 @@
 		"misc_animation_groups",
 		"misc_animation_groups",
 		"misc_animation_keys",
 		"misc_animation_keys",
 		"misc_boxselection",
 		"misc_boxselection",
+		"misc_controls_arcball",
 		"misc_controls_deviceorientation",
 		"misc_controls_deviceorientation",
 		"misc_controls_drag",
 		"misc_controls_drag",
 		"misc_controls_fly",
 		"misc_controls_fly",

+ 97 - 36
examples/games_fps.html

@@ -74,7 +74,7 @@
 
 
 			const GRAVITY = 30;
 			const GRAVITY = 30;
 
 
-			const NUM_SPHERES = 20;
+			const NUM_SPHERES = 100;
 			const SPHERE_RADIUS = 0.2;
 			const SPHERE_RADIUS = 0.2;
 
 
 			const STEPS_PER_FRAME = 5;
 			const STEPS_PER_FRAME = 5;
@@ -105,9 +105,14 @@
 			const playerDirection = new THREE.Vector3();
 			const playerDirection = new THREE.Vector3();
 
 
 			let playerOnFloor = false;
 			let playerOnFloor = false;
+			let mouseTime = 0;
 
 
 			const keyStates = {};
 			const keyStates = {};
 
 
+			const vector1 = new THREE.Vector3();
+			const vector2 = new THREE.Vector3();
+			const vector3 = new THREE.Vector3();
+
 			document.addEventListener( 'keydown', ( event ) => {
 			document.addEventListener( 'keydown', ( event ) => {
 
 
 				keyStates[ event.code ] = true;
 				keyStates[ event.code ] = true;
@@ -124,6 +129,14 @@
 
 
 				document.body.requestPointerLock();
 				document.body.requestPointerLock();
 
 
+				mouseTime = performance.now();
+
+			} );
+
+			document.addEventListener( 'mouseup', () => {
+
+				throwBall();
+
 			} );
 			} );
 
 
 			document.body.addEventListener( 'mousemove', ( event ) => {
 			document.body.addEventListener( 'mousemove', ( event ) => {
@@ -148,20 +161,26 @@
 
 
 			}
 			}
 
 
-			document.addEventListener( 'click', () => {
+			function throwBall() {
 
 
 				const sphere = spheres[ sphereIdx ];
 				const sphere = spheres[ sphereIdx ];
 
 
 				camera.getWorldDirection( playerDirection );
 				camera.getWorldDirection( playerDirection );
 
 
-				sphere.collider.center.copy( playerCollider.end );
-				sphere.velocity.copy( playerDirection ).multiplyScalar( 30 );
+				sphere.collider.center.copy( playerCollider.end ).addScaledVector( playerDirection, playerCollider.radius * 1.5 );
+
+				// throw the ball with more force if we hold the button longer, and if we move forward
+
+				const impulse = 15 + 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) );
+
+				sphere.velocity.copy( playerDirection ).multiplyScalar( impulse );
+				sphere.velocity.addScaledVector( playerVelocity, 2 );
 
 
 				sphereIdx = ( sphereIdx + 1 ) % spheres.length;
 				sphereIdx = ( sphereIdx + 1 ) % spheres.length;
 
 
-			} );
+			}
 
 
-			function playerCollitions() {
+			function playerCollisions() {
 
 
 				const result = worldOctree.capsuleIntersect( playerCollider );
 				const result = worldOctree.capsuleIntersect( playerCollider );
 
 
@@ -185,33 +204,68 @@
 
 
 			function updatePlayer( deltaTime ) {
 			function updatePlayer( deltaTime ) {
 
 
-				if ( playerOnFloor ) {
-
-					const damping = Math.exp( - 3 * deltaTime ) - 1;
-					playerVelocity.addScaledVector( playerVelocity, damping );
+				let damping = Math.exp( - 4 * deltaTime ) - 1;
 
 
-				} else {
+				if ( ! playerOnFloor ) {
 
 
 					playerVelocity.y -= GRAVITY * deltaTime;
 					playerVelocity.y -= GRAVITY * deltaTime;
 
 
+					// small air resistance
+					damping *= 0.1;
+
 				}
 				}
 
 
+				playerVelocity.addScaledVector( playerVelocity, damping );
+
 				const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime );
 				const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime );
 				playerCollider.translate( deltaPosition );
 				playerCollider.translate( deltaPosition );
 
 
-				playerCollitions();
+				playerCollisions();
 
 
 				camera.position.copy( playerCollider.end );
 				camera.position.copy( playerCollider.end );
 
 
 			}
 			}
 
 
+			function playerSphereCollision( sphere ) {
+
+				const center = vector1.addVectors( playerCollider.start, playerCollider.end ).multiplyScalar( 0.5 );
+
+				const sphere_center = sphere.collider.center;
+
+				const r = playerCollider.radius + sphere.collider.radius;
+				const r2 = r * r;
+
+				// approximation: player = 3 spheres
+
+				for ( const point of [ playerCollider.start, playerCollider.end, center ] ) {
+
+					const d2 = point.distanceToSquared( sphere_center );
+
+					if ( d2 < r2 ) {
+
+						const normal = vector1.subVectors( point, sphere_center ).normalize();
+						const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( playerVelocity ) );
+						const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( sphere.velocity ) );
+
+						playerVelocity.add( v2 ).sub( v1 );
+						sphere.velocity.add( v1 ).sub( v2 );
+
+						const d = ( r - Math.sqrt( d2 ) ) / 2;
+						sphere_center.addScaledVector( normal, - d );
+
+					}
+
+				}
+
+			}
+
 			function spheresCollisions() {
 			function spheresCollisions() {
 
 
-				for ( let i = 0; i < spheres.length; i ++ ) {
+				for ( let i = 0, length = spheres.length; i < length; i ++ ) {
 
 
 					const s1 = spheres[ i ];
 					const s1 = spheres[ i ];
 
 
-					for ( let j = i + 1; j < spheres.length; j ++ ) {
+					for ( let j = i + 1; j < length; j ++ ) {
 
 
 						const s2 = spheres[ j ];
 						const s2 = spheres[ j ];
 
 
@@ -221,9 +275,10 @@
 
 
 						if ( d2 < r2 ) {
 						if ( d2 < r2 ) {
 
 
-							const normal = s1.collider.clone().center.sub( s2.collider.center ).normalize();
-							const v1 = normal.clone().multiplyScalar( normal.dot( s1.velocity ) );
-							const v2 = normal.clone().multiplyScalar( normal.dot( s2.velocity ) );
+							const normal = vector1.subVectors( s1.collider.center, s2.collider.center ).normalize();
+							const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( s1.velocity ) );
+							const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( s2.velocity ) );
+
 							s1.velocity.add( v2 ).sub( v1 );
 							s1.velocity.add( v2 ).sub( v1 );
 							s2.velocity.add( v1 ).sub( v2 );
 							s2.velocity.add( v1 ).sub( v2 );
 
 
@@ -242,7 +297,7 @@
 
 
 			function updateSpheres( deltaTime ) {
 			function updateSpheres( deltaTime ) {
 
 
-				spheres.forEach( sphere =>{
+				spheres.forEach( sphere => {
 
 
 					sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );
 					sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );
 
 
@@ -262,11 +317,17 @@
 					const damping = Math.exp( - 1.5 * deltaTime ) - 1;
 					const damping = Math.exp( - 1.5 * deltaTime ) - 1;
 					sphere.velocity.addScaledVector( sphere.velocity, damping );
 					sphere.velocity.addScaledVector( sphere.velocity, damping );
 
 
-					spheresCollisions();
+					playerSphereCollision( sphere );
+
+				} );
+
+				spheresCollisions();
+
+				for ( const sphere of spheres ) {
 
 
 					sphere.mesh.position.copy( sphere.collider.center );
 					sphere.mesh.position.copy( sphere.collider.center );
 
 
-				} );
+				}
 
 
 			}
 			}
 
 
@@ -293,33 +354,34 @@
 
 
 			function controls( deltaTime ) {
 			function controls( deltaTime ) {
 
 
-				const speed = 25;
+				// gives a bit of air control
+				const speedDelta = deltaTime * ( playerOnFloor ? 25 : 8 );
 
 
-				if ( playerOnFloor ) {
+				if ( keyStates[ 'KeyW' ] ) {
 
 
-					if ( keyStates[ 'KeyW' ] ) {
+					playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) );
 
 
-						playerVelocity.add( getForwardVector().multiplyScalar( speed * deltaTime ) );
+				}
 
 
-					}
+				if ( keyStates[ 'KeyS' ] ) {
 
 
-					if ( keyStates[ 'KeyS' ] ) {
+					playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) );
 
 
-						playerVelocity.add( getForwardVector().multiplyScalar( - speed * deltaTime ) );
+				}
 
 
-					}
+				if ( keyStates[ 'KeyA' ] ) {
 
 
-					if ( keyStates[ 'KeyA' ] ) {
+					playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) );
 
 
-						playerVelocity.add( getSideVector().multiplyScalar( - speed * deltaTime ) );
+				}
 
 
-					}
+				if ( keyStates[ 'KeyD' ] ) {
 
 
-					if ( keyStates[ 'KeyD' ] ) {
+					playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) );
 
 
-						playerVelocity.add( getSideVector().multiplyScalar( speed * deltaTime ) );
+				}
 
 
-					}
+				if ( playerOnFloor ) {
 
 
 					if ( keyStates[ 'Space' ] ) {
 					if ( keyStates[ 'Space' ] ) {
 
 
@@ -367,7 +429,7 @@
 				// we look for collisions in substeps to mitigate the risk of
 				// we look for collisions in substeps to mitigate the risk of
 				// an object traversing another too quickly for detection.
 				// an object traversing another too quickly for detection.
 
 
-				for ( let i = 0 ; i < STEPS_PER_FRAME ; i ++ ) {
+				for ( let i = 0; i < STEPS_PER_FRAME; i ++ ) {
 
 
 					controls( deltaTime );
 					controls( deltaTime );
 
 
@@ -385,7 +447,6 @@
 
 
 			}
 			}
 
 
-
 		</script>
 		</script>
 	</body>
 	</body>
 </html>
 </html>

+ 26 - 1
examples/index.html

@@ -66,9 +66,11 @@
 		const expandButton = document.getElementById( 'expandButton' );
 		const expandButton = document.getElementById( 'expandButton' );
 		const viewSrcButton = document.getElementById( 'button' );
 		const viewSrcButton = document.getElementById( 'button' );
 		const panelScrim = document.getElementById( 'panelScrim' );
 		const panelScrim = document.getElementById( 'panelScrim' );
-
 		const previewsToggler = document.getElementById( 'previewsToggler' );
 		const previewsToggler = document.getElementById( 'previewsToggler' );
 
 
+		const sectionLink = document.querySelector( '#sections > a' );
+		const sectionDefaultHref = sectionLink.href;
+
 		const links = {};
 		const links = {};
 		const validRedirects = new Map();
 		const validRedirects = new Map();
 		const container = document.createElement( 'div' );
 		const container = document.createElement( 'div' );
@@ -133,6 +135,10 @@
 
 
 				updateFilter( files, tags );
 				updateFilter( files, tags );
 
 
+			} else {
+
+				updateLink( '' );
+
 			}
 			}
 
 
 			// Events
 			// Events
@@ -280,6 +286,25 @@
 
 
 			layoutList( files );
 			layoutList( files );
 
 
+			updateLink( v );
+
+		}
+
+		function updateLink( search ) {
+
+			// update docs link
+
+			if ( search ) {
+
+				let link = sectionLink.href.split( /[?#]/ )[ 0 ];
+				sectionLink.href = `${link}?q=${search}`;
+
+			} else {
+
+				sectionLink.href = sectionDefaultHref;
+
+			}
+
 		}
 		}
 
 
 		function filterExample( file, exp, tags ) {
 		function filterExample( file, exp, tags ) {

+ 2995 - 0
examples/js/controls/ArcballControls.js

@@ -0,0 +1,2995 @@
+( function () {
+
+	const STATE = {
+		IDLE: Symbol(),
+		ROTATE: Symbol(),
+		PAN: Symbol(),
+		SCALE: Symbol(),
+		FOV: Symbol(),
+		FOCUS: Symbol(),
+		ZROTATE: Symbol(),
+		TOUCH_MULTI: Symbol(),
+		ANIMATION_FOCUS: Symbol(),
+		ANIMATION_ROTATE: Symbol()
+	};
+	const INPUT = {
+		NONE: Symbol(),
+		ONE_FINGER: Symbol(),
+		ONE_FINGER_SWITCHED: Symbol(),
+		TWO_FINGER: Symbol(),
+		MULT_FINGER: Symbol(),
+		CURSOR: Symbol()
+	}; //cursor center coordinates
+
+	const _center = {
+		x: 0,
+		y: 0
+	}; //transformation matrices for gizmos and camera
+
+	const _transformation = {
+		camera: new THREE.Matrix4(),
+		gizmos: new THREE.Matrix4()
+	}; //events
+
+	const _changeEvent = {
+		type: 'change'
+	};
+	const _startEvent = {
+		type: 'start'
+	};
+	const _endEvent = {
+		type: 'end'
+	};
+	/**
+ *
+ * @param {Camera} camera Virtual camera used in the scene
+ * @param {HTMLElement} domElement Renderer's dom element
+ * @param {Scene} scene The scene to be rendered
+ */
+
+	class ArcballControls extends THREE.Object3D {
+
+		constructor( _camera, domElement, scene = null ) {
+
+			super();
+
+			this.onWindowResize = () => {
+
+				const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
+				this._tbRadius = this.calculateTbRadius( this.camera );
+				const newRadius = this._tbRadius / scale;
+				const curve = new THREE.EllipseCurve( 0, 0, newRadius, newRadius );
+				const points = curve.getPoints( this._curvePts );
+				const curveGeometry = new THREE.BufferGeometry().setFromPoints( points );
+
+				for ( const gizmo in this._gizmos.children ) {
+
+					this._gizmos.children[ gizmo ].geometry = curveGeometry;
+
+				}
+
+				this.dispatchEvent( _changeEvent );
+
+			};
+
+			this.onContextMenu = event => {
+
+				if ( ! this.enabled ) {
+
+					return;
+
+				}
+
+				for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+					if ( this.mouseActions[ i ].mouse == 2 ) {
+
+						//prevent only if button 2 is actually used
+						event.preventDefault();
+						break;
+
+					}
+
+				}
+
+			};
+
+			this.onPointerCancel = () => {
+
+				this._touchStart.splice( 0, this._touchStart.length );
+
+				this._touchCurrent.splice( 0, this._touchCurrent.length );
+
+				this._input = INPUT.NONE;
+
+			};
+
+			this.onPointerDown = event => {
+
+				if ( event.button == 0 && event.isPrimary ) {
+
+					this._downValid = true;
+
+					this._downEvents.push( event );
+
+					this._downStart = performance.now();
+
+				} else {
+
+					this._downValid = false;
+
+				}
+
+				if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
+
+					this._touchStart.push( event );
+
+					this._touchCurrent.push( event );
+
+					switch ( this._input ) {
+
+						case INPUT.NONE:
+							//singleStart
+							this._input = INPUT.ONE_FINGER;
+							this.onSinglePanStart( event, 'ROTATE' );
+							window.addEventListener( 'pointermove', this.onPointerMove );
+							window.addEventListener( 'pointerup', this.onPointerUp );
+							break;
+
+						case INPUT.ONE_FINGER:
+						case INPUT.ONE_FINGER_SWITCHED:
+							//doubleStart
+							this._input = INPUT.TWO_FINGER;
+							this.onRotateStart();
+							this.onPinchStart();
+							this.onDoublePanStart();
+							break;
+
+						case INPUT.TWO_FINGER:
+							//multipleStart
+							this._input = INPUT.MULT_FINGER;
+							this.onTriplePanStart( event );
+							break;
+
+					}
+
+				} else if ( event.pointerType != 'touch' && this._input == INPUT.NONE ) {
+
+					let modifier = null;
+
+					if ( event.ctrlKey || event.metaKey ) {
+
+						modifier = 'CTRL';
+
+					} else if ( event.shiftKey ) {
+
+						modifier = 'SHIFT';
+
+					}
+
+					this._mouseOp = this.getOpFromAction( event.button, modifier );
+
+					if ( this._mouseOp != null ) {
+
+						window.addEventListener( 'pointermove', this.onPointerMove );
+						window.addEventListener( 'pointerup', this.onPointerUp ); //singleStart
+
+						this._input = INPUT.CURSOR;
+						this._button = event.button;
+						this.onSinglePanStart( event, this._mouseOp );
+
+					}
+
+				}
+
+			};
+
+			this.onPointerMove = event => {
+
+				if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
+
+					switch ( this._input ) {
+
+						case INPUT.ONE_FINGER:
+							//singleMove
+							this.updateTouchEvent( event );
+							this.onSinglePanMove( event, STATE.ROTATE );
+							break;
+
+						case INPUT.ONE_FINGER_SWITCHED:
+							const movement = this.calculatePointersDistance( this._touchCurrent[ 0 ], event ) * this._devPxRatio;
+
+							if ( movement >= this._switchSensibility ) {
+
+								//singleMove
+								this._input = INPUT.ONE_FINGER;
+								this.updateTouchEvent( event );
+								this.onSinglePanStart( event, 'ROTATE' );
+								break;
+
+							}
+
+							break;
+
+						case INPUT.TWO_FINGER:
+							//rotate/pan/pinchMove
+							this.updateTouchEvent( event );
+							this.onRotateMove();
+							this.onPinchMove();
+							this.onDoublePanMove();
+							break;
+
+						case INPUT.MULT_FINGER:
+							//multMove
+							this.updateTouchEvent( event );
+							this.onTriplePanMove( event );
+							break;
+
+					}
+
+				} else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
+
+					let modifier = null;
+
+					if ( event.ctrlKey || event.metaKey ) {
+
+						modifier = 'CTRL';
+
+					} else if ( event.shiftKey ) {
+
+						modifier = 'SHIFT';
+
+					}
+
+					const mouseOpState = this.getOpStateFromAction( this._button, modifier );
+
+					if ( mouseOpState != null ) {
+
+						this.onSinglePanMove( event, mouseOpState );
+
+					}
+
+				} //checkDistance
+
+
+				if ( this._downValid ) {
+
+					const movement = this.calculatePointersDistance( this._downEvents[ this._downEvents.length - 1 ], event ) * this._devPxRatio;
+
+					if ( movement > this._movementThreshold ) {
+
+						this._downValid = false;
+
+					}
+
+				}
+
+			};
+
+			this.onPointerUp = event => {
+
+				if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
+
+					const nTouch = this._touchCurrent.length;
+
+					for ( let i = 0; i < nTouch; i ++ ) {
+
+						if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
+
+							this._touchCurrent.splice( i, 1 );
+
+							this._touchStart.splice( i, 1 );
+
+							break;
+
+						}
+
+					}
+
+					switch ( this._input ) {
+
+						case INPUT.ONE_FINGER:
+						case INPUT.ONE_FINGER_SWITCHED:
+							//singleEnd
+							window.removeEventListener( 'pointermove', this.onPointerMove );
+							window.removeEventListener( 'pointerup', this.onPointerUp );
+							this._input = INPUT.NONE;
+							this.onSinglePanEnd();
+							break;
+
+						case INPUT.TWO_FINGER:
+							//doubleEnd
+							this.onDoublePanEnd( event );
+							this.onPinchEnd( event );
+							this.onRotateEnd( event ); //switching to singleStart
+
+							this._input = INPUT.ONE_FINGER_SWITCHED;
+							break;
+
+						case INPUT.MULT_FINGER:
+							if ( this._touchCurrent.length == 0 ) {
+
+								window.removeEventListener( 'pointermove', this.onPointerMove );
+								window.removeEventListener( 'pointerup', this.onPointerUp ); //multCancel
+
+								this._input = INPUT.NONE;
+								this.onTriplePanEnd();
+
+							}
+
+							break;
+
+					}
+
+				} else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
+
+					window.removeEventListener( 'pointermove', this.onPointerMove );
+					window.removeEventListener( 'pointerup', this.onPointerUp );
+					this._input = INPUT.NONE;
+					this.onSinglePanEnd();
+					this._button = - 1;
+
+				}
+
+				if ( event.isPrimary ) {
+
+					if ( this._downValid ) {
+
+						const downTime = event.timeStamp - this._downEvents[ this._downEvents.length - 1 ].timeStamp;
+
+						if ( downTime <= this._maxDownTime ) {
+
+							if ( this._nclicks == 0 ) {
+
+								//first valid click detected
+								this._nclicks = 1;
+								this._clickStart = performance.now();
+
+							} else {
+
+								const clickInterval = event.timeStamp - this._clickStart;
+
+								const movement = this.calculatePointersDistance( this._downEvents[ 1 ], this._downEvents[ 0 ] ) * this._devPxRatio;
+
+								if ( clickInterval <= this._maxInterval && movement <= this._posThreshold ) {
+
+									//second valid click detected
+									//fire double tap and reset values
+									this._nclicks = 0;
+
+									this._downEvents.splice( 0, this._downEvents.length );
+
+									this.onDoubleTap( event );
+
+								} else {
+
+									//new 'first click'
+									this._nclicks = 1;
+
+									this._downEvents.shift();
+
+									this._clickStart = performance.now();
+
+								}
+
+							}
+
+						} else {
+
+							this._downValid = false;
+							this._nclicks = 0;
+
+							this._downEvents.splice( 0, this._downEvents.length );
+
+						}
+
+					} else {
+
+						this._nclicks = 0;
+
+						this._downEvents.splice( 0, this._downEvents.length );
+
+					}
+
+				}
+
+			};
+
+			this.onWheel = event => {
+
+				if ( this.enabled && this.enableZoom ) {
+
+					let modifier = null;
+
+					if ( event.ctrlKey || event.metaKey ) {
+
+						modifier = 'CTRL';
+
+					} else if ( event.shiftKey ) {
+
+						modifier = 'SHIFT';
+
+					}
+
+					const mouseOp = this.getOpFromAction( 'WHEEL', modifier );
+
+					if ( mouseOp != null ) {
+
+						event.preventDefault();
+						this.dispatchEvent( _startEvent );
+						const notchDeltaY = 125; //distance of one notch of mouse wheel
+
+						let sgn = event.deltaY / notchDeltaY;
+						let size = 1;
+
+						if ( sgn > 0 ) {
+
+							size = 1 / this.scaleFactor;
+
+						} else if ( sgn < 0 ) {
+
+							size = this.scaleFactor;
+
+						}
+
+						switch ( mouseOp ) {
+
+							case 'ZOOM':
+								this.updateTbState( STATE.SCALE, true );
+
+								if ( sgn > 0 ) {
+
+									size = 1 / Math.pow( this.scaleFactor, sgn );
+
+								} else if ( sgn < 0 ) {
+
+									size = Math.pow( this.scaleFactor, - sgn );
+
+								}
+
+								if ( this.cursorZoom && this.enablePan ) {
+
+									let scalePoint;
+
+									if ( this.camera.isOrthographicCamera ) {
+
+										scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position );
+
+									} else if ( this.camera.isPerspectiveCamera ) {
+
+										scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position );
+
+									}
+
+									this.applyTransformMatrix( this.scale( size, scalePoint ) );
+
+								} else {
+
+									this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
+
+								}
+
+								if ( this._grid != null ) {
+
+									this.disposeGrid();
+									this.drawGrid();
+
+								}
+
+								this.updateTbState( STATE.IDLE, false );
+								this.dispatchEvent( _changeEvent );
+								this.dispatchEvent( _endEvent );
+								break;
+
+							case 'FOV':
+								if ( this.camera.isPerspectiveCamera ) {
+
+									this.updateTbState( STATE.FOV, true ); //Vertigo effect
+									//	  fov / 2
+									//		|\
+									//		| \
+									//		|  \
+									//	x	|	\
+									//		| 	 \
+									//		| 	  \
+									//		| _ _ _\
+									//			y
+									//check for iOs shift shortcut
+
+									if ( event.deltaX != 0 ) {
+
+										sgn = event.deltaX / notchDeltaY;
+										size = 1;
+
+										if ( sgn > 0 ) {
+
+											size = 1 / Math.pow( this.scaleFactor, sgn );
+
+										} else if ( sgn < 0 ) {
+
+											size = Math.pow( this.scaleFactor, - sgn );
+
+										}
+
+									}
+
+									this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
+
+									const x = this._v3_1.distanceTo( this._gizmos.position );
+
+									let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
+									//check min and max distance
+
+									xNew = THREE.MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
+									const y = x * Math.tan( THREE.MathUtils.DEG2RAD * this.camera.fov * 0.5 ); //calculate new fov
+
+									let newFov = THREE.MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); //check min and max fov
+
+									if ( newFov > this.maxFov ) {
+
+										newFov = this.maxFov;
+
+									} else if ( newFov < this.minFov ) {
+
+										newFov = this.minFov;
+
+									}
+
+									const newDistance = y / Math.tan( THREE.MathUtils.DEG2RAD * ( newFov / 2 ) );
+									size = x / newDistance;
+									this.setFov( newFov );
+									this.applyTransformMatrix( this.scale( size, this._gizmos.position, false ) );
+
+								}
+
+								if ( this._grid != null ) {
+
+									this.disposeGrid();
+									this.drawGrid();
+
+								}
+
+								this.updateTbState( STATE.IDLE, false );
+								this.dispatchEvent( _changeEvent );
+								this.dispatchEvent( _endEvent );
+								break;
+
+						}
+
+					}
+
+				}
+
+			};
+
+			this.onKeyDown = event => {
+
+				if ( event.key == 'c' ) {
+
+					if ( event.ctrlKey || event.metaKey ) {
+
+						this.copyState();
+
+					}
+
+				} else if ( event.key == 'v' ) {
+
+					if ( event.ctrlKey || event.metaKey ) {
+
+						this.pasteState();
+
+					}
+
+				}
+
+			};
+
+			this.onSinglePanStart = ( event, operation ) => {
+
+				if ( this.enabled ) {
+
+					this.dispatchEvent( _startEvent );
+					this.setCenter( event.clientX, event.clientY );
+
+					switch ( operation ) {
+
+						case 'PAN':
+							if ( ! this.enablePan ) {
+
+								return;
+
+							}
+
+							if ( this._animationId != - 1 ) {
+
+								cancelAnimationFrame( this._animationId );
+								this._animationId = - 1;
+								this._timeStart = - 1;
+								this.activateGizmos( false );
+								this.dispatchEvent( _changeEvent );
+
+							}
+
+							this.updateTbState( STATE.PAN, true );
+
+							this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
+
+							if ( this.enableGrid ) {
+
+								this.drawGrid();
+								this.dispatchEvent( _changeEvent );
+
+							}
+
+							break;
+
+						case 'ROTATE':
+							if ( ! this.enableRotate ) {
+
+								return;
+
+							}
+
+							if ( this._animationId != - 1 ) {
+
+								cancelAnimationFrame( this._animationId );
+								this._animationId = - 1;
+								this._timeStart = - 1;
+
+							}
+
+							this.updateTbState( STATE.ROTATE, true );
+
+							this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
+
+							this.activateGizmos( true );
+
+							if ( this.enableAnimations ) {
+
+								this._timePrev = this._timeCurrent = performance.now();
+								this._angleCurrent = this._anglePrev = 0;
+
+								this._cursorPosPrev.copy( this._startCursorPosition );
+
+								this._cursorPosCurr.copy( this._cursorPosPrev );
+
+								this._wCurr = 0;
+								this._wPrev = this._wCurr;
+
+							}
+
+							this.dispatchEvent( _changeEvent );
+							break;
+
+						case 'FOV':
+							if ( ! this.camera.isPerspectiveCamera || ! this.enableZoom ) {
+
+								return;
+
+							}
+
+							if ( this._animationId != - 1 ) {
+
+								cancelAnimationFrame( this._animationId );
+								this._animationId = - 1;
+								this._timeStart = - 1;
+								this.activateGizmos( false );
+								this.dispatchEvent( _changeEvent );
+
+							}
+
+							this.updateTbState( STATE.FOV, true );
+
+							this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+							this._currentCursorPosition.copy( this._startCursorPosition );
+
+							break;
+
+						case 'ZOOM':
+							if ( ! this.enableZoom ) {
+
+								return;
+
+							}
+
+							if ( this._animationId != - 1 ) {
+
+								cancelAnimationFrame( this._animationId );
+								this._animationId = - 1;
+								this._timeStart = - 1;
+								this.activateGizmos( false );
+								this.dispatchEvent( _changeEvent );
+
+							}
+
+							this.updateTbState( STATE.SCALE, true );
+
+							this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+							this._currentCursorPosition.copy( this._startCursorPosition );
+
+							break;
+
+					}
+
+				}
+
+			};
+
+			this.onSinglePanMove = ( event, opState ) => {
+
+				if ( this.enabled ) {
+
+					const restart = opState != this._state;
+					this.setCenter( event.clientX, event.clientY );
+
+					switch ( opState ) {
+
+						case STATE.PAN:
+							if ( this.enablePan ) {
+
+								if ( restart ) {
+
+									//switch to pan operation
+									this.dispatchEvent( _endEvent );
+									this.dispatchEvent( _startEvent );
+									this.updateTbState( opState, true );
+
+									this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
+
+									if ( this.enableGrid ) {
+
+										this.drawGrid();
+
+									}
+
+									this.activateGizmos( false );
+
+								} else {
+
+									//continue with pan operation
+									this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
+
+									this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) );
+
+								}
+
+							}
+
+							break;
+
+						case STATE.ROTATE:
+							if ( this.enableRotate ) {
+
+								if ( restart ) {
+
+									//switch to rotate operation
+									this.dispatchEvent( _endEvent );
+									this.dispatchEvent( _startEvent );
+									this.updateTbState( opState, true );
+
+									this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
+
+									if ( this.enableGrid ) {
+
+										this.disposeGrid();
+
+									}
+
+									this.activateGizmos( true );
+
+								} else {
+
+									//continue with rotate operation
+									this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
+
+									const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition );
+
+									const angle = this._startCursorPosition.angleTo( this._currentCursorPosition );
+
+									const amount = Math.max( distance / this._tbRadius, angle ); //effective rotation angle
+
+									this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) );
+
+									if ( this.enableAnimations ) {
+
+										this._timePrev = this._timeCurrent;
+										this._timeCurrent = performance.now();
+										this._anglePrev = this._angleCurrent;
+										this._angleCurrent = amount;
+
+										this._cursorPosPrev.copy( this._cursorPosCurr );
+
+										this._cursorPosCurr.copy( this._currentCursorPosition );
+
+										this._wPrev = this._wCurr;
+										this._wCurr = this.calculateAngularSpeed( this._anglePrev, this._angleCurrent, this._timePrev, this._timeCurrent );
+
+									}
+
+								}
+
+							}
+
+							break;
+
+						case STATE.SCALE:
+							if ( this.enableZoom ) {
+
+								if ( restart ) {
+
+									//switch to zoom operation
+									this.dispatchEvent( _endEvent );
+									this.dispatchEvent( _startEvent );
+									this.updateTbState( opState, true );
+
+									this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+									this._currentCursorPosition.copy( this._startCursorPosition );
+
+									if ( this.enableGrid ) {
+
+										this.disposeGrid();
+
+									}
+
+									this.activateGizmos( false );
+
+								} else {
+
+									//continue with zoom operation
+									const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
+
+									this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+									const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
+									let size = 1;
+
+									if ( movement < 0 ) {
+
+										size = 1 / Math.pow( this.scaleFactor, - movement * screenNotches );
+
+									} else if ( movement > 0 ) {
+
+										size = Math.pow( this.scaleFactor, movement * screenNotches );
+
+									}
+
+									this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
+
+								}
+
+							}
+
+							break;
+
+						case STATE.FOV:
+							if ( this.enableZoom && this.camera.isPerspectiveCamera ) {
+
+								if ( restart ) {
+
+									//switch to fov operation
+									this.dispatchEvent( _endEvent );
+									this.dispatchEvent( _startEvent );
+									this.updateTbState( opState, true );
+
+									this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+									this._currentCursorPosition.copy( this._startCursorPosition );
+
+									if ( this.enableGrid ) {
+
+										this.disposeGrid();
+
+									}
+
+									this.activateGizmos( false );
+
+								} else {
+
+									//continue with fov operation
+									const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
+
+									this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+									const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
+									let size = 1;
+
+									if ( movement < 0 ) {
+
+										size = 1 / Math.pow( this.scaleFactor, - movement * screenNotches );
+
+									} else if ( movement > 0 ) {
+
+										size = Math.pow( this.scaleFactor, movement * screenNotches );
+
+									}
+
+									this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
+
+									const x = this._v3_1.distanceTo( this._gizmos.position );
+
+									let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
+									//check min and max distance
+
+									xNew = THREE.MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
+									const y = x * Math.tan( THREE.MathUtils.DEG2RAD * this._fovState * 0.5 ); //calculate new fov
+
+									let newFov = THREE.MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); //check min and max fov
+
+									newFov = THREE.MathUtils.clamp( newFov, this.minFov, this.maxFov );
+									const newDistance = y / Math.tan( THREE.MathUtils.DEG2RAD * ( newFov / 2 ) );
+									size = x / newDistance;
+
+									this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
+
+									this.setFov( newFov );
+									this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); //adjusting distance
+
+									const direction = this._gizmos.position.clone().sub( this.camera.position ).normalize().multiplyScalar( newDistance / x );
+
+									this._m4_1.makeTranslation( direction.x, direction.y, direction.z );
+
+								}
+
+							}
+
+							break;
+
+					}
+
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+			};
+
+			this.onSinglePanEnd = () => {
+
+				if ( this._state == STATE.ROTATE ) {
+
+					if ( ! this.enableRotate ) {
+
+						return;
+
+					}
+
+					if ( this.enableAnimations ) {
+
+						//perform rotation animation
+						const deltaTime = performance.now() - this._timeCurrent;
+
+						if ( deltaTime < 120 ) {
+
+							const w = Math.abs( ( this._wPrev + this._wCurr ) / 2 );
+							const self = this;
+							this._animationId = window.requestAnimationFrame( function ( t ) {
+
+								self.updateTbState( STATE.ANIMATION_ROTATE, true );
+								const rotationAxis = self.calculateRotationAxis( self._cursorPosPrev, self._cursorPosCurr );
+								self.onRotationAnim( t, rotationAxis, Math.min( w, self.wMax ) );
+
+							} );
+
+						} else {
+
+							//cursor has been standing still for over 120 ms since last movement
+							this.updateTbState( STATE.IDLE, false );
+							this.activateGizmos( false );
+							this.dispatchEvent( _changeEvent );
+
+						}
+
+					} else {
+
+						this.updateTbState( STATE.IDLE, false );
+						this.activateGizmos( false );
+						this.dispatchEvent( _changeEvent );
+
+					}
+
+				} else if ( this._state == STATE.PAN || this._state == STATE.IDLE ) {
+
+					this.updateTbState( STATE.IDLE, false );
+
+					if ( this.enableGrid ) {
+
+						this.disposeGrid();
+
+					}
+
+					this.activateGizmos( false );
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+				this.dispatchEvent( _endEvent );
+
+			};
+
+			this.onDoubleTap = event => {
+
+				if ( this.enabled && this.enablePan && this.scene != null ) {
+
+					this.dispatchEvent( _startEvent );
+					this.setCenter( event.clientX, event.clientY );
+					const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.camera );
+
+					if ( hitP != null && this.enableAnimations ) {
+
+						const self = this;
+
+						if ( this._animationId != - 1 ) {
+
+							window.cancelAnimationFrame( this._animationId );
+
+						}
+
+						this._timeStart = - 1;
+						this._animationId = window.requestAnimationFrame( function ( t ) {
+
+							self.updateTbState( STATE.ANIMATION_FOCUS, true );
+							self.onFocusAnim( t, hitP, self._cameraMatrixState, self._gizmoMatrixState );
+
+						} );
+
+					} else if ( hitP != null && ! this.enableAnimations ) {
+
+						this.updateTbState( STATE.FOCUS, true );
+						this.focus( hitP, this.scaleFactor );
+						this.updateTbState( STATE.IDLE, false );
+						this.dispatchEvent( _changeEvent );
+
+					}
+
+				}
+
+				this.dispatchEvent( _endEvent );
+
+			};
+
+			this.onDoublePanStart = () => {
+
+				if ( this.enabled && this.enablePan ) {
+
+					this.dispatchEvent( _startEvent );
+					this.updateTbState( STATE.PAN, true );
+					this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
+
+					this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) );
+
+					this._currentCursorPosition.copy( this._startCursorPosition );
+
+					this.activateGizmos( false );
+
+				}
+
+			};
+
+			this.onDoublePanMove = () => {
+
+				if ( this.enabled && this.enablePan ) {
+
+					this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
+
+					if ( this._state != STATE.PAN ) {
+
+						this.updateTbState( STATE.PAN, true );
+
+						this._startCursorPosition.copy( this._currentCursorPosition );
+
+					}
+
+					this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) );
+
+					this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) );
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+			};
+
+			this.onDoublePanEnd = () => {
+
+				this.updateTbState( STATE.IDLE, false );
+				this.dispatchEvent( _endEvent );
+
+			};
+
+			this.onRotateStart = () => {
+
+				if ( this.enabled && this.enableRotate ) {
+
+					this.dispatchEvent( _startEvent );
+					this.updateTbState( STATE.ZROTATE, true ); //this._startFingerRotation = event.rotation;
+
+					this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
+					this._currentFingerRotation = this._startFingerRotation;
+					this.camera.getWorldDirection( this._rotationAxis ); //rotation axis
+
+					if ( ! this.enablePan && ! this.enableZoom ) {
+
+						this.activateGizmos( true );
+
+					}
+
+				}
+
+			};
+
+			this.onRotateMove = () => {
+
+				if ( this.enabled && this.enableRotate ) {
+
+					this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
+					let rotationPoint;
+
+					if ( this._state != STATE.ZROTATE ) {
+
+						this.updateTbState( STATE.ZROTATE, true );
+						this._startFingerRotation = this._currentFingerRotation;
+
+					} //this._currentFingerRotation = event.rotation;
+
+
+					this._currentFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
+
+					if ( ! this.enablePan ) {
+
+						rotationPoint = new THREE.Vector3().setFromMatrixPosition( this._gizmoMatrixState );
+
+					} else {
+
+						this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
+
+						rotationPoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._v3_2 );
+
+					}
+
+					const amount = THREE.MathUtils.DEG2RAD * ( this._startFingerRotation - this._currentFingerRotation );
+					this.applyTransformMatrix( this.zRotate( rotationPoint, amount ) );
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+			};
+
+			this.onRotateEnd = () => {
+
+				this.updateTbState( STATE.IDLE, false );
+				this.activateGizmos( false );
+				this.dispatchEvent( _endEvent );
+
+			};
+
+			this.onPinchStart = () => {
+
+				if ( this.enabled && this.enableZoom ) {
+
+					this.dispatchEvent( _startEvent );
+					this.updateTbState( STATE.SCALE, true );
+					this._startFingerDistance = this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] );
+					this._currentFingerDistance = this._startFingerDistance;
+					this.activateGizmos( false );
+
+				}
+
+			};
+
+			this.onPinchMove = () => {
+
+				if ( this.enabled && this.enableZoom ) {
+
+					this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
+					const minDistance = 12; //minimum distance between fingers (in css pixels)
+
+					if ( this._state != STATE.SCALE ) {
+
+						this._startFingerDistance = this._currentFingerDistance;
+						this.updateTbState( STATE.SCALE, true );
+
+					}
+
+					this._currentFingerDistance = Math.max( this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ), minDistance * this._devPxRatio );
+					const amount = this._currentFingerDistance / this._startFingerDistance;
+					let scalePoint;
+
+					if ( ! this.enablePan ) {
+
+						scalePoint = this._gizmos.position;
+
+					} else {
+
+						if ( this.camera.isOrthographicCamera ) {
+
+							scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position );
+
+						} else if ( this.camera.isPerspectiveCamera ) {
+
+							scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position );
+
+						}
+
+					}
+
+					this.applyTransformMatrix( this.scale( amount, scalePoint ) );
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+			};
+
+			this.onPinchEnd = () => {
+
+				this.updateTbState( STATE.IDLE, false );
+				this.dispatchEvent( _endEvent );
+
+			};
+
+			this.onTriplePanStart = () => {
+
+				if ( this.enabled && this.enableZoom ) {
+
+					this.dispatchEvent( _startEvent );
+					this.updateTbState( STATE.SCALE, true ); //const center = event.center;
+
+					let clientX = 0;
+					let clientY = 0;
+					const nFingers = this._touchCurrent.length;
+
+					for ( let i = 0; i < nFingers; i ++ ) {
+
+						clientX += this._touchCurrent[ i ].clientX;
+						clientY += this._touchCurrent[ i ].clientY;
+
+					}
+
+					this.setCenter( clientX / nFingers, clientY / nFingers );
+
+					this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+					this._currentCursorPosition.copy( this._startCursorPosition );
+
+				}
+
+			};
+
+			this.onTriplePanMove = () => {
+
+				if ( this.enabled && this.enableZoom ) {
+
+					//	  fov / 2
+					//		|\
+					//		| \
+					//		|  \
+					//	x	|	\
+					//		| 	 \
+					//		| 	  \
+					//		| _ _ _\
+					//			y
+					//const center = event.center;
+					let clientX = 0;
+					let clientY = 0;
+					const nFingers = this._touchCurrent.length;
+
+					for ( let i = 0; i < nFingers; i ++ ) {
+
+						clientX += this._touchCurrent[ i ].clientX;
+						clientY += this._touchCurrent[ i ].clientY;
+
+					}
+
+					this.setCenter( clientX / nFingers, clientY / nFingers );
+					const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
+
+					this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
+
+					const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
+					let size = 1;
+
+					if ( movement < 0 ) {
+
+						size = 1 / Math.pow( this.scaleFactor, - movement * screenNotches );
+
+					} else if ( movement > 0 ) {
+
+						size = Math.pow( this.scaleFactor, movement * screenNotches );
+
+					}
+
+					this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
+
+					const x = this._v3_1.distanceTo( this._gizmos.position );
+
+					let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
+					//check min and max distance
+
+					xNew = THREE.MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
+					const y = x * Math.tan( THREE.MathUtils.DEG2RAD * this._fovState * 0.5 ); //calculate new fov
+
+					let newFov = THREE.MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); //check min and max fov
+
+					newFov = THREE.MathUtils.clamp( newFov, this.minFov, this.maxFov );
+					const newDistance = y / Math.tan( THREE.MathUtils.DEG2RAD * ( newFov / 2 ) );
+					size = x / newDistance;
+
+					this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
+
+					this.setFov( newFov );
+					this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); //adjusting distance
+
+					const direction = this._gizmos.position.clone().sub( this.camera.position ).normalize().multiplyScalar( newDistance / x );
+
+					this._m4_1.makeTranslation( direction.x, direction.y, direction.z );
+
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+			};
+
+			this.onTriplePanEnd = () => {
+
+				this.updateTbState( STATE.IDLE, false );
+				this.dispatchEvent( _endEvent ); //this.dispatchEvent( _changeEvent );
+
+			};
+
+			this.setCenter = ( clientX, clientY ) => {
+
+				_center.x = clientX;
+				_center.y = clientY;
+
+			};
+
+			this.initializeMouseActions = () => {
+
+				this.setMouseAction( 'PAN', 0, 'CTRL' );
+				this.setMouseAction( 'PAN', 2 );
+				this.setMouseAction( 'ROTATE', 0 );
+				this.setMouseAction( 'ZOOM', 'WHEEL' );
+				this.setMouseAction( 'ZOOM', 1 );
+				this.setMouseAction( 'FOV', 'WHEEL', 'SHIFT' );
+				this.setMouseAction( 'FOV', 1, 'SHIFT' );
+
+			};
+
+			this.compareMouseAction = ( action1, action2 ) => {
+
+				if ( action1.operation == action2.operation ) {
+
+					if ( action1.mouse == action2.mouse && action1.key == action2.key ) {
+
+						return true;
+
+					} else {
+
+						return false;
+
+					}
+
+				} else {
+
+					return false;
+
+				}
+
+			};
+
+			this.setMouseAction = ( operation, mouse, key = null ) => {
+
+				const operationInput = [ 'PAN', 'ROTATE', 'ZOOM', 'FOV' ];
+				const mouseInput = [ 0, 1, 2, 'WHEEL' ];
+				const keyInput = [ 'CTRL', 'SHIFT', null ];
+				let state;
+
+				if ( ! operationInput.includes( operation ) || ! mouseInput.includes( mouse ) || ! keyInput.includes( key ) ) {
+
+					//invalid parameters
+					return false;
+
+				}
+
+				if ( mouse == 'WHEEL' ) {
+
+					if ( operation != 'ZOOM' && operation != 'FOV' ) {
+
+						//cannot associate 2D operation to 1D input
+						return false;
+
+					}
+
+				}
+
+				switch ( operation ) {
+
+					case 'PAN':
+						state = STATE.PAN;
+						break;
+
+					case 'ROTATE':
+						state = STATE.ROTATE;
+						break;
+
+					case 'ZOOM':
+						state = STATE.SCALE;
+						break;
+
+					case 'FOV':
+						state = STATE.FOV;
+						break;
+
+				}
+
+				const action = {
+					operation: operation,
+					mouse: mouse,
+					key: key,
+					state: state
+				};
+
+				for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+					if ( this.mouseActions[ i ].mouse == action.mouse && this.mouseActions[ i ].key == action.key ) {
+
+						this.mouseActions.splice( i, 1, action );
+						return true;
+
+					}
+
+				}
+
+				this.mouseActions.push( action );
+				return true;
+
+			};
+
+			this.unsetMouseAction = ( mouse, key = null ) => {
+
+				for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+					if ( this.mouseActions[ i ].mouse == mouse && this.mouseActions[ i ].key == key ) {
+
+						this.mouseActions.splice( i, 1 );
+						return true;
+
+					}
+
+				}
+
+				return false;
+
+			};
+
+			this.getOpFromAction = ( mouse, key ) => {
+
+				let action;
+
+				for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+					action = this.mouseActions[ i ];
+
+					if ( action.mouse == mouse && action.key == key ) {
+
+						return action.operation;
+
+					}
+
+				}
+
+				if ( key != null ) {
+
+					for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+						action = this.mouseActions[ i ];
+
+						if ( action.mouse == mouse && action.key == null ) {
+
+							return action.operation;
+
+						}
+
+					}
+
+				}
+
+				return null;
+
+			};
+
+			this.getOpStateFromAction = ( mouse, key ) => {
+
+				let action;
+
+				for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+					action = this.mouseActions[ i ];
+
+					if ( action.mouse == mouse && action.key == key ) {
+
+						return action.state;
+
+					}
+
+				}
+
+				if ( key != null ) {
+
+					for ( let i = 0; i < this.mouseActions.length; i ++ ) {
+
+						action = this.mouseActions[ i ];
+
+						if ( action.mouse == mouse && action.key == null ) {
+
+							return action.state;
+
+						}
+
+					}
+
+				}
+
+				return null;
+
+			};
+
+			this.getAngle = ( p1, p2 ) => {
+
+				return Math.atan2( p2.clientY - p1.clientY, p2.clientX - p1.clientX ) * 180 / Math.PI;
+
+			};
+
+			this.updateTouchEvent = event => {
+
+				for ( let i = 0; i < this._touchCurrent.length; i ++ ) {
+
+					if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
+
+						this._touchCurrent.splice( i, 1, event );
+
+						break;
+
+					}
+
+				}
+
+			};
+
+			this.calculateAngularSpeed = ( p0, p1, t0, t1 ) => {
+
+				const s = p1 - p0;
+				const t = ( t1 - t0 ) / 1000;
+
+				if ( t == 0 ) {
+
+					return 0;
+
+				}
+
+				return s / t;
+
+			};
+
+			this.calculatePointersDistance = ( p0, p1 ) => {
+
+				return Math.sqrt( Math.pow( p1.clientX - p0.clientX, 2 ) + Math.pow( p1.clientY - p0.clientY, 2 ) );
+
+			};
+
+			this.calculateRotationAxis = ( vec1, vec2 ) => {
+
+				this._rotationMatrix.extractRotation( this._cameraMatrixState );
+
+				this._quat.setFromRotationMatrix( this._rotationMatrix );
+
+				this._rotationAxis.crossVectors( vec1, vec2 ).applyQuaternion( this._quat );
+
+				return this._rotationAxis.normalize().clone();
+
+			};
+
+			this.calculateTbRadius = camera => {
+
+				const factor = 0.67;
+				const distance = camera.position.distanceTo( this._gizmos.position );
+
+				if ( camera.type == 'PerspectiveCamera' ) {
+
+					const halfFovV = THREE.MathUtils.DEG2RAD * camera.fov * 0.5; //vertical fov/2 in radians
+
+					const halfFovH = Math.atan( camera.aspect * Math.tan( halfFovV ) ); //horizontal fov/2 in radians
+
+					return Math.tan( Math.min( halfFovV, halfFovH ) ) * distance * factor;
+
+				} else if ( camera.type == 'OrthographicCamera' ) {
+
+					return Math.min( camera.top, camera.right ) * factor;
+
+				}
+
+			};
+
+			this.focus = ( point, size, amount = 1 ) => {
+
+				const focusPoint = point.clone(); //move center of camera (along with gizmos) towards point of interest
+
+				focusPoint.sub( this._gizmos.position ).multiplyScalar( amount );
+
+				this._translationMatrix.makeTranslation( focusPoint.x, focusPoint.y, focusPoint.z );
+
+				const gizmoStateTemp = this._gizmoMatrixState.clone();
+
+				this._gizmoMatrixState.premultiply( this._translationMatrix );
+
+				this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+				const cameraStateTemp = this._cameraMatrixState.clone();
+
+				this._cameraMatrixState.premultiply( this._translationMatrix );
+
+				this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); //apply zoom
+
+
+				if ( this.enableZoom ) {
+
+					this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
+
+				}
+
+				this._gizmoMatrixState.copy( gizmoStateTemp );
+
+				this._cameraMatrixState.copy( cameraStateTemp );
+
+			};
+
+			this.drawGrid = () => {
+
+				if ( this.scene != null ) {
+
+					const color = 0x888888;
+					const multiplier = 3;
+					let size, divisions, maxLength, tick;
+
+					if ( this.camera.isOrthographicCamera ) {
+
+						const width = this.camera.right - this.camera.left;
+						const height = this.camera.bottom - this.camera.top;
+						maxLength = Math.max( width, height );
+						tick = maxLength / 20;
+						size = maxLength / this.camera.zoom * multiplier;
+						divisions = size / tick * this.camera.zoom;
+
+					} else if ( this.camera.isPerspectiveCamera ) {
+
+						const distance = this.camera.position.distanceTo( this._gizmos.position );
+						const halfFovV = THREE.MathUtils.DEG2RAD * this.camera.fov * 0.5;
+						const halfFovH = Math.atan( this.camera.aspect * Math.tan( halfFovV ) );
+						maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2;
+						tick = maxLength / 20;
+						size = maxLength * multiplier;
+						divisions = size / tick;
+
+					}
+
+					if ( this._grid == null ) {
+
+						this._grid = new THREE.GridHelper( size, divisions, color, color );
+
+						this._grid.position.copy( this._gizmos.position );
+
+						this._gridPosition.copy( this._grid.position );
+
+						this._grid.quaternion.copy( this.camera.quaternion );
+
+						this._grid.rotateX( Math.PI * 0.5 );
+
+						this.scene.add( this._grid );
+
+					}
+
+				}
+
+			};
+
+			this.dispose = () => {
+
+				if ( this._animationId != - 1 ) {
+
+					window.cancelAnimationFrame( this._animationId );
+
+				}
+
+				this.domElement.removeEventListener( 'pointerdown', this.onPointerDown );
+				this.domElement.removeEventListener( 'pointercancel', this.onPointerCancel );
+				this.domElement.removeEventListener( 'wheel', this.onWheel );
+				this.domElement.removeEventListener( 'contextmenu', this.onContextMenu );
+				window.removeEventListener( 'pointermove', this.onPointerMove );
+				window.removeEventListener( 'pointerup', this.onPointerUp );
+				window.removeEventListener( 'resize', this.onWindowResize );
+				window.addEventListener( 'keydown', this.onKeyDown );
+				this.scene.remove( this._gizmos );
+				this.disposeGrid();
+
+			};
+
+			this.disposeGrid = () => {
+
+				if ( this._grid != null && this.scene != null ) {
+
+					this.scene.remove( this._grid );
+					this._grid = null;
+
+				}
+
+			};
+
+			this.easeOutCubic = t => {
+
+				return 1 - Math.pow( 1 - t, 3 );
+
+			};
+
+			this.activateGizmos = isActive => {
+
+				const gizmoX = this._gizmos.children[ 0 ];
+				const gizmoY = this._gizmos.children[ 1 ];
+				const gizmoZ = this._gizmos.children[ 2 ];
+
+				if ( isActive ) {
+
+					gizmoX.material.setValues( {
+						opacity: 1
+					} );
+					gizmoY.material.setValues( {
+						opacity: 1
+					} );
+					gizmoZ.material.setValues( {
+						opacity: 1
+					} );
+
+				} else {
+
+					gizmoX.material.setValues( {
+						opacity: 0.6
+					} );
+					gizmoY.material.setValues( {
+						opacity: 0.6
+					} );
+					gizmoZ.material.setValues( {
+						opacity: 0.6
+					} );
+
+				}
+
+			};
+
+			this.getCursorNDC = ( cursorX, cursorY, canvas ) => {
+
+				const canvasRect = canvas.getBoundingClientRect();
+
+				this._v2_1.setX( ( cursorX - canvasRect.left ) / canvasRect.width * 2 - 1 );
+
+				this._v2_1.setY( ( canvasRect.bottom - cursorY ) / canvasRect.height * 2 - 1 );
+
+				return this._v2_1.clone();
+
+			};
+
+			this.getCursorPosition = ( cursorX, cursorY, canvas ) => {
+
+				this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
+
+				this._v2_1.x *= ( this.camera.right - this.camera.left ) * 0.5;
+				this._v2_1.y *= ( this.camera.top - this.camera.bottom ) * 0.5;
+				return this._v2_1.clone();
+
+			};
+
+			this.setCamera = camera => {
+
+				camera.lookAt( this._tbCenter );
+				camera.updateMatrix(); //setting state
+
+				if ( camera.type == 'PerspectiveCamera' ) {
+
+					this._fov0 = camera.fov;
+					this._fovState = camera.fov;
+
+				}
+
+				this._cameraMatrixState0.copy( camera.matrix );
+
+				this._cameraMatrixState.copy( this._cameraMatrixState0 );
+
+				this._cameraProjectionState.copy( camera.projectionMatrix );
+
+				this._zoom0 = camera.zoom;
+				this._zoomState = this._zoom0;
+				this._initialNear = camera.near;
+				this._nearPos0 = camera.position.distanceTo( this._tbCenter ) - camera.near;
+				this._nearPos = this._initialNear;
+				this._initialFar = camera.far;
+				this._farPos0 = camera.position.distanceTo( this._tbCenter ) - camera.far;
+				this._farPos = this._initialFar;
+
+				this._up0.copy( camera.up );
+
+				this._upState.copy( camera.up );
+
+				this.camera = camera;
+				this.camera.updateProjectionMatrix(); //making gizmos
+
+				this._tbRadius = this.calculateTbRadius( camera );
+				this.makeGizmos( this._tbCenter, this._tbRadius );
+
+			};
+
+			this.makeGizmos = ( tbCenter, tbRadius ) => {
+
+				const curve = new THREE.EllipseCurve( 0, 0, tbRadius, tbRadius );
+				const points = curve.getPoints( this._curvePts ); //geometry
+
+				const curveGeometry = new THREE.BufferGeometry().setFromPoints( points ); //material
+
+				const curveMaterialX = new THREE.LineBasicMaterial( {
+					color: 0xff8080,
+					fog: false,
+					transparent: true,
+					opacity: 0.6
+				} );
+				const curveMaterialY = new THREE.LineBasicMaterial( {
+					color: 0x80ff80,
+					fog: false,
+					transparent: true,
+					opacity: 0.6
+				} );
+				const curveMaterialZ = new THREE.LineBasicMaterial( {
+					color: 0x8080ff,
+					fog: false,
+					transparent: true,
+					opacity: 0.6
+				} ); //line
+
+				const gizmoX = new THREE.Line( curveGeometry, curveMaterialX );
+				const gizmoY = new THREE.Line( curveGeometry, curveMaterialY );
+				const gizmoZ = new THREE.Line( curveGeometry, curveMaterialZ );
+				const rotation = Math.PI * 0.5;
+				gizmoX.rotation.x = rotation;
+				gizmoY.rotation.y = rotation; //setting state
+
+				this._gizmoMatrixState0.identity().setPosition( tbCenter );
+
+				this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
+
+				if ( this.camera.zoom != 1 ) {
+
+					//adapt gizmos size to camera zoom
+					const size = 1 / this.camera.zoom;
+
+					this._scaleMatrix.makeScale( size, size, size );
+
+					this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z );
+
+					this._gizmoMatrixState.premultiply( this._translationMatrix ).premultiply( this._scaleMatrix );
+
+					this._translationMatrix.makeTranslation( tbCenter.x, tbCenter.y, tbCenter.z );
+
+					this._gizmoMatrixState.premultiply( this._translationMatrix );
+
+				}
+
+				this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+				this._gizmos.clear();
+
+				this._gizmos.add( gizmoX );
+
+				this._gizmos.add( gizmoY );
+
+				this._gizmos.add( gizmoZ );
+
+			};
+
+			this.onFocusAnim = ( time, point, cameraMatrix, gizmoMatrix ) => {
+
+				if ( this._timeStart == - 1 ) {
+
+					//animation start
+					this._timeStart = time;
+
+				}
+
+				if ( this._state == STATE.ANIMATION_FOCUS ) {
+
+					const deltaTime = time - this._timeStart;
+					const animTime = deltaTime / this.focusAnimationTime;
+
+					this._gizmoMatrixState.copy( gizmoMatrix );
+
+					if ( animTime >= 1 ) {
+
+						//animation end
+						this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+						this.focus( point, this.scaleFactor );
+						this._timeStart = - 1;
+						this.updateTbState( STATE.IDLE, false );
+						this.activateGizmos( false );
+						this.dispatchEvent( _changeEvent );
+
+					} else {
+
+						const amount = this.easeOutCubic( animTime );
+						const size = 1 - amount + this.scaleFactor * amount;
+
+						this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+						this.focus( point, size, amount );
+						this.dispatchEvent( _changeEvent );
+						const self = this;
+						this._animationId = window.requestAnimationFrame( function ( t ) {
+
+							self.onFocusAnim( t, point, cameraMatrix, gizmoMatrix.clone() );
+
+						} );
+
+					}
+
+				} else {
+
+					//interrupt animation
+					this._animationId = - 1;
+					this._timeStart = - 1;
+
+				}
+
+			};
+
+			this.onRotationAnim = ( time, rotationAxis, w0 ) => {
+
+				if ( this._timeStart == - 1 ) {
+
+					//animation start
+					this._anglePrev = 0;
+					this._angleCurrent = 0;
+					this._timeStart = time;
+
+				}
+
+				if ( this._state == STATE.ANIMATION_ROTATE ) {
+
+					//w = w0 + alpha * t
+					const deltaTime = ( time - this._timeStart ) / 1000;
+					const w = w0 + - this.dampingFactor * deltaTime;
+
+					if ( w > 0 ) {
+
+						//tetha = 0.5 * alpha * t^2 + w0 * t + tetha0
+						this._angleCurrent = 0.5 * - this.dampingFactor * Math.pow( deltaTime, 2 ) + w0 * deltaTime + 0;
+						this.applyTransformMatrix( this.rotate( rotationAxis, this._angleCurrent ) );
+						this.dispatchEvent( _changeEvent );
+						const self = this;
+						this._animationId = window.requestAnimationFrame( function ( t ) {
+
+							self.onRotationAnim( t, rotationAxis, w0 );
+
+						} );
+
+					} else {
+
+						this._animationId = - 1;
+						this._timeStart = - 1;
+						this.updateTbState( STATE.IDLE, false );
+						this.activateGizmos( false );
+						this.dispatchEvent( _changeEvent );
+
+					}
+
+				} else {
+
+					//interrupt animation
+					this._animationId = - 1;
+					this._timeStart = - 1;
+
+					if ( this._state != STATE.ROTATE ) {
+
+						this.activateGizmos( false );
+						this.dispatchEvent( _changeEvent );
+
+					}
+
+				}
+
+			};
+
+			this.pan = ( p0, p1, adjust = false ) => {
+
+				const movement = p0.clone().sub( p1 );
+
+				if ( this.camera.isOrthographicCamera ) {
+
+					//adjust movement amount
+					movement.multiplyScalar( 1 / this.camera.zoom );
+
+				} else if ( this.camera.isPerspectiveCamera && adjust ) {
+
+					//adjust movement amount
+					this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position
+
+
+					this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position
+
+
+					const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.camera.position.distanceTo( this._gizmos.position );
+					movement.multiplyScalar( 1 / distanceFactor );
+
+				}
+
+				this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.camera.quaternion );
+
+				this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z );
+
+				this.setTransformationMatrices( this._m4_1, this._m4_1 );
+				return _transformation;
+
+			};
+
+			this.reset = () => {
+
+				this.camera.zoom = this._zoom0;
+
+				if ( this.camera.isPerspectiveCamera ) {
+
+					this.camera.fov = this._fov0;
+
+				}
+
+				this.camera.near = this._nearPos;
+				this.camera.far = this._farPos;
+
+				this._cameraMatrixState.copy( this._cameraMatrixState0 );
+
+				this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
+
+				this.camera.up.copy( this._up0 );
+				this.camera.updateMatrix();
+				this.camera.updateProjectionMatrix();
+
+				this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
+
+				this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+				this._gizmos.updateMatrix();
+
+				this._tbRadius = this.calculateTbRadius( this.camera );
+				this.makeGizmos( this._gizmos.position, this._tbRadius );
+				this.camera.lookAt( this._gizmos.position );
+				this.updateTbState( STATE.IDLE, false );
+				this.dispatchEvent( _changeEvent );
+
+			};
+
+			this.rotate = ( axis, angle ) => {
+
+				const point = this._gizmos.position; //rotation center
+
+				this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
+
+				this._rotationMatrix.makeRotationAxis( axis, - angle ); //rotate camera
+
+
+				this._m4_1.makeTranslation( point.x, point.y, point.z );
+
+				this._m4_1.multiply( this._rotationMatrix );
+
+				this._m4_1.multiply( this._translationMatrix );
+
+				this.setTransformationMatrices( this._m4_1 );
+				return _transformation;
+
+			};
+
+			this.copyState = () => {
+
+				let state;
+
+				if ( this.camera.isOrthographicCamera ) {
+
+					state = JSON.stringify( {
+						arcballState: {
+							cameraFar: this.camera.far,
+							cameraMatrix: this.camera.matrix,
+							cameraNear: this.camera.near,
+							cameraUp: this.camera.up,
+							cameraZoom: this.camera.zoom,
+							gizmoMatrix: this._gizmos.matrix
+						}
+					} );
+
+				} else if ( this.camera.isPerspectiveCamera ) {
+
+					state = JSON.stringify( {
+						arcballState: {
+							cameraFar: this.camera.far,
+							cameraFov: this.camera.fov,
+							cameraMatrix: this.camera.matrix,
+							cameraNear: this.camera.near,
+							cameraUp: this.camera.up,
+							cameraZoom: this.camera.zoom,
+							gizmoMatrix: this._gizmos.matrix
+						}
+					} );
+
+				}
+
+				navigator.clipboard.writeText( state );
+
+			};
+
+			this.pasteState = () => {
+
+				const self = this;
+				navigator.clipboard.readText().then( function resolved( value ) {
+
+					self.setStateFromJSON( value );
+
+				} );
+
+			};
+
+			this.saveState = () => {
+
+				this._cameraMatrixState0.copy( this.camera.matrix );
+
+				this._gizmoMatrixState0.copy( this._gizmos.matrix );
+
+				this._nearPos = this.camera.near;
+				this._farPos = this.camera.far;
+				this._zoom0 = this.camera.zoom;
+
+				this._up0.copy( this.camera.up );
+
+				if ( this.camera.isPerspectiveCamera ) {
+
+					this._fov0 = this.camera.fov;
+
+				}
+
+			};
+
+			this.scale = ( size, point, scaleGizmos = true ) => {
+
+				const scalePoint = point.clone();
+				let sizeInverse = 1 / size;
+
+				if ( this.camera.isOrthographicCamera ) {
+
+					//camera zoom
+					this.camera.zoom = this._zoomState;
+					this.camera.zoom *= size; //check min and max zoom
+
+					if ( this.camera.zoom > this.maxZoom ) {
+
+						this.camera.zoom = this.maxZoom;
+						sizeInverse = this._zoomState / this.maxZoom;
+
+					} else if ( this.camera.zoom < this.minZoom ) {
+
+						this.camera.zoom = this.minZoom;
+						sizeInverse = this._zoomState / this.minZoom;
+
+					}
+
+					this.camera.updateProjectionMatrix();
+
+					this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position
+					//scale gizmos so they appear in the same spot having the same dimension
+
+
+					this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
+
+					this._translationMatrix.makeTranslation( - this._v3_1.x, - this._v3_1.y, - this._v3_1.z );
+
+					this._m4_2.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ).multiply( this._scaleMatrix );
+
+					this._m4_2.multiply( this._translationMatrix ); //move camera and gizmos to obtain pinch effect
+
+
+					scalePoint.sub( this._v3_1 );
+					const amount = scalePoint.clone().multiplyScalar( sizeInverse );
+					scalePoint.sub( amount );
+
+					this._m4_1.makeTranslation( scalePoint.x, scalePoint.y, scalePoint.z );
+
+					this._m4_2.premultiply( this._m4_1 );
+
+					this.setTransformationMatrices( this._m4_1, this._m4_2 );
+					return _transformation;
+
+				} else if ( this.camera.isPerspectiveCamera ) {
+
+					this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
+
+					this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); //move camera
+
+
+					let distance = this._v3_1.distanceTo( scalePoint );
+
+					let amount = distance - distance * sizeInverse; //check min and max distance
+
+					const newDistance = distance - amount;
+
+					if ( newDistance < this.minDistance ) {
+
+						sizeInverse = this.minDistance / distance;
+						amount = distance - distance * sizeInverse;
+
+					} else if ( newDistance > this.maxDistance ) {
+
+						sizeInverse = this.maxDistance / distance;
+						amount = distance - distance * sizeInverse;
+
+					}
+
+					let direction = scalePoint.clone().sub( this._v3_1 ).normalize().multiplyScalar( amount );
+
+					this._m4_1.makeTranslation( direction.x, direction.y, direction.z );
+
+					if ( scaleGizmos ) {
+
+						//scale gizmos so they appear in the same spot having the same dimension
+						const pos = this._v3_2;
+						distance = pos.distanceTo( scalePoint );
+						amount = distance - distance * sizeInverse;
+						direction = scalePoint.clone().sub( this._v3_2 ).normalize().multiplyScalar( amount );
+
+						this._translationMatrix.makeTranslation( pos.x, pos.y, pos.z );
+
+						this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
+
+						this._m4_2.makeTranslation( direction.x, direction.y, direction.z ).multiply( this._translationMatrix );
+
+						this._m4_2.multiply( this._scaleMatrix );
+
+						this._translationMatrix.makeTranslation( - pos.x, - pos.y, - pos.z );
+
+						this._m4_2.multiply( this._translationMatrix );
+
+						this.setTransformationMatrices( this._m4_1, this._m4_2 );
+
+					} else {
+
+						this.setTransformationMatrices( this._m4_1 );
+
+					}
+
+					return _transformation;
+
+				}
+
+			};
+
+			this.setFov = value => {
+
+				if ( this.camera.isPerspectiveCamera ) {
+
+					this.camera.fov = THREE.MathUtils.clamp( value, this.minFov, this.maxFov );
+					this.camera.updateProjectionMatrix();
+
+				}
+
+			};
+
+			this.setTarget = ( x, y, z ) => {
+
+				this._tbCenter.set( x, y, z );
+
+				this._gizmos.position.set( x, y, z ); //for correct radius calculation
+
+
+				this._tbRadius = this.calculateTbRadius( this.camera );
+				this.makeGizmos( this._tbCenter, this._tbRadius );
+				this.camera.lookAt( this._tbCenter );
+
+			};
+
+			this.zRotate = ( point, angle ) => {
+
+				this._rotationMatrix.makeRotationAxis( this._rotationAxis, angle );
+
+				this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
+
+				this._m4_1.makeTranslation( point.x, point.y, point.z );
+
+				this._m4_1.multiply( this._rotationMatrix );
+
+				this._m4_1.multiply( this._translationMatrix );
+
+				this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ).sub( point ); //vector from rotation center to gizmos position
+
+
+				this._v3_2.copy( this._v3_1 ).applyAxisAngle( this._rotationAxis, angle ); //apply rotation
+
+
+				this._v3_2.sub( this._v3_1 );
+
+				this._m4_2.makeTranslation( this._v3_2.x, this._v3_2.y, this._v3_2.z );
+
+				this.setTransformationMatrices( this._m4_1, this._m4_2 );
+				return _transformation;
+
+			};
+
+			this.unprojectOnObj = ( cursor, camera ) => {
+
+				const raycaster = new THREE.Raycaster();
+				raycaster.near = camera.near;
+				raycaster.far = camera.far;
+				raycaster.setFromCamera( cursor, camera );
+				const intersect = raycaster.intersectObjects( this.scene.children, true );
+
+				for ( let i = 0; i < intersect.length; i ++ ) {
+
+					if ( intersect[ i ].object.uuid != this._gizmos.uuid && intersect[ i ].face != null ) {
+
+						return intersect[ i ].point.clone();
+
+					}
+
+				}
+
+				return null;
+
+			};
+
+			this.unprojectOnTbSurface = ( camera, cursorX, cursorY, canvas, tbRadius ) => {
+
+				if ( camera.type == 'OrthographicCamera' ) {
+
+					this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
+
+					this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
+
+					const x2 = Math.pow( this._v2_1.x, 2 );
+					const y2 = Math.pow( this._v2_1.y, 2 );
+					const r2 = Math.pow( this._tbRadius, 2 );
+
+					if ( x2 + y2 <= r2 * 0.5 ) {
+
+						//intersection with sphere
+						this._v3_1.setZ( Math.sqrt( r2 - ( x2 + y2 ) ) );
+
+					} else {
+
+						//intersection with hyperboloid
+						this._v3_1.setZ( r2 * 0.5 / Math.sqrt( x2 + y2 ) );
+
+					}
+
+					return this._v3_1;
+
+				} else if ( camera.type == 'PerspectiveCamera' ) {
+
+					//unproject cursor on the near plane
+					this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
+
+					this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
+
+					this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
+
+					const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
+
+
+					const cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
+					const radius2 = Math.pow( tbRadius, 2 ); //	  camera
+					//		|\
+					//		| \
+					//		|  \
+					//	h	|	\
+					//		| 	 \
+					//		| 	  \
+					//	_ _ | _ _ _\ _ _  near plane
+					//			l
+
+					const h = this._v3_1.z;
+					const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
+
+					if ( l == 0 ) {
+
+						//ray aligned with camera
+						rayDir.set( this._v3_1.x, this._v3_1.y, tbRadius );
+						return rayDir;
+
+					}
+
+					const m = h / l;
+					const q = cameraGizmoDistance;
+					/*
+         * calculate intersection point between unprojected ray and trackball surface
+         *|y = m * x + q
+         *|x^2 + y^2 = r^2
+         *
+         * (m^2 + 1) * x^2 + (2 * m * q) * x + q^2 - r^2 = 0
+         */
+
+					let a = Math.pow( m, 2 ) + 1;
+					let b = 2 * m * q;
+					let c = Math.pow( q, 2 ) - radius2;
+					let delta = Math.pow( b, 2 ) - 4 * a * c;
+
+					if ( delta >= 0 ) {
+
+						//intersection with sphere
+						this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
+
+						this._v2_1.setY( m * this._v2_1.x + q );
+
+						const angle = THREE.MathUtils.RAD2DEG * this._v2_1.angle();
+
+						if ( angle >= 45 ) {
+
+							//if angle between intersection point and X' axis is >= 45°, return that point
+							//otherwise, calculate intersection point with hyperboloid
+							const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( cameraGizmoDistance - this._v2_1.y, 2 ) );
+							rayDir.multiplyScalar( rayLength );
+							rayDir.z += cameraGizmoDistance;
+							return rayDir;
+
+						}
+
+					} //intersection with hyperboloid
+
+					/*
+         *|y = m * x + q
+         *|y = (1 / x) * (r^2 / 2)
+         *
+         * m * x^2 + q * x - r^2 / 2 = 0
+         */
+
+
+					a = m;
+					b = q;
+					c = - radius2 * 0.5;
+					delta = Math.pow( b, 2 ) - 4 * a * c;
+
+					this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
+
+					this._v2_1.setY( m * this._v2_1.x + q );
+
+					const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( cameraGizmoDistance - this._v2_1.y, 2 ) );
+					rayDir.multiplyScalar( rayLength );
+					rayDir.z += cameraGizmoDistance;
+					return rayDir;
+
+				}
+
+			};
+
+			this.unprojectOnTbPlane = ( camera, cursorX, cursorY, canvas, initialDistance = false ) => {
+
+				if ( camera.type == 'OrthographicCamera' ) {
+
+					this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
+
+					this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
+
+					return this._v3_1.clone();
+
+				} else if ( camera.type == 'PerspectiveCamera' ) {
+
+					this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); //unproject cursor on the near plane
+
+
+					this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
+
+					this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
+
+					const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
+					//	  camera
+					//		|\
+					//		| \
+					//		|  \
+					//	h	|	\
+					//		| 	 \
+					//		| 	  \
+					//	_ _ | _ _ _\ _ _  near plane
+					//			l
+
+
+					const h = this._v3_1.z;
+					const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
+					let cameraGizmoDistance;
+
+					if ( initialDistance ) {
+
+						cameraGizmoDistance = this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ).distanceTo( this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ) );
+
+					} else {
+
+						cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
+
+					}
+					/*
+         * calculate intersection point between unprojected ray and the plane
+         *|y = mx + q
+         *|y = 0
+         *
+         * x = -q/m
+        */
+
+
+					if ( l == 0 ) {
+
+						//ray aligned with camera
+						rayDir.set( 0, 0, 0 );
+						return rayDir;
+
+					}
+
+					const m = h / l;
+					const q = cameraGizmoDistance;
+					const x = - q / m;
+					const rayLength = Math.sqrt( Math.pow( q, 2 ) + Math.pow( x, 2 ) );
+					rayDir.multiplyScalar( rayLength );
+					rayDir.z = 0;
+					return rayDir;
+
+				}
+
+			};
+
+			this.updateMatrixState = () => {
+
+				//update camera and gizmos state
+				this._cameraMatrixState.copy( this.camera.matrix );
+
+				this._gizmoMatrixState.copy( this._gizmos.matrix );
+
+				if ( this.camera.isOrthographicCamera ) {
+
+					this._cameraProjectionState.copy( this.camera.projectionMatrix );
+
+					this.camera.updateProjectionMatrix();
+					this._zoomState = this.camera.zoom;
+
+				} else if ( this.camera.isPerspectiveCamera ) {
+
+					this._fovState = this.camera.fov;
+
+				}
+
+			};
+
+			this.updateTbState = ( newState, updateMatrices ) => {
+
+				this._state = newState;
+
+				if ( updateMatrices ) {
+
+					this.updateMatrixState();
+
+				}
+
+			};
+
+			this.update = () => {
+
+				const EPS = 0.000001; //check min/max parameters
+
+				if ( this.camera.isOrthographicCamera ) {
+
+					//check zoom
+					if ( this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom ) {
+
+						const newZoom = THREE.MathUtils.clamp( this.camera.zoom, this.minZoom, this.maxZoom );
+						this.applyTransformMatrix( this.scale( newZoom / this.camera.zoom, this._gizmos.position, true ) );
+
+					}
+
+				} else if ( this.camera.isPerspectiveCamera ) {
+
+					//check distance
+					const distance = this.camera.position.distanceTo( this._gizmos.position );
+
+					if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) {
+
+						const newDistance = THREE.MathUtils.clamp( distance, this.minDistance, this.maxDistance );
+						this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) );
+						this.updateMatrixState();
+
+					} //check fov
+
+
+					if ( this.camera.fov < this.minFov || this.camera.fov > this.maxFov ) {
+
+						this.camera.fov = THREE.MathUtils.clamp( this.camera.fov, this.minFov, this.maxFov );
+						this.camera.updateProjectionMatrix();
+
+					}
+
+					const oldRadius = this._tbRadius;
+					this._tbRadius = this.calculateTbRadius( this.camera );
+
+					if ( oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS ) {
+
+						const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
+						const newRadius = this._tbRadius / scale;
+						const curve = new THREE.EllipseCurve( 0, 0, newRadius, newRadius );
+						const points = curve.getPoints( this._curvePts );
+						const curveGeometry = new THREE.BufferGeometry().setFromPoints( points );
+
+						for ( const gizmo in this._gizmos.children ) {
+
+							this._gizmos.children[ gizmo ].geometry = curveGeometry;
+
+						}
+
+					}
+
+				}
+
+				this.camera.lookAt( this._gizmos.position );
+
+			};
+
+			this.setStateFromJSON = json => {
+
+				const state = JSON.parse( json );
+
+				if ( state.arcballState != undefined ) {
+
+					this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements );
+
+					this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
+
+					this.camera.up.copy( state.arcballState.cameraUp );
+					this.camera.near = state.arcballState.cameraNear;
+					this.camera.far = state.arcballState.cameraFar;
+					this.camera.zoom = state.arcballState.cameraZoom;
+
+					if ( this.camera.isPerspectiveCamera ) {
+
+						this.camera.fov = state.arcballState.cameraFov;
+
+					}
+
+					this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements );
+
+					this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+					this.camera.updateMatrix();
+					this.camera.updateProjectionMatrix();
+
+					this._gizmos.updateMatrix();
+
+					this._tbRadius = this.calculateTbRadius( this.camera );
+					const gizmoTmp = new THREE.Matrix4().copy( this._gizmoMatrixState0 );
+					this.makeGizmos( this._gizmos.position, this._tbRadius );
+
+					this._gizmoMatrixState0.copy( gizmoTmp );
+
+					this.camera.lookAt( this._gizmos.position );
+					this.updateTbState( STATE.IDLE, false );
+					this.dispatchEvent( _changeEvent );
+
+				}
+
+			};
+
+			this.camera = null;
+			this.domElement = domElement;
+			this.scene = scene;
+			this.mouseActions = [];
+			this._mouseOp = null; //global vectors and matrices that are used in some operations to avoid creating new objects every time (e.g. every time cursor moves)
+
+			this._v2_1 = new THREE.Vector2();
+			this._v3_1 = new THREE.Vector3();
+			this._v3_2 = new THREE.Vector3();
+			this._m4_1 = new THREE.Matrix4();
+			this._m4_2 = new THREE.Matrix4();
+			this._quat = new THREE.Quaternion(); //transformation matrices
+
+			this._translationMatrix = new THREE.Matrix4(); //matrix for translation operation
+
+			this._rotationMatrix = new THREE.Matrix4(); //matrix for rotation operation
+
+			this._scaleMatrix = new THREE.Matrix4(); //matrix for scaling operation
+
+			this._rotationAxis = new THREE.Vector3(); //axis for rotate operation
+			//camera state
+
+			this._cameraMatrixState = new THREE.Matrix4();
+			this._cameraProjectionState = new THREE.Matrix4();
+			this._fovState = 1;
+			this._upState = new THREE.Vector3();
+			this._zoomState = 1;
+			this._nearPos = 0;
+			this._farPos = 0;
+			this._gizmoMatrixState = new THREE.Matrix4(); //initial values
+
+			this._up0 = new THREE.Vector3();
+			this._zoom0 = 1;
+			this._fov0 = 0;
+			this._initialNear = 0;
+			this._nearPos0 = 0;
+			this._initialFar = 0;
+			this._farPos0 = 0;
+			this._cameraMatrixState0 = new THREE.Matrix4();
+			this._gizmoMatrixState0 = new THREE.Matrix4(); //pointers array
+
+			this._button = - 1;
+			this._touchStart = [];
+			this._touchCurrent = [];
+			this._input = INPUT.NONE; //two fingers touch interaction
+
+			this._switchSensibility = 32; //minimum movement to be performed to fire single pan start after the second finger has been released
+
+			this._startFingerDistance = 0; //distance between two fingers
+
+			this._currentFingerDistance = 0;
+			this._startFingerRotation = 0; //amount of rotation performed with two fingers
+
+			this._currentFingerRotation = 0; //double tap
+
+			this._devPxRatio = 0;
+			this._downValid = true;
+			this._nclicks = 0;
+			this._downEvents = [];
+			this._downStart = 0; //pointerDown time
+
+			this._clickStart = 0; //first click time
+
+			this._maxDownTime = 250;
+			this._maxInterval = 300;
+			this._posThreshold = 24;
+			this._movementThreshold = 24; //cursor positions
+
+			this._currentCursorPosition = new THREE.Vector3();
+			this._startCursorPosition = new THREE.Vector3(); //grid
+
+			this._grid = null; //grid to be visualized during pan operation
+
+			this._gridPosition = new THREE.Vector3(); //gizmos
+
+			this._gizmos = new THREE.Group();
+			this._curvePts = 128; //animations
+
+			this._timeStart = - 1; //initial time
+
+			this._animationId = - 1; //focus animation
+
+			this.focusAnimationTime = 500; //duration of focus animation in ms
+			//rotate animation
+
+			this._timePrev = 0; //time at which previous rotate operation has been detected
+
+			this._timeCurrent = 0; //time at which current rotate operation has been detected
+
+			this._anglePrev = 0; //angle of previous rotation
+
+			this._angleCurrent = 0; //angle of current rotation
+
+			this._cursorPosPrev = new THREE.Vector3(); //cursor position when previous rotate operation has been detected
+
+			this._cursorPosCurr = new THREE.Vector3(); //cursor position when current rotate operation has been detected
+
+			this._wPrev = 0; //angular velocity of the previous rotate operation
+
+			this._wCurr = 0; //angular velocity of the current rotate operation
+			//parameters
+
+			this.adjustNearFar = false;
+			this.scaleFactor = 1.1; //zoom/distance multiplier
+
+			this.dampingFactor = 25;
+			this.wMax = 20; //maximum angular velocity allowed
+
+			this.enableAnimations = true; //if animations should be performed
+
+			this.enableGrid = false; //if grid should be showed during pan operation
+
+			this.cursorZoom = false; //if wheel zoom should be cursor centered
+
+			this.minFov = 5;
+			this.maxFov = 90;
+			this.enabled = true;
+			this.enablePan = true;
+			this.enableRotate = true;
+			this.enableZoom = true;
+			this.enableGizmos = true;
+			this.minDistance = 0;
+			this.maxDistance = Infinity;
+			this.minZoom = 0;
+			this.maxZoom = Infinity; //trackball parameters
+
+			this._tbCenter = new THREE.Vector3( 0, 0, 0 );
+			this._tbRadius = 1; //FSA
+
+			this._state = STATE.IDLE;
+			this.setCamera( _camera );
+
+			if ( this.scene != null ) {
+
+				this.scene.add( this._gizmos );
+
+			}
+
+			this.domElement.style.touchAction = 'none';
+			this._devPxRatio = window.devicePixelRatio;
+			this.initializeMouseActions();
+			this.domElement.addEventListener( 'contextmenu', this.onContextMenu );
+			this.domElement.addEventListener( 'wheel', this.onWheel );
+			this.domElement.addEventListener( 'pointerdown', this.onPointerDown );
+			this.domElement.addEventListener( 'pointercancel', this.onPointerCancel );
+			window.addEventListener( 'keydown', this.onKeyDown );
+			window.addEventListener( 'resize', this.onWindowResize );
+
+		} //listeners
+
+
+		/**
+   * Apply a transformation matrix, to the camera and gizmos
+   * @param {Object} transformation Object containing matrices to apply to camera and gizmos
+   */
+		applyTransformMatrix( transformation ) {
+
+			if ( transformation.camera != null ) {
+
+				this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera );
+
+				this._m4_1.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
+
+				this.camera.updateMatrix(); //update camera up vector
+
+				if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) {
+
+					this.camera.up.copy( this._upState ).applyQuaternion( this.camera.quaternion );
+
+				}
+
+			}
+
+			if ( transformation.gizmos != null ) {
+
+				this._m4_1.copy( this._gizmoMatrixState ).premultiply( transformation.gizmos );
+
+				this._m4_1.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+
+				this._gizmos.updateMatrix();
+
+			}
+
+			if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) {
+
+				this._tbRadius = this.calculateTbRadius( this.camera );
+
+				if ( this.adjustNearFar ) {
+
+					const cameraDistance = this.camera.position.distanceTo( this._gizmos.position );
+					const bb = new THREE.Box3();
+					bb.setFromObject( this._gizmos );
+					const sphere = new THREE.Sphere();
+					bb.getBoundingSphere( sphere );
+					const adjustedNearPosition = Math.max( this._nearPos0, sphere.radius + sphere.center.length() );
+					const regularNearPosition = cameraDistance - this._initialNear;
+					const minNearPos = Math.min( adjustedNearPosition, regularNearPosition );
+					this.camera.near = cameraDistance - minNearPos;
+					const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() );
+					const regularFarPosition = cameraDistance - this._initialFar;
+					const minFarPos = Math.min( adjustedFarPosition, regularFarPosition );
+					this.camera.far = cameraDistance - minFarPos;
+					this.camera.updateProjectionMatrix();
+
+				} else {
+
+					let update = false;
+
+					if ( this.camera.near != this._initialNear ) {
+
+						this.camera.near = this._initialNear;
+						update = true;
+
+					}
+
+					if ( this.camera.far != this._initialFar ) {
+
+						this.camera.far = this._initialFar;
+						update = true;
+
+					}
+
+					if ( update ) {
+
+						this.camera.updateProjectionMatrix();
+
+					}
+
+				}
+
+			}
+
+		}
+		/**
+   * Calculate the angular speed
+   * @param {Number} p0 Position at t0
+   * @param {Number} p1 Position at t1
+   * @param {Number} t0 Initial time in milliseconds
+   * @param {Number} t1 Ending time in milliseconds
+   */
+
+
+		/**
+   * Set gizmos visibility
+   * @param {Boolean} value Value of gizmos visibility
+   */
+		setGizmosVisible( value ) {
+
+			this._gizmos.visible = value;
+			this.dispatchEvent( _changeEvent );
+
+		}
+		/**
+   * Creates the rotation gizmos matching trackball center and radius
+   * @param {Vector3} tbCenter The trackball center
+   * @param {number} tbRadius The trackball radius
+   */
+
+
+		/**
+   * Set values in transformation object
+   * @param {Matrix4} camera Transformation to be applied to the camera
+   * @param {Matrix4} gizmos Transformation to be applied to gizmos
+   */
+		setTransformationMatrices( camera = null, gizmos = null ) {
+
+			if ( camera != null ) {
+
+				if ( _transformation.camera != null ) {
+
+					_transformation.camera.copy( camera );
+
+				} else {
+
+					_transformation.camera = camera.clone();
+
+				}
+
+			} else {
+
+				_transformation.camera = null;
+
+			}
+
+			if ( gizmos != null ) {
+
+				if ( _transformation.gizmos != null ) {
+
+					_transformation.gizmos.copy( gizmos );
+
+				} else {
+
+					_transformation.gizmos = gizmos.clone();
+
+				}
+
+			} else {
+
+				_transformation.gizmos = null;
+
+			}
+
+		}
+		/**
+   * Rotate camera around its direction axis passing by a given point by a given angle
+   * @param {Vector3} point The point where the rotation axis is passing trough
+   * @param {Number} angle Angle in radians
+   * @returns The computed transormation matix
+   */
+
+
+	}
+
+	THREE.ArcballControls = ArcballControls;
+
+} )();

+ 0 - 21
examples/js/controls/TransformControls.js

@@ -217,27 +217,6 @@
 
 
 				if ( planeIntersect ) {
 				if ( planeIntersect ) {
 
 
-					let space = this.space;
-
-					if ( this.mode === 'scale' ) {
-
-						space = 'local';
-
-					} else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) {
-
-						space = 'world';
-
-					}
-
-					if ( space === 'local' && this.mode === 'rotate' ) {
-
-						const snap = this.rotationSnap;
-						if ( this.axis === 'X' && snap ) this.object.rotation.x = Math.round( this.object.rotation.x / snap ) * snap;
-						if ( this.axis === 'Y' && snap ) this.object.rotation.y = Math.round( this.object.rotation.y / snap ) * snap;
-						if ( this.axis === 'Z' && snap ) this.object.rotation.z = Math.round( this.object.rotation.z / snap ) * snap;
-
-					}
-
 					this.object.updateMatrixWorld();
 					this.object.updateMatrixWorld();
 					this.object.parent.updateMatrixWorld();
 					this.object.parent.updateMatrixWorld();
 
 

+ 2 - 2
examples/js/csm/CSMShader.js

@@ -202,14 +202,14 @@ IncidentLight directLight;
 
 
 	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
 	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
 
 
-	irradiance += getLightProbeIrradiance( lightProbe, geometry );
+	irradiance += getLightProbeIrradiance( lightProbe, geometry.normal );
 
 
 	#if ( NUM_HEMI_LIGHTS > 0 )
 	#if ( NUM_HEMI_LIGHTS > 0 )
 
 
 		#pragma unroll_loop_start
 		#pragma unroll_loop_start
 		for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
 		for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
 
 
-			irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );
+			irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );
 
 
 		}
 		}
 		#pragma unroll_loop_end
 		#pragma unroll_loop_end

+ 1 - 1
examples/js/csm/Frustum.js

@@ -93,7 +93,7 @@
 
 
 				}
 				}
 
 
-				if ( i === breaks - 1 ) {
+				if ( i === breaks.length - 1 ) {
 
 
 					for ( let j = 0; j < 4; j ++ ) {
 					for ( let j = 0; j < 4; j ++ ) {
 
 

+ 11 - 1
examples/js/exporters/USDZExporter.js

@@ -418,7 +418,17 @@ ${array.join( '' )}
 
 
 		}
 		}
 
 
-		inputs.push( `${pad}float inputs:opacity = ${material.opacity}` );
+		if ( material.alphaMap !== null ) {
+
+			inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>` );
+			inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` );
+			samplers.push( buildTexture( material.alphaMap, 'opacity' ) );
+
+		} else {
+
+			inputs.push( `${pad}float inputs:opacity = ${material.opacity}` );
+
+		}
 
 
 		if ( material.isMeshPhysicalMaterial ) {
 		if ( material.isMeshPhysicalMaterial ) {
 
 

+ 115 - 0
examples/js/geometries/ParametricGeometry.js

@@ -0,0 +1,115 @@
+( function () {
+
+	/**
+ * Parametric Surfaces Geometry
+ * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html
+ */
+
+	class ParametricGeometry extends THREE.BufferGeometry {
+
+		constructor( func = ( u, v, target ) => target.set( u, v, Math.cos( u ) * Math.sin( v ) ), slices = 8, stacks = 8 ) {
+
+			super();
+			this.type = 'ParametricGeometry';
+			this.parameters = {
+				func: func,
+				slices: slices,
+				stacks: stacks
+			}; // buffers
+
+			const indices = [];
+			const vertices = [];
+			const normals = [];
+			const uvs = [];
+			const EPS = 0.00001;
+			const normal = new THREE.Vector3();
+			const p0 = new THREE.Vector3(),
+				p1 = new THREE.Vector3();
+			const pu = new THREE.Vector3(),
+				pv = new THREE.Vector3();
+
+			if ( func.length < 3 ) {
+
+				console.error( 'THREE.ParametricGeometry: Function must now modify a THREE.Vector3 as third parameter.' );
+
+			} // generate vertices, normals and uvs
+
+
+			const sliceCount = slices + 1;
+
+			for ( let i = 0; i <= stacks; i ++ ) {
+
+				const v = i / stacks;
+
+				for ( let j = 0; j <= slices; j ++ ) {
+
+					const u = j / slices; // vertex
+
+					func( u, v, p0 );
+					vertices.push( p0.x, p0.y, p0.z ); // normal
+					// approximate tangent vectors via finite differences
+
+					if ( u - EPS >= 0 ) {
+
+						func( u - EPS, v, p1 );
+						pu.subVectors( p0, p1 );
+
+					} else {
+
+						func( u + EPS, v, p1 );
+						pu.subVectors( p1, p0 );
+
+					}
+
+					if ( v - EPS >= 0 ) {
+
+						func( u, v - EPS, p1 );
+						pv.subVectors( p0, p1 );
+
+					} else {
+
+						func( u, v + EPS, p1 );
+						pv.subVectors( p1, p0 );
+
+					} // cross product of tangent vectors returns surface normal
+
+
+					normal.crossVectors( pu, pv ).normalize();
+					normals.push( normal.x, normal.y, normal.z ); // uv
+
+					uvs.push( u, v );
+
+				}
+
+			} // generate indices
+
+
+			for ( let i = 0; i < stacks; i ++ ) {
+
+				for ( let j = 0; j < slices; j ++ ) {
+
+					const a = i * sliceCount + j;
+					const b = i * sliceCount + j + 1;
+					const c = ( i + 1 ) * sliceCount + j + 1;
+					const d = ( i + 1 ) * sliceCount + j; // faces one and two
+
+					indices.push( a, b, d );
+					indices.push( b, c, d );
+
+				}
+
+			} // build geometry
+
+
+			this.setIndex( indices );
+			this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+			this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+			this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
+
+		}
+
+	}
+
+	THREE.ParametricGeometry = ParametricGeometry;
+
+} )();

+ 49 - 0
examples/js/geometries/TextGeometry.js

@@ -0,0 +1,49 @@
+( function () {
+
+	/**
+ * Text = 3D Text
+ *
+ * parameters = {
+ *  font: <THREE.Font>, // font
+ *
+ *  size: <float>, // size of the text
+ *  height: <float>, // thickness to extrude text
+ *  curveSegments: <int>, // number of points on the curves
+ *
+ *  bevelEnabled: <bool>, // turn on bevel
+ *  bevelThickness: <float>, // how deep into text bevel goes
+ *  bevelSize: <float>, // how far from text outline (including bevelOffset) is bevel
+ *  bevelOffset: <float> // how far from text outline does bevel start
+ * }
+ */
+
+	class TextGeometry extends THREE.ExtrudeGeometry {
+
+		constructor( text, parameters = {} ) {
+
+			const font = parameters.font;
+
+			if ( ! ( font && font.isFont ) ) {
+
+				console.error( 'THREE.TextGeometry: font parameter is not an instance of THREE.Font.' );
+				return new THREE.BufferGeometry();
+
+			}
+
+			const shapes = font.generateShapes( text, parameters.size ); // translate parameters to THREE.ExtrudeGeometry API
+
+			parameters.depth = parameters.height !== undefined ? parameters.height : 50; // defaults
+
+			if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10;
+			if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8;
+			if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false;
+			super( shapes, parameters );
+			this.type = 'TextGeometry';
+
+		}
+
+	}
+
+	THREE.TextGeometry = TextGeometry;
+
+} )();

+ 2 - 2
examples/js/interactive/InteractiveGroup.js

@@ -24,7 +24,7 @@
 				_pointer.x = event.clientX / element.clientWidth * 2 - 1;
 				_pointer.x = event.clientX / element.clientWidth * 2 - 1;
 				_pointer.y = - ( event.clientY / element.clientHeight ) * 2 + 1;
 				_pointer.y = - ( event.clientY / element.clientHeight ) * 2 + 1;
 				raycaster.setFromCamera( _pointer, camera );
 				raycaster.setFromCamera( _pointer, camera );
-				const intersects = raycaster.intersectObjects( scope.children );
+				const intersects = raycaster.intersectObjects( scope.children, false );
 
 
 				if ( intersects.length > 0 ) {
 				if ( intersects.length > 0 ) {
 
 
@@ -63,7 +63,7 @@
 				tempMatrix.identity().extractRotation( controller.matrixWorld );
 				tempMatrix.identity().extractRotation( controller.matrixWorld );
 				raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
 				raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
 				raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
 				raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
-				const intersections = raycaster.intersectObjects( scope.children );
+				const intersections = raycaster.intersectObjects( scope.children, false );
 
 
 				if ( intersections.length > 0 ) {
 				if ( intersections.length > 0 ) {
 
 

+ 183 - 0
examples/js/loaders/FontLoader.js

@@ -0,0 +1,183 @@
+( function () {
+
+	class FontLoader extends THREE.Loader {
+
+		constructor( manager ) {
+
+			super( manager );
+
+		}
+
+		load( url, onLoad, onProgress, onError ) {
+
+			const scope = this;
+			const loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setRequestHeader( this.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( text ) {
+
+				let json;
+
+				try {
+
+					json = JSON.parse( text );
+
+				} catch ( e ) {
+
+					console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );
+					json = JSON.parse( text.substring( 65, text.length - 2 ) );
+
+				}
+
+				const font = scope.parse( json );
+				if ( onLoad ) onLoad( font );
+
+			}, onProgress, onError );
+
+		}
+
+		parse( json ) {
+
+			return new Font( json );
+
+		}
+
+	} //
+
+
+	class Font {
+
+		constructor( data ) {
+
+			this.type = 'Font';
+			this.data = data;
+
+		}
+
+		generateShapes( text, size = 100 ) {
+
+			const shapes = [];
+			const paths = createPaths( text, size, this.data );
+
+			for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
+
+				Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+
+			}
+
+			return shapes;
+
+		}
+
+	}
+
+	function createPaths( text, size, data ) {
+
+		const chars = Array.from( text );
+		const scale = size / data.resolution;
+		const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;
+		const paths = [];
+		let offsetX = 0,
+			offsetY = 0;
+
+		for ( let i = 0; i < chars.length; i ++ ) {
+
+			const char = chars[ i ];
+
+			if ( char === '\n' ) {
+
+				offsetX = 0;
+				offsetY -= line_height;
+
+			} else {
+
+				const ret = createPath( char, scale, offsetX, offsetY, data );
+				offsetX += ret.offsetX;
+				paths.push( ret.path );
+
+			}
+
+		}
+
+		return paths;
+
+	}
+
+	function createPath( char, scale, offsetX, offsetY, data ) {
+
+		const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
+
+		if ( ! glyph ) {
+
+			console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
+			return;
+
+		}
+
+		const path = new THREE.ShapePath();
+		let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
+
+		if ( glyph.o ) {
+
+			const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+
+			for ( let i = 0, l = outline.length; i < l; ) {
+
+				const action = outline[ i ++ ];
+
+				switch ( action ) {
+
+					case 'm':
+						// moveTo
+						x = outline[ i ++ ] * scale + offsetX;
+						y = outline[ i ++ ] * scale + offsetY;
+						path.moveTo( x, y );
+						break;
+
+					case 'l':
+						// lineTo
+						x = outline[ i ++ ] * scale + offsetX;
+						y = outline[ i ++ ] * scale + offsetY;
+						path.lineTo( x, y );
+						break;
+
+					case 'q':
+						// quadraticCurveTo
+						cpx = outline[ i ++ ] * scale + offsetX;
+						cpy = outline[ i ++ ] * scale + offsetY;
+						cpx1 = outline[ i ++ ] * scale + offsetX;
+						cpy1 = outline[ i ++ ] * scale + offsetY;
+						path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
+						break;
+
+					case 'b':
+						// bezierCurveTo
+						cpx = outline[ i ++ ] * scale + offsetX;
+						cpy = outline[ i ++ ] * scale + offsetY;
+						cpx1 = outline[ i ++ ] * scale + offsetX;
+						cpy1 = outline[ i ++ ] * scale + offsetY;
+						cpx2 = outline[ i ++ ] * scale + offsetX;
+						cpy2 = outline[ i ++ ] * scale + offsetY;
+						path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );
+						break;
+
+				}
+
+			}
+
+		}
+
+		return {
+			offsetX: glyph.ha * scale,
+			path: path
+		};
+
+	}
+
+	Font.prototype.isFont = true;
+
+	THREE.Font = Font;
+	THREE.FontLoader = FontLoader;
+
+} )();

+ 1 - 1
examples/js/loaders/GCodeLoader.js

@@ -152,7 +152,7 @@
 
 
 					if ( delta( state.e, line.e ) > 0 ) {
 					if ( delta( state.e, line.e ) > 0 ) {
 
 
-						line.extruding = delta( state.e, line.e ) > 0;
+						state.extruding = delta( state.e, line.e ) > 0;
 
 
 						if ( currentLayer == undefined || line.z != currentLayer.z ) {
 						if ( currentLayer == undefined || line.z != currentLayer.z ) {
 
 

+ 89 - 42
examples/js/loaders/GLTFLoader.js

@@ -581,9 +581,8 @@
 
 
 				if ( extension.clearcoatNormalTexture.scale !== undefined ) {
 				if ( extension.clearcoatNormalTexture.scale !== undefined ) {
 
 
-					const scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
-
-					materialParams.clearcoatNormalScale = new THREE.Vector2( scale, - scale );
+					const scale = extension.clearcoatNormalTexture.scale;
+					materialParams.clearcoatNormalScale = new THREE.Vector2( scale, scale );
 
 
 				}
 				}
 
 
@@ -2110,7 +2109,28 @@
 		_getNodeRef( cache, index, object ) {
 		_getNodeRef( cache, index, object ) {
 
 
 			if ( cache.refs[ index ] <= 1 ) return object;
 			if ( cache.refs[ index ] <= 1 ) return object;
-			const ref = object.clone();
+			const ref = object.clone(); // Propagates mappings to the cloned object, prevents mappings on the
+			// original object from being lost.
+
+			const updateMappings = ( original, clone ) => {
+
+				const mappings = this.associations.get( original );
+
+				if ( mappings != null ) {
+
+					this.associations.set( clone, mappings );
+
+				}
+
+				for ( const [ i, child ] of original.children.entries() ) {
+
+					updateMappings( child, clone.children[ i ] );
+
+				}
+
+			};
+
+			updateMappings( object, ref );
 			ref.name += '_instance_' + cache.uses[ index ] ++;
 			ref.name += '_instance_' + cache.uses[ index ] ++;
 			return ref;
 			return ref;
 
 
@@ -2488,28 +2508,12 @@
 			const URL = self.URL || self.webkitURL;
 			const URL = self.URL || self.webkitURL;
 			let sourceURI = source.uri || '';
 			let sourceURI = source.uri || '';
 			let isObjectURL = false;
 			let isObjectURL = false;
-			let hasAlpha = true;
-			const isJPEG = sourceURI.search( /\.jpe?g($|\?)/i ) > 0 || sourceURI.search( /^data\:image\/jpeg/ ) === 0;
-			if ( source.mimeType === 'image/jpeg' || isJPEG ) hasAlpha = false;
 
 
 			if ( source.bufferView !== undefined ) {
 			if ( source.bufferView !== undefined ) {
 
 
 				// Load binary image data from bufferView, if provided.
 				// Load binary image data from bufferView, if provided.
 				sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
 				sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
 
 
-					if ( source.mimeType === 'image/png' ) {
-
-						// Inspect the PNG 'IHDR' chunk to determine whether the image could have an
-						// alpha channel. This check is conservative — the image could have an alpha
-						// channel with all values == 1, and the indexed type (colorType == 3) only
-						// sometimes contains alpha.
-						//
-						// https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header
-						const colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false );
-						hasAlpha = colorType === 6 || colorType === 4 || colorType === 3;
-
-					}
-
 					isObjectURL = true;
 					isObjectURL = true;
 					const blob = new Blob( [ bufferView ], {
 					const blob = new Blob( [ bufferView ], {
 						type: source.mimeType
 						type: source.mimeType
@@ -2557,9 +2561,7 @@
 				}
 				}
 
 
 				texture.flipY = false;
 				texture.flipY = false;
-				if ( textureDef.name ) texture.name = textureDef.name; // When there is definitely no alpha channel in the texture, set THREE.RGBFormat to save space.
-
-				if ( ! hasAlpha ) texture.format = THREE.RGBFormat;
+				if ( textureDef.name ) texture.name = textureDef.name;
 				const samplers = json.samplers || {};
 				const samplers = json.samplers || {};
 				const sampler = samplers[ textureDef.sampler ] || {};
 				const sampler = samplers[ textureDef.sampler ] || {};
 				texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;
 				texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;
@@ -2567,8 +2569,7 @@
 				texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
 				texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
 				texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
 				texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
 				parser.associations.set( texture, {
 				parser.associations.set( texture, {
-					type: 'textures',
-					index: textureIndex
+					textures: textureIndex
 				} );
 				} );
 				return texture;
 				return texture;
 
 
@@ -2638,7 +2639,7 @@
 
 
 			const geometry = mesh.geometry;
 			const geometry = mesh.geometry;
 			let material = mesh.material;
 			let material = mesh.material;
-			const useVertexTangents = geometry.attributes.tangent !== undefined;
+			const useDerivativeTangents = geometry.attributes.tangent === undefined;
 			const useVertexColors = geometry.attributes.color !== undefined;
 			const useVertexColors = geometry.attributes.color !== undefined;
 			const useFlatShading = geometry.attributes.normal === undefined;
 			const useFlatShading = geometry.attributes.normal === undefined;
 
 
@@ -2680,11 +2681,11 @@
 			} // Clone the material if it will be modified
 			} // Clone the material if it will be modified
 
 
 
 
-			if ( useVertexTangents || useVertexColors || useFlatShading ) {
+			if ( useDerivativeTangents || useVertexColors || useFlatShading ) {
 
 
 				let cacheKey = 'ClonedMaterial:' + material.uuid + ':';
 				let cacheKey = 'ClonedMaterial:' + material.uuid + ':';
 				if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
 				if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
-				if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
+				if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:';
 				if ( useVertexColors ) cacheKey += 'vertex-colors:';
 				if ( useVertexColors ) cacheKey += 'vertex-colors:';
 				if ( useFlatShading ) cacheKey += 'flat-shading:';
 				if ( useFlatShading ) cacheKey += 'flat-shading:';
 				let cachedMaterial = this.cache.get( cacheKey );
 				let cachedMaterial = this.cache.get( cacheKey );
@@ -2695,7 +2696,7 @@
 					if ( useVertexColors ) cachedMaterial.vertexColors = true;
 					if ( useVertexColors ) cachedMaterial.vertexColors = true;
 					if ( useFlatShading ) cachedMaterial.flatShading = true;
 					if ( useFlatShading ) cachedMaterial.flatShading = true;
 
 
-					if ( useVertexTangents ) {
+					if ( useDerivativeTangents ) {
 
 
 						// https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
 						// https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
 						if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1;
 						if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1;
@@ -2832,13 +2833,13 @@
 
 
 			if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
 			if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
 
 
-				pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
-
-				materialParams.normalScale = new THREE.Vector2( 1, - 1 );
+				pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );
+				materialParams.normalScale = new THREE.Vector2( 1, 1 );
 
 
 				if ( materialDef.normalTexture.scale !== undefined ) {
 				if ( materialDef.normalTexture.scale !== undefined ) {
 
 
-					materialParams.normalScale.set( materialDef.normalTexture.scale, - materialDef.normalTexture.scale );
+					const scale = materialDef.normalTexture.scale;
+					materialParams.normalScale.set( scale, scale );
 
 
 				}
 				}
 
 
@@ -2888,8 +2889,7 @@
 				if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding;
 				if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding;
 				assignExtrasToUserData( material, materialDef );
 				assignExtrasToUserData( material, materialDef );
 				parser.associations.set( material, {
 				parser.associations.set( material, {
-					type: 'materials',
-					index: materialIndex
+					materials: materialIndex
 				} );
 				} );
 				if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
 				if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
 				return material;
 				return material;
@@ -3082,6 +3082,15 @@
 
 
 				}
 				}
 
 
+				for ( let i = 0, il = meshes.length; i < il; i ++ ) {
+
+					parser.associations.set( meshes[ i ], {
+						meshes: meshIndex,
+						primitives: i
+					} );
+
+				}
+
 				if ( meshes.length === 1 ) {
 				if ( meshes.length === 1 ) {
 
 
 					return meshes[ 0 ];
 					return meshes[ 0 ];
@@ -3089,6 +3098,9 @@
 				}
 				}
 
 
 				const group = new THREE.Group();
 				const group = new THREE.Group();
+				parser.associations.set( group, {
+					meshes: meshIndex
+				} );
 
 
 				for ( let i = 0, il = meshes.length; i < il; i ++ ) {
 				for ( let i = 0, il = meshes.length; i < il; i ++ ) {
 
 
@@ -3466,10 +3478,13 @@
 
 
 				}
 				}
 
 
-				parser.associations.set( node, {
-					type: 'nodes',
-					index: nodeIndex
-				} );
+				if ( ! parser.associations.has( node ) ) {
+
+					parser.associations.set( node, {} );
+
+				}
+
+				parser.associations.get( node ).nodes = nodeIndex;
 				return node;
 				return node;
 
 
 			} );
 			} );
@@ -3499,12 +3514,44 @@
 
 
 			for ( let i = 0, il = nodeIds.length; i < il; i ++ ) {
 			for ( let i = 0, il = nodeIds.length; i < il; i ++ ) {
 
 
-				pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) );
+				pending.push( buildNodeHierarchy( nodeIds[ i ], scene, json, parser ) );
 
 
 			}
 			}
 
 
 			return Promise.all( pending ).then( function () {
 			return Promise.all( pending ).then( function () {
 
 
+				// Removes dangling associations, associations that reference a node that
+				// didn't make it into the scene.
+				const reduceAssociations = node => {
+
+					const reducedAssociations = new Map();
+
+					for ( const [ key, value ] of parser.associations ) {
+
+						if ( key instanceof THREE.Material || key instanceof THREE.Texture ) {
+
+							reducedAssociations.set( key, value );
+
+						}
+
+					}
+
+					node.traverse( node => {
+
+						const mappings = parser.associations.get( node );
+
+						if ( mappings != null ) {
+
+							reducedAssociations.set( node, mappings );
+
+						}
+
+					} );
+					return reducedAssociations;
+
+				};
+
+				parser.associations = reduceAssociations( scene );
 				return scene;
 				return scene;
 
 
 			} );
 			} );
@@ -3513,7 +3560,7 @@
 
 
 	}
 	}
 
 
-	function buildNodeHierachy( nodeId, parentObject, json, parser ) {
+	function buildNodeHierarchy( nodeId, parentObject, json, parser ) {
 
 
 		const nodeDef = json.nodes[ nodeId ];
 		const nodeDef = json.nodes[ nodeId ];
 		return parser.getDependency( 'node', nodeId ).then( function ( node ) {
 		return parser.getDependency( 'node', nodeId ).then( function ( node ) {
@@ -3587,7 +3634,7 @@
 				for ( let i = 0, il = children.length; i < il; i ++ ) {
 				for ( let i = 0, il = children.length; i < il; i ++ ) {
 
 
 					const child = children[ i ];
 					const child = children[ i ];
-					pending.push( buildNodeHierachy( child, node, json, parser ) );
+					pending.push( buildNodeHierarchy( child, node, json, parser ) );
 
 
 				}
 				}
 
 

+ 545 - 0
examples/js/loaders/KTX2Loader.js

@@ -0,0 +1,545 @@
+( function () {
+
+	/**
+ * THREE.Loader for KTX 2.0 GPU Texture containers.
+ *
+ * KTX 2.0 is a container format for various GPU texture formats. The loader
+ * supports Basis Universal GPU textures, which can be quickly transcoded to
+ * a wide variety of GPU texture compression formats. While KTX 2.0 also allows
+ * other hardware-specific formats, this loader does not yet parse them.
+ *
+ * References:
+ * - KTX: http://github.khronos.org/KTX-Specification/
+ * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
+ */
+	const KTX2TransferSRGB = 2;
+	const KTX2_ALPHA_PREMULTIPLIED = 1;
+
+	const _taskCache = new WeakMap();
+
+	class KTX2Loader extends THREE.Loader {
+
+		constructor( manager ) {
+
+			super( manager );
+			this.transcoderPath = '';
+			this.transcoderBinary = null;
+			this.transcoderPending = null;
+			this.workerPool = new THREE.WorkerPool();
+			this.workerSourceURL = '';
+			this.workerConfig = null;
+
+			if ( typeof MSC_TRANSCODER !== 'undefined' ) {
+
+				console.warn( 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' );
+
+			}
+
+		}
+
+		setTranscoderPath( path ) {
+
+			this.transcoderPath = path;
+			return this;
+
+		}
+
+		setWorkerLimit( num ) {
+
+			this.workerPool.setWorkerLimit( num );
+			return this;
+
+		}
+
+		detectSupport( renderer ) {
+
+			this.workerConfig = {
+				astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
+				etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
+				etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
+				dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
+				bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
+				pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
+			};
+			return this;
+
+		}
+
+		dispose() {
+
+			this.workerPool.dispose();
+			if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL );
+			return this;
+
+		}
+
+		init() {
+
+			if ( ! this.transcoderPending ) {
+
+				// Load transcoder wrapper.
+				const jsLoader = new THREE.FileLoader( this.manager );
+				jsLoader.setPath( this.transcoderPath );
+				jsLoader.setWithCredentials( this.withCredentials );
+				const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); // Load transcoder WASM binary.
+
+				const binaryLoader = new THREE.FileLoader( this.manager );
+				binaryLoader.setPath( this.transcoderPath );
+				binaryLoader.setResponseType( 'arraybuffer' );
+				binaryLoader.setWithCredentials( this.withCredentials );
+				const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' );
+				this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ).then( ( [ jsContent, binaryContent ] ) => {
+
+					const fn = KTX2Loader.BasisWorker.toString();
+					const body = [ '/* constants */', 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), '/* basis_transcoder.js */', jsContent, '/* worker */', fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) ].join( '\n' );
+					this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
+					this.transcoderBinary = binaryContent;
+					this.workerPool.setWorkerCreator( () => {
+
+						const worker = new Worker( this.workerSourceURL );
+						const transcoderBinary = this.transcoderBinary.slice( 0 );
+						worker.postMessage( {
+							type: 'init',
+							config: this.workerConfig,
+							transcoderBinary
+						}, [ transcoderBinary ] );
+						return worker;
+
+					} );
+
+				} );
+
+			}
+
+			return this.transcoderPending;
+
+		}
+
+		load( url, onLoad, onProgress, onError ) {
+
+			if ( this.workerConfig === null ) {
+
+				throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' );
+
+			}
+
+			const loader = new THREE.FileLoader( this.manager );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setWithCredentials( this.withCredentials );
+			const texture = new THREE.CompressedTexture();
+			loader.load( url, buffer => {
+
+				// Check for an existing task using this buffer. A transferred buffer cannot be transferred
+				// again from this thread.
+				if ( _taskCache.has( buffer ) ) {
+
+					const cachedTask = _taskCache.get( buffer );
+
+					return cachedTask.promise.then( onLoad ).catch( onError );
+
+				}
+
+				this._createTexture( [ buffer ] ).then( function ( _texture ) {
+
+					texture.copy( _texture );
+					texture.needsUpdate = true;
+					if ( onLoad ) onLoad( texture );
+
+				} ).catch( onError );
+
+			}, onProgress, onError );
+			return texture;
+
+		}
+
+		_createTextureFrom( transcodeResult ) {
+
+			const {
+				mipmaps,
+				width,
+				height,
+				format,
+				type,
+				error,
+				dfdTransferFn,
+				dfdFlags
+			} = transcodeResult;
+			if ( type === 'error' ) return Promise.reject( error );
+			const texture = new THREE.CompressedTexture( mipmaps, width, height, format, THREE.UnsignedByteType );
+			texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
+			texture.magFilter = THREE.LinearFilter;
+			texture.generateMipmaps = false;
+			texture.needsUpdate = true;
+			texture.encoding = dfdTransferFn === KTX2TransferSRGB ? THREE.sRGBEncoding : THREE.LinearEncoding;
+			texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED );
+			return texture;
+
+		}
+		/**
+   * @param {ArrayBuffer[]} buffers
+   * @param {object?} config
+   * @return {Promise<CompressedTexture>}
+   */
+
+
+		_createTexture( buffers, config = {} ) {
+
+			const taskConfig = config;
+			const texturePending = this.init().then( () => {
+
+				return this.workerPool.postMessage( {
+					type: 'transcode',
+					buffers,
+					taskConfig: taskConfig
+				}, buffers );
+
+			} ).then( e => this._createTextureFrom( e.data ) ); // Cache the task result.
+
+			_taskCache.set( buffers[ 0 ], {
+				promise: texturePending
+			} );
+
+			return texturePending;
+
+		}
+
+		dispose() {
+
+			URL.revokeObjectURL( this.workerSourceURL );
+			this.workerPool.dispose();
+			return this;
+
+		}
+
+	}
+	/* CONSTANTS */
+
+
+	KTX2Loader.BasisFormat = {
+		ETC1S: 0,
+		UASTC_4x4: 1
+	};
+	KTX2Loader.TranscoderFormat = {
+		ETC1: 0,
+		ETC2: 1,
+		BC1: 2,
+		BC3: 3,
+		BC4: 4,
+		BC5: 5,
+		BC7_M6_OPAQUE_ONLY: 6,
+		BC7_M5: 7,
+		PVRTC1_4_RGB: 8,
+		PVRTC1_4_RGBA: 9,
+		ASTC_4x4: 10,
+		ATC_RGB: 11,
+		ATC_RGBA_INTERPOLATED_ALPHA: 12,
+		RGBA32: 13,
+		RGB565: 14,
+		BGR565: 15,
+		RGBA4444: 16
+	};
+	KTX2Loader.EngineFormat = {
+		RGBAFormat: THREE.RGBAFormat,
+		RGBA_ASTC_4x4_Format: THREE.RGBA_ASTC_4x4_Format,
+		RGBA_BPTC_Format: THREE.RGBA_BPTC_Format,
+		RGBA_ETC2_EAC_Format: THREE.RGBA_ETC2_EAC_Format,
+		RGBA_PVRTC_4BPPV1_Format: THREE.RGBA_PVRTC_4BPPV1_Format,
+		RGBA_S3TC_DXT5_Format: THREE.RGBA_S3TC_DXT5_Format,
+		RGB_ETC1_Format: THREE.RGB_ETC1_Format,
+		RGB_ETC2_Format: THREE.RGB_ETC2_Format,
+		RGB_PVRTC_4BPPV1_Format: THREE.RGB_PVRTC_4BPPV1_Format,
+		RGB_S3TC_DXT1_Format: THREE.RGB_S3TC_DXT1_Format
+	};
+	/* WEB WORKER */
+
+	KTX2Loader.BasisWorker = function () {
+
+		let config;
+		let transcoderPending;
+		let BasisModule;
+		const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
+
+		const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
+
+		const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
+
+		self.addEventListener( 'message', function ( e ) {
+
+			const message = e.data;
+
+			switch ( message.type ) {
+
+				case 'init':
+					config = message.config;
+					init( message.transcoderBinary );
+					break;
+
+				case 'transcode':
+					transcoderPending.then( () => {
+
+						try {
+
+							const {
+								width,
+								height,
+								hasAlpha,
+								mipmaps,
+								format,
+								dfdTransferFn,
+								dfdFlags
+							} = transcode( message.buffers[ 0 ] );
+							const buffers = [];
+
+							for ( let i = 0; i < mipmaps.length; ++ i ) {
+
+								buffers.push( mipmaps[ i ].data.buffer );
+
+							}
+
+							self.postMessage( {
+								type: 'transcode',
+								id: message.id,
+								width,
+								height,
+								hasAlpha,
+								mipmaps,
+								format,
+								dfdTransferFn,
+								dfdFlags
+							}, buffers );
+
+						} catch ( error ) {
+
+							console.error( error );
+							self.postMessage( {
+								type: 'error',
+								id: message.id,
+								error: error.message
+							} );
+
+						}
+
+					} );
+					break;
+
+			}
+
+		} );
+
+		function init( wasmBinary ) {
+
+			transcoderPending = new Promise( resolve => {
+
+				BasisModule = {
+					wasmBinary,
+					onRuntimeInitialized: resolve
+				};
+				BASIS( BasisModule ); // eslint-disable-line no-undef
+
+			} ).then( () => {
+
+				BasisModule.initializeBasis();
+
+				if ( BasisModule.KTX2File === undefined ) {
+
+					console.warn( 'THREE.KTX2Loader: Please update Basis Universal transcoder.' );
+
+				}
+
+			} );
+
+		}
+
+		function transcode( buffer ) {
+
+			const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) );
+
+			function cleanup() {
+
+				ktx2File.close();
+				ktx2File.delete();
+
+			}
+
+			if ( ! ktx2File.isValid() ) {
+
+				cleanup();
+				throw new Error( 'THREE.KTX2Loader:	Invalid or unsupported .ktx2 file' );
+
+			}
+
+			const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
+			const width = ktx2File.getWidth();
+			const height = ktx2File.getHeight();
+			const levels = ktx2File.getLevels();
+			const hasAlpha = ktx2File.getHasAlpha();
+			const dfdTransferFn = ktx2File.getDFDTransferFunc();
+			const dfdFlags = ktx2File.getDFDFlags();
+			const {
+				transcoderFormat,
+				engineFormat
+			} = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+
+			if ( ! width || ! height || ! levels ) {
+
+				cleanup();
+				throw new Error( 'THREE.KTX2Loader:	Invalid texture' );
+
+			}
+
+			if ( ! ktx2File.startTranscoding() ) {
+
+				cleanup();
+				throw new Error( 'THREE.KTX2Loader: .startTranscoding failed' );
+
+			}
+
+			const mipmaps = [];
+
+			for ( let mip = 0; mip < levels; mip ++ ) {
+
+				const levelInfo = ktx2File.getImageLevelInfo( mip, 0, 0 );
+				const mipWidth = levelInfo.origWidth;
+				const mipHeight = levelInfo.origHeight;
+				const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, 0, 0, transcoderFormat ) );
+				const status = ktx2File.transcodeImage( dst, mip, 0, 0, transcoderFormat, 0, - 1, - 1 );
+
+				if ( ! status ) {
+
+					cleanup();
+					throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' );
+
+				}
+
+				mipmaps.push( {
+					data: dst,
+					width: mipWidth,
+					height: mipHeight
+				} );
+
+			}
+
+			cleanup();
+			return {
+				width,
+				height,
+				hasAlpha,
+				mipmaps,
+				format: engineFormat,
+				dfdTransferFn,
+				dfdFlags
+			};
+
+		} //
+		// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
+		// device capabilities, and texture dimensions. The list below ranks the formats separately
+		// for ETC1S and UASTC.
+		//
+		// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
+		// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
+		// chooses RGBA32 only as a last resort and does not expose that option to the caller.
+
+
+		const FORMAT_OPTIONS = [ {
+			if: 'astcSupported',
+			basisFormat: [ BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
+			engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
+			priorityETC1S: Infinity,
+			priorityUASTC: 1,
+			needsPowerOfTwo: false
+		}, {
+			if: 'bptcSupported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
+			engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
+			priorityETC1S: 3,
+			priorityUASTC: 2,
+			needsPowerOfTwo: false
+		}, {
+			if: 'dxtSupported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
+			engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
+			priorityETC1S: 4,
+			priorityUASTC: 5,
+			needsPowerOfTwo: false
+		}, {
+			if: 'etc2Supported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
+			engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
+			priorityETC1S: 1,
+			priorityUASTC: 3,
+			needsPowerOfTwo: false
+		}, {
+			if: 'etc1Supported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC1 ],
+			engineFormat: [ EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format ],
+			priorityETC1S: 2,
+			priorityUASTC: 4,
+			needsPowerOfTwo: false
+		}, {
+			if: 'pvrtcSupported',
+			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
+			transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
+			engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
+			priorityETC1S: 5,
+			priorityUASTC: 6,
+			needsPowerOfTwo: true
+		} ];
+		const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
+
+			return a.priorityETC1S - b.priorityETC1S;
+
+		} );
+		const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
+
+			return a.priorityUASTC - b.priorityUASTC;
+
+		} );
+
+		function getTranscoderFormat( basisFormat, width, height, hasAlpha ) {
+
+			let transcoderFormat;
+			let engineFormat;
+			const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
+
+			for ( let i = 0; i < options.length; i ++ ) {
+
+				const opt = options[ i ];
+				if ( ! config[ opt.if ] ) continue;
+				if ( ! opt.basisFormat.includes( basisFormat ) ) continue;
+				if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
+				transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
+				engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
+				return {
+					transcoderFormat,
+					engineFormat
+				};
+
+			}
+
+			console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' );
+			transcoderFormat = TranscoderFormat.RGBA32;
+			engineFormat = EngineFormat.RGBAFormat;
+			return {
+				transcoderFormat,
+				engineFormat
+			};
+
+		}
+
+		function isPowerOfTwo( value ) {
+
+			if ( value <= 2 ) return true;
+			return ( value & value - 1 ) === 0 && value !== 0;
+
+		}
+
+	};
+
+	THREE.KTX2Loader = KTX2Loader;
+
+} )();

+ 5 - 4
examples/js/loaders/RGBELoader.js

@@ -348,10 +348,11 @@
 			const RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) {
 			const RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) {
 
 
 				const e = sourceArray[ sourceOffset + 3 ];
 				const e = sourceArray[ sourceOffset + 3 ];
-				const scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
-				destArray[ destOffset + 0 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 0 ] * scale );
-				destArray[ destOffset + 1 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 1 ] * scale );
-				destArray[ destOffset + 2 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 2 ] * scale );
+				const scale = Math.pow( 2.0, e - 128.0 ) / 255.0; // clamping to 65504, the maximum representable value in float16
+
+				destArray[ destOffset + 0 ] = THREE.DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 0 ] * scale, 65504 ) );
+				destArray[ destOffset + 1 ] = THREE.DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 1 ] * scale, 65504 ) );
+				destArray[ destOffset + 2 ] = THREE.DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 2 ] * scale, 65504 ) );
 
 
 			};
 			};
 
 

+ 4 - 5
examples/js/loaders/RGBMLoader.js

@@ -310,7 +310,7 @@
 				} else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) {
 				} else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) {
 
 
 					var gr = data[ off + ( x << 1 ) ],
 					var gr = data[ off + ( x << 1 ) ],
-						al = rs( data, off + ( x << i ) ) == tr ? 0 : 255;
+						al = rs( data, off + ( x << 1 ) ) == tr ? 0 : 255;
 					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
 					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
 
 
 				}
 				}
@@ -485,7 +485,7 @@
 
 
 				break;
 				break;
 
 
-			} //else {  log("unknown chunk type", type, len);  }
+			} //else {  console.log("unknown chunk type", type, len);  out.tabs[type]=data.slice(offset,offset+len);  }
 
 
 
 
 			offset += len;
 			offset += len;
@@ -498,7 +498,6 @@
 
 
 			var fr = out.frames[ out.frames.length - 1 ];
 			var fr = out.frames[ out.frames.length - 1 ];
 			fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height );
 			fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height );
-			foff = 0;
 
 
 		}
 		}
 
 
@@ -1111,8 +1110,8 @@
 			paeth = UPNG.decode._paeth;
 			paeth = UPNG.decode._paeth;
 
 
 		bpp = Math.ceil( bpp / 8 );
 		bpp = Math.ceil( bpp / 8 );
-		var i = 0,
-			di = 1,
+		var i,
+			di,
 			type = data[ off ],
 			type = data[ off ],
 			x = 0;
 			x = 0;
 		if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ];
 		if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ];

+ 1 - 0
examples/js/loaders/SVGLoader.js

@@ -856,6 +856,7 @@
 
 
 				addStyle( 'fill', 'fill' );
 				addStyle( 'fill', 'fill' );
 				addStyle( 'fill-opacity', 'fillOpacity', clamp );
 				addStyle( 'fill-opacity', 'fillOpacity', clamp );
+				addStyle( 'fill-rule', 'fillRule' );
 				addStyle( 'opacity', 'opacity', clamp );
 				addStyle( 'opacity', 'opacity', clamp );
 				addStyle( 'stroke', 'stroke' );
 				addStyle( 'stroke', 'stroke' );
 				addStyle( 'stroke-opacity', 'strokeOpacity', clamp );
 				addStyle( 'stroke-opacity', 'strokeOpacity', clamp );

+ 6 - 5
examples/js/postprocessing/SAOPass.js

@@ -6,15 +6,15 @@
 
 
 	class SAOPass extends THREE.Pass {
 	class SAOPass extends THREE.Pass {
 
 
-		constructor( scene, camera, depthTexture, useNormals, resolution ) {
+		constructor( scene, camera, useDepthTexture = false, useNormals = false, resolution = new THREE.Vector2( 256, 256 ) ) {
 
 
 			super();
 			super();
 			this.scene = scene;
 			this.scene = scene;
 			this.camera = camera;
 			this.camera = camera;
 			this.clear = true;
 			this.clear = true;
 			this.needsSwap = false;
 			this.needsSwap = false;
-			this.supportsDepthTextureExtension = depthTexture !== undefined ? depthTexture : false;
-			this.supportsNormalTexture = useNormals !== undefined ? useNormals : false;
+			this.supportsDepthTextureExtension = useDepthTexture;
+			this.supportsNormalTexture = useNormals;
 			this.originalClearColor = new THREE.Color();
 			this.originalClearColor = new THREE.Color();
 			this._oldClearColor = new THREE.Color();
 			this._oldClearColor = new THREE.Color();
 			this.oldClearAlpha = 1;
 			this.oldClearAlpha = 1;
@@ -30,7 +30,7 @@
 				saoBlurStdDev: 4,
 				saoBlurStdDev: 4,
 				saoBlurDepthCutoff: 0.01
 				saoBlurDepthCutoff: 0.01
 			};
 			};
-			this.resolution = resolution !== undefined ? new THREE.Vector2( resolution.x, resolution.y ) : new THREE.Vector2( 256, 256 );
+			this.resolution = new THREE.Vector2( resolution.x, resolution.y );
 			this.saoRenderTarget = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, {
 			this.saoRenderTarget = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, {
 				minFilter: THREE.LinearFilter,
 				minFilter: THREE.LinearFilter,
 				magFilter: THREE.LinearFilter,
 				magFilter: THREE.LinearFilter,
@@ -44,10 +44,11 @@
 				format: THREE.RGBAFormat
 				format: THREE.RGBAFormat
 			} );
 			} );
 			this.depthRenderTarget = this.normalRenderTarget.clone();
 			this.depthRenderTarget = this.normalRenderTarget.clone();
+			let depthTexture;
 
 
 			if ( this.supportsDepthTextureExtension ) {
 			if ( this.supportsDepthTextureExtension ) {
 
 
-				const depthTexture = new THREE.DepthTexture();
+				depthTexture = new THREE.DepthTexture();
 				depthTexture.type = THREE.UnsignedShortType;
 				depthTexture.type = THREE.UnsignedShortType;
 				this.beautyRenderTarget.depthTexture = depthTexture;
 				this.beautyRenderTarget.depthTexture = depthTexture;
 				this.beautyRenderTarget.depthBuffer = true;
 				this.beautyRenderTarget.depthBuffer = true;

+ 0 - 3
examples/js/postprocessing/SSRPass.js

@@ -9,7 +9,6 @@
 			width,
 			width,
 			height,
 			height,
 			selects,
 			selects,
-			encoding,
 			bouncing = false,
 			bouncing = false,
 			groundReflector
 			groundReflector
 		} ) {
 		} ) {
@@ -26,7 +25,6 @@
 			this.output = 0;
 			this.output = 0;
 			this.maxDistance = THREE.SSRShader.uniforms.maxDistance.value;
 			this.maxDistance = THREE.SSRShader.uniforms.maxDistance.value;
 			this.thickness = THREE.SSRShader.uniforms.thickness.value;
 			this.thickness = THREE.SSRShader.uniforms.thickness.value;
-			this.encoding = encoding;
 			this.tempColor = new THREE.Color();
 			this.tempColor = new THREE.Color();
 			this._selects = selects;
 			this._selects = selects;
 			this.selective = Array.isArray( this._selects );
 			this.selective = Array.isArray( this._selects );
@@ -307,7 +305,6 @@
 		) {
 		) {
 
 
 			// render beauty and depth
 			// render beauty and depth
-			if ( this.encoding ) this.beautyRenderTarget.texture.encoding = this.encoding;
 			renderer.setRenderTarget( this.beautyRenderTarget );
 			renderer.setRenderTarget( this.beautyRenderTarget );
 			renderer.clear();
 			renderer.clear();
 
 

+ 1 - 4
examples/js/postprocessing/SSRrPass.js

@@ -8,8 +8,7 @@
 			camera,
 			camera,
 			width,
 			width,
 			height,
 			height,
-			selects,
-			encoding
+			selects
 		} ) {
 		} ) {
 
 
 			super();
 			super();
@@ -24,7 +23,6 @@
 			this.ior = THREE.SSRrShader.uniforms.ior.value;
 			this.ior = THREE.SSRrShader.uniforms.ior.value;
 			this.maxDistance = THREE.SSRrShader.uniforms.maxDistance.value;
 			this.maxDistance = THREE.SSRrShader.uniforms.maxDistance.value;
 			this.surfDist = THREE.SSRrShader.uniforms.surfDist.value;
 			this.surfDist = THREE.SSRrShader.uniforms.surfDist.value;
-			this.encoding = encoding;
 			this.tempColor = new THREE.Color();
 			this.tempColor = new THREE.Color();
 			this.selects = selects;
 			this.selects = selects;
 			this._specular = THREE.SSRrShader.defines.SPECULAR;
 			this._specular = THREE.SSRrShader.defines.SPECULAR;
@@ -225,7 +223,6 @@
 		) {
 		) {
 
 
 			// render beauty and depth
 			// render beauty and depth
-			if ( this.encoding ) this.beautyRenderTarget.texture.encoding = this.encoding;
 			renderer.setRenderTarget( this.beautyRenderTarget );
 			renderer.setRenderTarget( this.beautyRenderTarget );
 			renderer.clear();
 			renderer.clear();
 			this.scene.children.forEach( child => {
 			this.scene.children.forEach( child => {

+ 1 - 1
examples/js/shaders/MMDToonShader.js

@@ -32,7 +32,7 @@ void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in Geometri
 
 
 	reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );
 	reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );
 
 
-	reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;
+	reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;
 
 
 }
 }
 
 

Some files were not shown because too many files changed in this diff