super-three 0.167.0 → 0.168.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/build/three.cjs +165 -44
  2. package/build/three.module.js +165 -45
  3. package/build/three.module.min.js +1 -1
  4. package/build/three.webgpu.js +23600 -21931
  5. package/build/three.webgpu.min.js +1 -1
  6. package/examples/jsm/Addons.js +3 -1
  7. package/examples/jsm/animation/AnimationClipCreator.js +1 -1
  8. package/examples/jsm/capabilities/WebGL.js +27 -21
  9. package/examples/jsm/controls/ArcballControls.js +174 -158
  10. package/examples/jsm/controls/DragControls.js +260 -132
  11. package/examples/jsm/controls/FirstPersonControls.js +175 -163
  12. package/examples/jsm/controls/FlyControls.js +194 -188
  13. package/examples/jsm/controls/OrbitControls.js +777 -786
  14. package/examples/jsm/controls/PointerLockControls.js +24 -15
  15. package/examples/jsm/controls/TrackballControls.js +453 -452
  16. package/examples/jsm/effects/AnaglyphEffect.js +6 -13
  17. package/examples/jsm/effects/ParallaxBarrierEffect.js +17 -11
  18. package/examples/jsm/effects/StereoEffect.js +6 -1
  19. package/examples/jsm/environments/RoomEnvironment.js +1 -1
  20. package/examples/jsm/exporters/GLTFExporter.js +6 -1
  21. package/examples/jsm/exporters/USDZExporter.js +29 -8
  22. package/examples/jsm/helpers/LightProbeHelper.js +42 -43
  23. package/examples/jsm/loaders/KTX2Loader.js +19 -7
  24. package/examples/jsm/loaders/MaterialXLoader.js +11 -4
  25. package/examples/jsm/loaders/PCDLoader.js +9 -9
  26. package/examples/jsm/objects/SkyMesh.js +188 -0
  27. package/examples/jsm/objects/Water2Mesh.js +159 -0
  28. package/examples/jsm/objects/WaterMesh.js +102 -0
  29. package/examples/jsm/physics/RapierPhysics.js +20 -0
  30. package/examples/jsm/postprocessing/OutlinePass.js +31 -46
  31. package/examples/jsm/postprocessing/SSAARenderPass.js +3 -1
  32. package/examples/jsm/shaders/BleachBypassShader.js +1 -2
  33. package/examples/jsm/shaders/OutputShader.js +1 -1
  34. package/examples/jsm/transpiler/TSLEncoder.js +8 -8
  35. package/examples/jsm/utils/TextureUtils.js +1 -1
  36. package/package.json +2 -1
  37. package/src/Three.WebGPU.js +6 -0
  38. package/src/Three.js +1 -0
  39. package/src/constants.js +1 -1
  40. package/src/extras/Controls.js +32 -0
  41. package/src/loaders/ObjectLoader.js +3 -3
  42. package/src/{nodes/loaders → loaders/nodes}/NodeLoader.js +4 -4
  43. package/src/{nodes/loaders → loaders/nodes}/NodeMaterialLoader.js +1 -2
  44. package/src/materials/Material.js +4 -7
  45. package/src/{nodes/materials → materials/nodes}/InstancedPointsNodeMaterial.js +33 -24
  46. package/src/{nodes/materials → materials/nodes}/Line2NodeMaterial.js +34 -37
  47. package/src/{nodes/materials → materials/nodes}/LineBasicNodeMaterial.js +3 -4
  48. package/src/{nodes/materials → materials/nodes}/LineDashedNodeMaterial.js +7 -9
  49. package/src/{nodes/materials → materials/nodes}/MeshBasicNodeMaterial.js +11 -11
  50. package/src/{nodes/materials → materials/nodes}/MeshLambertNodeMaterial.js +5 -5
  51. package/src/{nodes/materials → materials/nodes}/MeshMatcapNodeMaterial.js +8 -8
  52. package/src/materials/nodes/MeshNormalNodeMaterial.js +40 -0
  53. package/src/{nodes/materials → materials/nodes}/MeshPhongNodeMaterial.js +9 -8
  54. package/src/{nodes/materials → materials/nodes}/MeshPhysicalNodeMaterial.js +18 -17
  55. package/src/{nodes/materials → materials/nodes}/MeshSSSNodeMaterial.js +6 -6
  56. package/src/{nodes/materials → materials/nodes}/MeshStandardNodeMaterial.js +17 -11
  57. package/src/{nodes/materials → materials/nodes}/MeshToonNodeMaterial.js +4 -4
  58. package/src/{nodes/materials → materials/nodes}/NodeMaterial.js +67 -86
  59. package/src/{nodes/materials/Materials.js → materials/nodes/NodeMaterials.js} +1 -1
  60. package/src/{nodes/materials → materials/nodes}/PointsNodeMaterial.js +3 -4
  61. package/src/{nodes/materials → materials/nodes}/ShadowNodeMaterial.js +4 -4
  62. package/src/{nodes/materials → materials/nodes}/SpriteNodeMaterial.js +11 -11
  63. package/src/{nodes/materials → materials/nodes}/VolumeNodeMaterial.js +14 -16
  64. package/src/nodes/Nodes.js +103 -159
  65. package/src/nodes/TSL.js +176 -0
  66. package/src/nodes/accessors/AccessorsUtils.js +8 -8
  67. package/src/nodes/accessors/BatchNode.js +48 -20
  68. package/src/nodes/accessors/{BitangentNode.js → Bitangent.js} +9 -9
  69. package/src/nodes/accessors/BufferAttributeNode.js +5 -5
  70. package/src/nodes/accessors/BufferNode.js +4 -4
  71. package/src/nodes/accessors/{CameraNode.js → Camera.js} +10 -10
  72. package/src/nodes/accessors/ClippingNode.js +16 -14
  73. package/src/nodes/accessors/CubeTextureNode.js +5 -7
  74. package/src/nodes/accessors/InstanceNode.js +17 -13
  75. package/src/nodes/accessors/InstancedPointsMaterialNode.js +4 -4
  76. package/src/nodes/accessors/MaterialNode.js +52 -55
  77. package/src/nodes/accessors/MaterialProperties.js +3 -0
  78. package/src/nodes/accessors/MaterialReferenceNode.js +6 -4
  79. package/src/nodes/accessors/ModelNode.js +11 -11
  80. package/src/nodes/accessors/ModelViewProjectionNode.js +6 -7
  81. package/src/nodes/accessors/MorphNode.js +12 -12
  82. package/src/nodes/accessors/Normal.js +62 -0
  83. package/src/nodes/accessors/Object3DNode.js +10 -10
  84. package/src/nodes/accessors/PointUVNode.js +4 -4
  85. package/src/nodes/accessors/Position.js +10 -0
  86. package/src/nodes/accessors/ReferenceBaseNode.js +143 -0
  87. package/src/nodes/accessors/ReferenceNode.js +13 -7
  88. package/src/nodes/accessors/ReflectVector.js +10 -0
  89. package/src/nodes/accessors/RendererReferenceNode.js +6 -6
  90. package/src/nodes/accessors/SceneNode.js +5 -6
  91. package/src/nodes/accessors/SkinningNode.js +84 -21
  92. package/src/nodes/accessors/StorageBufferNode.js +4 -5
  93. package/src/nodes/accessors/StorageTextureNode.js +5 -5
  94. package/src/nodes/accessors/Tangent.js +22 -0
  95. package/src/nodes/accessors/Texture3DNode.js +11 -11
  96. package/src/nodes/accessors/{TextureBicubicNode.js → TextureBicubic.js} +3 -32
  97. package/src/nodes/accessors/TextureNode.js +16 -21
  98. package/src/nodes/accessors/TextureSizeNode.js +4 -6
  99. package/src/nodes/accessors/{UniformsNode.js → UniformArrayNode.js} +17 -8
  100. package/src/nodes/accessors/UserDataNode.js +4 -4
  101. package/src/nodes/accessors/VelocityNode.js +83 -0
  102. package/src/nodes/accessors/VertexColorNode.js +4 -4
  103. package/src/nodes/code/CodeNode.js +5 -5
  104. package/src/nodes/code/ExpressionNode.js +4 -4
  105. package/src/nodes/code/FunctionCallNode.js +5 -5
  106. package/src/nodes/code/FunctionNode.js +5 -23
  107. package/src/nodes/code/ScriptableNode.js +4 -6
  108. package/src/nodes/code/ScriptableValueNode.js +4 -6
  109. package/src/nodes/core/AssignNode.js +5 -5
  110. package/src/nodes/core/AttributeNode.js +7 -20
  111. package/src/nodes/core/BypassNode.js +5 -5
  112. package/src/nodes/core/CacheNode.js +5 -5
  113. package/src/nodes/core/ConstNode.js +2 -2
  114. package/src/nodes/core/ContextNode.js +17 -11
  115. package/src/nodes/core/IndexNode.js +12 -7
  116. package/src/nodes/core/InputNode.js +2 -2
  117. package/src/nodes/core/MRTNode.js +12 -6
  118. package/src/nodes/core/Node.js +39 -19
  119. package/src/nodes/core/NodeBuilder.js +46 -33
  120. package/src/nodes/core/NodeUtils.js +1 -0
  121. package/src/nodes/core/OutputStructNode.js +4 -4
  122. package/src/nodes/core/ParameterNode.js +4 -4
  123. package/src/nodes/core/PropertyNode.js +32 -32
  124. package/src/nodes/core/StackNode.js +26 -10
  125. package/src/nodes/core/StructTypeNode.js +2 -2
  126. package/src/nodes/core/TempNode.js +2 -2
  127. package/src/nodes/core/UniformGroupNode.js +8 -9
  128. package/src/nodes/core/UniformNode.js +4 -4
  129. package/src/nodes/core/VarNode.js +6 -6
  130. package/src/nodes/core/VaryingNode.js +5 -5
  131. package/src/nodes/display/AfterImageNode.js +9 -9
  132. package/src/nodes/display/AnaglyphPassNode.js +61 -0
  133. package/src/nodes/display/AnamorphicNode.js +11 -11
  134. package/src/nodes/display/BleachBypass.js +26 -0
  135. package/src/nodes/display/BlendMode.js +54 -0
  136. package/src/nodes/display/BloomNode.js +21 -19
  137. package/src/nodes/display/BumpMapNode.js +7 -12
  138. package/src/nodes/display/ColorAdjustment.js +46 -0
  139. package/src/nodes/display/ColorSpaceFunctions.js +38 -0
  140. package/src/nodes/display/ColorSpaceNode.js +40 -63
  141. package/src/nodes/display/DenoiseNode.js +20 -17
  142. package/src/nodes/display/DepthOfFieldNode.js +8 -6
  143. package/src/nodes/display/DotScreenNode.js +10 -22
  144. package/src/nodes/display/FXAANode.js +17 -15
  145. package/src/nodes/display/FilmNode.js +7 -6
  146. package/src/nodes/display/FrontFacingNode.js +5 -5
  147. package/src/nodes/display/GTAONode.js +19 -15
  148. package/src/nodes/display/GaussianBlurNode.js +11 -8
  149. package/src/nodes/display/Lut3DNode.js +6 -5
  150. package/src/nodes/display/MotionBlur.js +25 -0
  151. package/src/nodes/display/NormalMapNode.js +8 -10
  152. package/src/nodes/display/ParallaxBarrierPassNode.js +55 -0
  153. package/src/nodes/display/PassNode.js +84 -8
  154. package/src/nodes/display/PixelationPassNode.js +12 -7
  155. package/src/nodes/display/PosterizeNode.js +4 -6
  156. package/src/nodes/display/RGBShiftNode.js +8 -7
  157. package/src/nodes/display/RenderOutputNode.js +9 -9
  158. package/src/nodes/display/SSAAPassNode.js +284 -0
  159. package/src/nodes/display/Sepia.js +17 -0
  160. package/src/nodes/display/SobelOperatorNode.js +9 -7
  161. package/src/nodes/display/StereoCompositePassNode.js +107 -0
  162. package/src/nodes/display/StereoPassNode.js +80 -0
  163. package/src/nodes/display/ToneMappingFunctions.js +190 -0
  164. package/src/nodes/display/ToneMappingNode.js +12 -173
  165. package/src/nodes/display/TransitionNode.js +9 -8
  166. package/src/nodes/display/ViewportDepthNode.js +30 -29
  167. package/src/nodes/display/ViewportDepthTextureNode.js +6 -8
  168. package/src/nodes/display/ViewportNode.js +26 -25
  169. package/src/nodes/display/ViewportSharedTextureNode.js +6 -8
  170. package/src/nodes/display/ViewportTextureNode.js +7 -10
  171. package/src/nodes/fog/FogExp2Node.js +4 -6
  172. package/src/nodes/fog/FogNode.js +5 -7
  173. package/src/nodes/fog/FogRangeNode.js +4 -6
  174. package/src/nodes/functions/BSDF/BRDF_GGX.js +4 -4
  175. package/src/nodes/functions/BSDF/BRDF_Lambert.js +2 -2
  176. package/src/nodes/functions/BSDF/BRDF_Sheen.js +6 -6
  177. package/src/nodes/functions/BSDF/DFGApprox.js +2 -2
  178. package/src/nodes/functions/BSDF/D_GGX.js +2 -2
  179. package/src/nodes/functions/BSDF/D_GGX_Anisotropic.js +3 -3
  180. package/src/nodes/functions/BSDF/EnvironmentBRDF.js +2 -2
  181. package/src/nodes/functions/BSDF/F_Schlick.js +2 -2
  182. package/src/nodes/functions/BSDF/LTC.js +6 -6
  183. package/src/nodes/functions/BSDF/Schlick_to_F0.js +2 -2
  184. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated.js +2 -2
  185. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +2 -2
  186. package/src/nodes/functions/BasicLightingModel.js +1 -1
  187. package/src/nodes/functions/PhongLightingModel.js +5 -5
  188. package/src/nodes/functions/PhysicalLightingModel.js +32 -31
  189. package/src/nodes/functions/ShadowMaskModel.js +1 -1
  190. package/src/nodes/functions/ToonLightingModel.js +3 -3
  191. package/src/nodes/functions/material/getGeometryRoughness.js +4 -4
  192. package/src/nodes/functions/material/getRoughness.js +2 -2
  193. package/src/nodes/geometry/RangeNode.js +4 -4
  194. package/src/nodes/gpgpu/ComputeNode.js +5 -5
  195. package/src/nodes/lighting/AONode.js +2 -2
  196. package/src/nodes/lighting/AmbientLightNode.js +2 -7
  197. package/src/nodes/lighting/AnalyticLightNode.js +34 -13
  198. package/src/nodes/lighting/BasicEnvironmentNode.js +4 -3
  199. package/src/nodes/lighting/BasicLightMapNode.js +3 -3
  200. package/src/nodes/lighting/DirectionalLightNode.js +2 -7
  201. package/src/nodes/lighting/EnvironmentNode.js +20 -19
  202. package/src/nodes/lighting/HemisphereLightNode.js +3 -7
  203. package/src/nodes/lighting/IESSpotLightNode.js +3 -8
  204. package/src/nodes/lighting/IrradianceNode.js +2 -2
  205. package/src/nodes/lighting/LightNode.js +5 -5
  206. package/src/nodes/lighting/LightProbeNode.js +76 -0
  207. package/src/nodes/lighting/LightUtils.js +3 -3
  208. package/src/nodes/lighting/LightingContextNode.js +15 -17
  209. package/src/nodes/lighting/LightingNode.js +2 -2
  210. package/src/nodes/lighting/LightsNode.js +94 -76
  211. package/src/nodes/lighting/PointLightNode.js +3 -8
  212. package/src/nodes/lighting/RectAreaLightNode.js +2 -6
  213. package/src/nodes/lighting/SpotLightNode.js +3 -8
  214. package/src/nodes/materialx/MaterialXNodes.js +2 -2
  215. package/src/nodes/materialx/lib/mx_hsv.js +36 -31
  216. package/src/nodes/materialx/lib/mx_noise.js +96 -96
  217. package/src/nodes/materialx/lib/mx_transform_color.js +2 -2
  218. package/src/nodes/math/{CondNode.js → ConditionalNode.js} +18 -7
  219. package/src/nodes/math/Hash.js +13 -0
  220. package/src/nodes/math/MathNode.js +124 -124
  221. package/src/nodes/math/MathUtils.js +0 -7
  222. package/src/nodes/math/OperatorNode.js +94 -52
  223. package/src/nodes/math/TriNoise3D.js +19 -29
  224. package/src/nodes/pmrem/PMREMNode.js +58 -15
  225. package/src/nodes/pmrem/PMREMUtils.js +46 -46
  226. package/src/nodes/procedural/Checker.js +14 -0
  227. package/src/nodes/tsl/TSLBase.js +30 -0
  228. package/src/nodes/{shadernode/ShaderNode.js → tsl/TSLCore.js} +105 -97
  229. package/src/nodes/utils/ArrayElementNode.js +2 -2
  230. package/src/nodes/utils/ConvertNode.js +2 -2
  231. package/src/nodes/utils/CubeMapNode.js +157 -0
  232. package/src/nodes/utils/Discard.js +8 -0
  233. package/src/nodes/utils/EquirectUVNode.js +5 -5
  234. package/src/nodes/utils/FlipNode.js +65 -0
  235. package/src/nodes/utils/FunctionOverloadingNode.js +5 -5
  236. package/src/nodes/utils/JoinNode.js +2 -2
  237. package/src/nodes/utils/LoopNode.js +12 -6
  238. package/src/nodes/utils/MatcapUVNode.js +6 -6
  239. package/src/nodes/utils/MaxMipLevelNode.js +4 -4
  240. package/src/nodes/utils/OscNode.js +7 -7
  241. package/src/nodes/utils/Packing.js +4 -0
  242. package/src/nodes/utils/RTTNode.js +8 -8
  243. package/src/nodes/utils/ReflectorNode.js +8 -3
  244. package/src/nodes/utils/RemapNode.js +7 -7
  245. package/src/nodes/utils/RotateNode.js +4 -12
  246. package/src/nodes/utils/SetNode.js +2 -2
  247. package/src/nodes/utils/SplitNode.js +2 -2
  248. package/src/nodes/utils/SpriteSheetUVNode.js +5 -5
  249. package/src/nodes/utils/SpriteUtils.js +47 -0
  250. package/src/nodes/utils/StorageArrayElementNode.js +4 -6
  251. package/src/nodes/utils/TimerNode.js +5 -5
  252. package/src/nodes/utils/TriplanarTexturesNode.js +7 -9
  253. package/src/nodes/utils/UVUtils.js +19 -0
  254. package/src/nodes/utils/ViewportUtils.js +14 -0
  255. package/src/objects/BatchedMesh.js +2 -3
  256. package/src/renderers/WebGLRenderer.js +24 -0
  257. package/src/renderers/common/Backend.js +2 -2
  258. package/src/renderers/common/Background.js +3 -1
  259. package/src/renderers/common/BindGroup.js +3 -1
  260. package/src/renderers/common/Bindings.js +13 -15
  261. package/src/renderers/common/ClippingContext.js +14 -5
  262. package/src/renderers/common/CubeRenderTarget.js +9 -3
  263. package/src/renderers/common/PostProcessing.js +4 -1
  264. package/src/renderers/common/QuadMesh.js +2 -0
  265. package/src/renderers/common/RenderContext.js +24 -0
  266. package/src/renderers/common/RenderList.js +1 -1
  267. package/src/renderers/common/RenderObject.js +2 -2
  268. package/src/renderers/common/Renderer.js +75 -49
  269. package/src/renderers/common/SampledTexture.js +12 -3
  270. package/src/renderers/common/StandardRenderer.js +18 -0
  271. package/src/renderers/common/Textures.js +8 -12
  272. package/src/renderers/common/extras/PMREMGenerator.js +12 -13
  273. package/src/renderers/common/nodes/NodeBuilderState.js +1 -1
  274. package/src/renderers/common/nodes/NodeLibrary.js +118 -0
  275. package/src/renderers/common/nodes/NodeSampledTexture.js +2 -2
  276. package/src/renderers/common/nodes/Nodes.js +36 -9
  277. package/src/renderers/common/nodes/StandardNodeLibrary.js +105 -0
  278. package/src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl.js +2 -2
  279. package/src/renderers/webgl/WebGLProgram.js +1 -1
  280. package/src/renderers/webgl/WebGLProperties.js +7 -0
  281. package/src/renderers/webgl/WebGLTextures.js +61 -4
  282. package/src/renderers/webgl-fallback/WebGLBackend.js +88 -68
  283. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +57 -9
  284. package/src/renderers/webgl-fallback/utils/WebGLState.js +21 -0
  285. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +28 -21
  286. package/src/renderers/webgpu/WebGPUBackend.js +86 -68
  287. package/src/renderers/webgpu/WebGPURenderer.js +8 -8
  288. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +99 -63
  289. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +45 -21
  290. package/src/renderers/webgpu/utils/WebGPUConstants.js +2 -1
  291. package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +6 -15
  292. package/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js +62 -5
  293. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +36 -21
  294. package/src/renderers/webgpu/utils/WebGPUUtils.js +18 -1
  295. package/src/renderers/webxr/WebXRManager.js +32 -25
  296. package/examples/jsm/loaders/LogLuvLoader.js +0 -606
  297. package/src/nodes/accessors/NormalNode.js +0 -14
  298. package/src/nodes/accessors/PositionNode.js +0 -10
  299. package/src/nodes/accessors/ReflectVectorNode.js +0 -10
  300. package/src/nodes/accessors/TangentNode.js +0 -23
  301. package/src/nodes/core/NodeKeywords.js +0 -80
  302. package/src/nodes/display/BlendModeNode.js +0 -128
  303. package/src/nodes/display/ColorAdjustmentNode.js +0 -104
  304. package/src/nodes/materials/MeshNormalNodeMaterial.js +0 -40
  305. package/src/nodes/math/HashNode.js +0 -34
  306. package/src/nodes/procedural/CheckerNode.js +0 -42
  307. package/src/nodes/utils/DiscardNode.js +0 -28
  308. package/src/nodes/utils/PackingNode.js +0 -55
  309. package/src/nodes/utils/RotateUVNode.js +0 -35
  310. /package/src/{nodes/loaders → loaders/nodes}/NodeObjectLoader.js +0 -0
  311. /package/src/nodes/accessors/{UVNode.js → UV.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  import {
2
- EventDispatcher,
2
+ Controls,
3
3
  MOUSE,
4
4
  Quaternion,
5
5
  Spherical,
@@ -23,17 +23,30 @@ const _startEvent = { type: 'start' };
23
23
  const _endEvent = { type: 'end' };
24
24
  const _ray = new Ray();
25
25
  const _plane = new Plane();
26
- const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
26
+ const _TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
27
27
 
28
- class OrbitControls extends EventDispatcher {
28
+ const _v = new Vector3();
29
+ const _twoPI = 2 * Math.PI;
29
30
 
30
- constructor( object, domElement ) {
31
+ const _STATE = {
32
+ NONE: - 1,
33
+ ROTATE: 0,
34
+ DOLLY: 1,
35
+ PAN: 2,
36
+ TOUCH_ROTATE: 3,
37
+ TOUCH_PAN: 4,
38
+ TOUCH_DOLLY_PAN: 5,
39
+ TOUCH_DOLLY_ROTATE: 6
40
+ };
41
+ const _EPS = 0.000001;
31
42
 
32
- super();
43
+ class OrbitControls extends Controls {
33
44
 
34
- this.object = object;
35
- this.domElement = domElement;
36
- this.domElement.style.touchAction = 'none'; // disable touch scroll
45
+ constructor( object, domElement = null ) {
46
+
47
+ super( object, domElement );
48
+
49
+ this.state = _STATE.NONE;
37
50
 
38
51
  // Set to false to disable this control
39
52
  this.enabled = true;
@@ -109,1421 +122,1399 @@ class OrbitControls extends EventDispatcher {
109
122
  // the target DOM element for key events
110
123
  this._domElementKeyEvents = null;
111
124
 
112
- //
113
- // public methods
114
- //
125
+ // internals
115
126
 
116
- this.getPolarAngle = function () {
127
+ this._lastPosition = new Vector3();
128
+ this._lastQuaternion = new Quaternion();
129
+ this._lastTargetPosition = new Vector3();
117
130
 
118
- return spherical.phi;
131
+ // so camera.up is the orbit axis
132
+ this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
133
+ this._quatInverse = this._quat.clone().invert();
119
134
 
120
- };
135
+ // current position in spherical coordinates
136
+ this._spherical = new Spherical();
137
+ this._sphericalDelta = new Spherical();
121
138
 
122
- this.getAzimuthalAngle = function () {
139
+ this._scale = 1;
140
+ this._panOffset = new Vector3();
123
141
 
124
- return spherical.theta;
142
+ this._rotateStart = new Vector2();
143
+ this._rotateEnd = new Vector2();
144
+ this._rotateDelta = new Vector2();
125
145
 
126
- };
146
+ this._panStart = new Vector2();
147
+ this._panEnd = new Vector2();
148
+ this._panDelta = new Vector2();
127
149
 
128
- this.getDistance = function () {
150
+ this._dollyStart = new Vector2();
151
+ this._dollyEnd = new Vector2();
152
+ this._dollyDelta = new Vector2();
129
153
 
130
- return this.object.position.distanceTo( this.target );
154
+ this._dollyDirection = new Vector3();
155
+ this._mouse = new Vector2();
156
+ this._performCursorZoom = false;
131
157
 
132
- };
158
+ this._pointers = [];
159
+ this._pointerPositions = {};
133
160
 
134
- this.listenToKeyEvents = function ( domElement ) {
161
+ this._controlActive = false;
135
162
 
136
- domElement.addEventListener( 'keydown', onKeyDown );
137
- this._domElementKeyEvents = domElement;
163
+ // event listeners
138
164
 
139
- };
165
+ this._onPointerMove = onPointerMove.bind( this );
166
+ this._onPointerDown = onPointerDown.bind( this );
167
+ this._onPointerUp = onPointerUp.bind( this );
168
+ this._onContextMenu = onContextMenu.bind( this );
169
+ this._onMouseWheel = onMouseWheel.bind( this );
170
+ this._onKeyDown = onKeyDown.bind( this );
140
171
 
141
- this.stopListenToKeyEvents = function () {
172
+ this._onTouchStart = onTouchStart.bind( this );
173
+ this._onTouchMove = onTouchMove.bind( this );
142
174
 
143
- this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
144
- this._domElementKeyEvents = null;
175
+ this._onMouseDown = onMouseDown.bind( this );
176
+ this._onMouseMove = onMouseMove.bind( this );
145
177
 
146
- };
178
+ this._interceptControlDown = interceptControlDown.bind( this );
179
+ this._interceptControlUp = interceptControlUp.bind( this );
147
180
 
148
- this.saveState = function () {
181
+ //
149
182
 
150
- scope.target0.copy( scope.target );
151
- scope.position0.copy( scope.object.position );
152
- scope.zoom0 = scope.object.zoom;
183
+ if ( this.domElement !== null ) {
153
184
 
154
- };
185
+ this.connect();
155
186
 
156
- this.reset = function () {
187
+ }
157
188
 
158
- scope.target.copy( scope.target0 );
159
- scope.object.position.copy( scope.position0 );
160
- scope.object.zoom = scope.zoom0;
189
+ this.update();
161
190
 
162
- scope.object.updateProjectionMatrix();
163
- scope.dispatchEvent( _changeEvent );
191
+ }
164
192
 
165
- scope.update();
193
+ connect() {
166
194
 
167
- state = STATE.NONE;
195
+ this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
196
+ this.domElement.addEventListener( 'pointercancel', this._onPointerUp );
168
197
 
169
- };
198
+ this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
199
+ this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } );
170
200
 
171
- // this method is exposed, but perhaps it would be better if we can make it private...
172
- this.update = function () {
201
+ const document = this.domElement.getRootNode(); // offscreen canvas compatibility
202
+ document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } );
173
203
 
174
- const offset = new Vector3();
204
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
175
205
 
176
- // so camera.up is the orbit axis
177
- const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
178
- const quatInverse = quat.clone().invert();
206
+ }
179
207
 
180
- const lastPosition = new Vector3();
181
- const lastQuaternion = new Quaternion();
182
- const lastTargetPosition = new Vector3();
208
+ disconnect() {
183
209
 
184
- const twoPI = 2 * Math.PI;
210
+ this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
211
+ this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
212
+ this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
213
+ this.domElement.removeEventListener( 'pointercancel', this._onPointerUp );
185
214
 
186
- return function update( deltaTime = null ) {
215
+ this.domElement.removeEventListener( 'wheel', this._onMouseWheel );
216
+ this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
187
217
 
188
- const position = scope.object.position;
218
+ this.stopListenToKeyEvents();
189
219
 
190
- offset.copy( position ).sub( scope.target );
220
+ const document = this.domElement.getRootNode(); // offscreen canvas compatibility
221
+ document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } );
191
222
 
192
- // rotate offset to "y-axis-is-up" space
193
- offset.applyQuaternion( quat );
223
+ this.domElement.style.touchAction = 'auto';
194
224
 
195
- // angle from z-axis around y-axis
196
- spherical.setFromVector3( offset );
225
+ }
197
226
 
198
- if ( scope.autoRotate && state === STATE.NONE ) {
227
+ dispose() {
199
228
 
200
- rotateLeft( getAutoRotationAngle( deltaTime ) );
229
+ this.disconnect();
201
230
 
202
- }
231
+ }
203
232
 
204
- if ( scope.enableDamping ) {
233
+ getPolarAngle() {
205
234
 
206
- spherical.theta += sphericalDelta.theta * scope.dampingFactor;
207
- spherical.phi += sphericalDelta.phi * scope.dampingFactor;
235
+ return this._spherical.phi;
208
236
 
209
- } else {
237
+ }
210
238
 
211
- spherical.theta += sphericalDelta.theta;
212
- spherical.phi += sphericalDelta.phi;
239
+ getAzimuthalAngle() {
213
240
 
214
- }
241
+ return this._spherical.theta;
215
242
 
216
- // restrict theta to be between desired limits
243
+ }
217
244
 
218
- let min = scope.minAzimuthAngle;
219
- let max = scope.maxAzimuthAngle;
245
+ getDistance() {
220
246
 
221
- if ( isFinite( min ) && isFinite( max ) ) {
247
+ return this.object.position.distanceTo( this.target );
222
248
 
223
- if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
249
+ }
224
250
 
225
- if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
251
+ listenToKeyEvents( domElement ) {
226
252
 
227
- if ( min <= max ) {
253
+ domElement.addEventListener( 'keydown', this._onKeyDown );
254
+ this._domElementKeyEvents = domElement;
228
255
 
229
- spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
256
+ }
230
257
 
231
- } else {
258
+ stopListenToKeyEvents() {
232
259
 
233
- spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
234
- Math.max( min, spherical.theta ) :
235
- Math.min( max, spherical.theta );
260
+ if ( this._domElementKeyEvents !== null ) {
236
261
 
237
- }
262
+ this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown );
263
+ this._domElementKeyEvents = null;
238
264
 
239
- }
265
+ }
266
+
267
+ }
240
268
 
241
- // restrict phi to be between desired limits
242
- spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
269
+ saveState() {
243
270
 
244
- spherical.makeSafe();
271
+ this.target0.copy( this.target );
272
+ this.position0.copy( this.object.position );
273
+ this.zoom0 = this.object.zoom;
245
274
 
275
+ }
246
276
 
247
- // move target to panned location
277
+ reset() {
248
278
 
249
- if ( scope.enableDamping === true ) {
279
+ this.target.copy( this.target0 );
280
+ this.object.position.copy( this.position0 );
281
+ this.object.zoom = this.zoom0;
250
282
 
251
- scope.target.addScaledVector( panOffset, scope.dampingFactor );
283
+ this.object.updateProjectionMatrix();
284
+ this.dispatchEvent( _changeEvent );
252
285
 
253
- } else {
286
+ this.update();
254
287
 
255
- scope.target.add( panOffset );
288
+ this.state = _STATE.NONE;
256
289
 
257
- }
290
+ }
258
291
 
259
- // Limit the target distance from the cursor to create a sphere around the center of interest
260
- scope.target.sub( scope.cursor );
261
- scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius );
262
- scope.target.add( scope.cursor );
292
+ update( deltaTime = null ) {
263
293
 
264
- let zoomChanged = false;
265
- // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
266
- // we adjust zoom later in these cases
267
- if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) {
294
+ const position = this.object.position;
268
295
 
269
- spherical.radius = clampDistance( spherical.radius );
296
+ _v.copy( position ).sub( this.target );
270
297
 
271
- } else {
298
+ // rotate offset to "y-axis-is-up" space
299
+ _v.applyQuaternion( this._quat );
272
300
 
273
- const prevRadius = spherical.radius;
274
- spherical.radius = clampDistance( spherical.radius * scale );
275
- zoomChanged = prevRadius != spherical.radius;
301
+ // angle from z-axis around y-axis
302
+ this._spherical.setFromVector3( _v );
276
303
 
277
- }
304
+ if ( this.autoRotate && this.state === _STATE.NONE ) {
278
305
 
279
- offset.setFromSpherical( spherical );
306
+ this._rotateLeft( this._getAutoRotationAngle( deltaTime ) );
280
307
 
281
- // rotate offset back to "camera-up-vector-is-up" space
282
- offset.applyQuaternion( quatInverse );
308
+ }
283
309
 
284
- position.copy( scope.target ).add( offset );
310
+ if ( this.enableDamping ) {
285
311
 
286
- scope.object.lookAt( scope.target );
312
+ this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor;
313
+ this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor;
287
314
 
288
- if ( scope.enableDamping === true ) {
315
+ } else {
289
316
 
290
- sphericalDelta.theta *= ( 1 - scope.dampingFactor );
291
- sphericalDelta.phi *= ( 1 - scope.dampingFactor );
317
+ this._spherical.theta += this._sphericalDelta.theta;
318
+ this._spherical.phi += this._sphericalDelta.phi;
292
319
 
293
- panOffset.multiplyScalar( 1 - scope.dampingFactor );
320
+ }
294
321
 
295
- } else {
322
+ // restrict theta to be between desired limits
296
323
 
297
- sphericalDelta.set( 0, 0, 0 );
324
+ let min = this.minAzimuthAngle;
325
+ let max = this.maxAzimuthAngle;
298
326
 
299
- panOffset.set( 0, 0, 0 );
327
+ if ( isFinite( min ) && isFinite( max ) ) {
300
328
 
301
- }
329
+ if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI;
302
330
 
303
- // adjust camera position
304
- if ( scope.zoomToCursor && performCursorZoom ) {
331
+ if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI;
305
332
 
306
- let newRadius = null;
307
- if ( scope.object.isPerspectiveCamera ) {
333
+ if ( min <= max ) {
308
334
 
309
- // move the camera down the pointer ray
310
- // this method avoids floating point error
311
- const prevRadius = offset.length();
312
- newRadius = clampDistance( prevRadius * scale );
335
+ this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) );
313
336
 
314
- const radiusDelta = prevRadius - newRadius;
315
- scope.object.position.addScaledVector( dollyDirection, radiusDelta );
316
- scope.object.updateMatrixWorld();
337
+ } else {
317
338
 
318
- zoomChanged = !! radiusDelta;
339
+ this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ?
340
+ Math.max( min, this._spherical.theta ) :
341
+ Math.min( max, this._spherical.theta );
319
342
 
320
- } else if ( scope.object.isOrthographicCamera ) {
343
+ }
321
344
 
322
- // adjust the ortho camera position based on zoom changes
323
- const mouseBefore = new Vector3( mouse.x, mouse.y, 0 );
324
- mouseBefore.unproject( scope.object );
345
+ }
325
346
 
326
- const prevZoom = scope.object.zoom;
327
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
328
- scope.object.updateProjectionMatrix();
347
+ // restrict phi to be between desired limits
348
+ this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) );
329
349
 
330
- zoomChanged = prevZoom !== scope.object.zoom;
350
+ this._spherical.makeSafe();
331
351
 
332
- const mouseAfter = new Vector3( mouse.x, mouse.y, 0 );
333
- mouseAfter.unproject( scope.object );
334
352
 
335
- scope.object.position.sub( mouseAfter ).add( mouseBefore );
336
- scope.object.updateMatrixWorld();
353
+ // move target to panned location
337
354
 
338
- newRadius = offset.length();
355
+ if ( this.enableDamping === true ) {
339
356
 
340
- } else {
357
+ this.target.addScaledVector( this._panOffset, this.dampingFactor );
341
358
 
342
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
343
- scope.zoomToCursor = false;
359
+ } else {
344
360
 
345
- }
361
+ this.target.add( this._panOffset );
346
362
 
347
- // handle the placement of the target
348
- if ( newRadius !== null ) {
363
+ }
349
364
 
350
- if ( this.screenSpacePanning ) {
365
+ // Limit the target distance from the cursor to create a sphere around the center of interest
366
+ this.target.sub( this.cursor );
367
+ this.target.clampLength( this.minTargetRadius, this.maxTargetRadius );
368
+ this.target.add( this.cursor );
351
369
 
352
- // position the orbit target in front of the new camera position
353
- scope.target.set( 0, 0, - 1 )
354
- .transformDirection( scope.object.matrix )
355
- .multiplyScalar( newRadius )
356
- .add( scope.object.position );
370
+ let zoomChanged = false;
371
+ // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
372
+ // we adjust zoom later in these cases
373
+ if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) {
357
374
 
358
- } else {
375
+ this._spherical.radius = this._clampDistance( this._spherical.radius );
359
376
 
360
- // get the ray and translation plane to compute target
361
- _ray.origin.copy( scope.object.position );
362
- _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );
377
+ } else {
363
378
 
364
- // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
365
- // extremely large values
366
- if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) {
379
+ const prevRadius = this._spherical.radius;
380
+ this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale );
381
+ zoomChanged = prevRadius != this._spherical.radius;
367
382
 
368
- object.lookAt( scope.target );
383
+ }
369
384
 
370
- } else {
385
+ _v.setFromSpherical( this._spherical );
371
386
 
372
- _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );
373
- _ray.intersectPlane( _plane, scope.target );
387
+ // rotate offset back to "camera-up-vector-is-up" space
388
+ _v.applyQuaternion( this._quatInverse );
374
389
 
375
- }
390
+ position.copy( this.target ).add( _v );
376
391
 
377
- }
392
+ this.object.lookAt( this.target );
378
393
 
379
- }
394
+ if ( this.enableDamping === true ) {
380
395
 
381
- } else if ( scope.object.isOrthographicCamera ) {
396
+ this._sphericalDelta.theta *= ( 1 - this.dampingFactor );
397
+ this._sphericalDelta.phi *= ( 1 - this.dampingFactor );
382
398
 
383
- const prevZoom = scope.object.zoom;
384
- scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
399
+ this._panOffset.multiplyScalar( 1 - this.dampingFactor );
385
400
 
386
- if ( prevZoom !== scope.object.zoom ) {
401
+ } else {
387
402
 
388
- scope.object.updateProjectionMatrix();
389
- zoomChanged = true;
403
+ this._sphericalDelta.set( 0, 0, 0 );
390
404
 
391
- }
405
+ this._panOffset.set( 0, 0, 0 );
392
406
 
393
- }
407
+ }
394
408
 
395
- scale = 1;
396
- performCursorZoom = false;
409
+ // adjust camera position
410
+ if ( this.zoomToCursor && this._performCursorZoom ) {
397
411
 
398
- // update condition is:
399
- // min(camera displacement, camera rotation in radians)^2 > EPS
400
- // using small-angle approximation cos(x/2) = 1 - x^2 / 8
412
+ let newRadius = null;
413
+ if ( this.object.isPerspectiveCamera ) {
401
414
 
402
- if ( zoomChanged ||
403
- lastPosition.distanceToSquared( scope.object.position ) > EPS ||
404
- 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ||
405
- lastTargetPosition.distanceToSquared( scope.target ) > EPS ) {
415
+ // move the camera down the pointer ray
416
+ // this method avoids floating point error
417
+ const prevRadius = _v.length();
418
+ newRadius = this._clampDistance( prevRadius * this._scale );
406
419
 
407
- scope.dispatchEvent( _changeEvent );
420
+ const radiusDelta = prevRadius - newRadius;
421
+ this.object.position.addScaledVector( this._dollyDirection, radiusDelta );
422
+ this.object.updateMatrixWorld();
408
423
 
409
- lastPosition.copy( scope.object.position );
410
- lastQuaternion.copy( scope.object.quaternion );
411
- lastTargetPosition.copy( scope.target );
424
+ zoomChanged = !! radiusDelta;
412
425
 
413
- return true;
426
+ } else if ( this.object.isOrthographicCamera ) {
414
427
 
415
- }
428
+ // adjust the ortho camera position based on zoom changes
429
+ const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 );
430
+ mouseBefore.unproject( this.object );
416
431
 
417
- return false;
432
+ const prevZoom = this.object.zoom;
433
+ this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
434
+ this.object.updateProjectionMatrix();
418
435
 
419
- };
436
+ zoomChanged = prevZoom !== this.object.zoom;
420
437
 
421
- }();
438
+ const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 );
439
+ mouseAfter.unproject( this.object );
422
440
 
423
- this.dispose = function () {
441
+ this.object.position.sub( mouseAfter ).add( mouseBefore );
442
+ this.object.updateMatrixWorld();
424
443
 
425
- scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
444
+ newRadius = _v.length();
426
445
 
427
- scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
428
- scope.domElement.removeEventListener( 'pointercancel', onPointerUp );
429
- scope.domElement.removeEventListener( 'wheel', onMouseWheel );
446
+ } else {
430
447
 
431
- scope.domElement.removeEventListener( 'pointermove', onPointerMove );
432
- scope.domElement.removeEventListener( 'pointerup', onPointerUp );
448
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
449
+ this.zoomToCursor = false;
433
450
 
434
- const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
451
+ }
435
452
 
436
- document.removeEventListener( 'keydown', interceptControlDown, { capture: true } );
453
+ // handle the placement of the target
454
+ if ( newRadius !== null ) {
437
455
 
438
- if ( scope._domElementKeyEvents !== null ) {
456
+ if ( this.screenSpacePanning ) {
439
457
 
440
- scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
441
- scope._domElementKeyEvents = null;
458
+ // position the orbit target in front of the new camera position
459
+ this.target.set( 0, 0, - 1 )
460
+ .transformDirection( this.object.matrix )
461
+ .multiplyScalar( newRadius )
462
+ .add( this.object.position );
442
463
 
443
- }
464
+ } else {
444
465
 
445
- //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
466
+ // get the ray and translation plane to compute target
467
+ _ray.origin.copy( this.object.position );
468
+ _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );
446
469
 
447
- };
470
+ // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
471
+ // extremely large values
472
+ if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) {
448
473
 
449
- //
450
- // internals
451
- //
474
+ this.object.lookAt( this.target );
452
475
 
453
- const scope = this;
454
-
455
- const STATE = {
456
- NONE: - 1,
457
- ROTATE: 0,
458
- DOLLY: 1,
459
- PAN: 2,
460
- TOUCH_ROTATE: 3,
461
- TOUCH_PAN: 4,
462
- TOUCH_DOLLY_PAN: 5,
463
- TOUCH_DOLLY_ROTATE: 6
464
- };
476
+ } else {
465
477
 
466
- let state = STATE.NONE;
478
+ _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target );
479
+ _ray.intersectPlane( _plane, this.target );
467
480
 
468
- const EPS = 0.000001;
481
+ }
469
482
 
470
- // current position in spherical coordinates
471
- const spherical = new Spherical();
472
- const sphericalDelta = new Spherical();
483
+ }
473
484
 
474
- let scale = 1;
475
- const panOffset = new Vector3();
485
+ }
476
486
 
477
- const rotateStart = new Vector2();
478
- const rotateEnd = new Vector2();
479
- const rotateDelta = new Vector2();
487
+ } else if ( this.object.isOrthographicCamera ) {
480
488
 
481
- const panStart = new Vector2();
482
- const panEnd = new Vector2();
483
- const panDelta = new Vector2();
489
+ const prevZoom = this.object.zoom;
490
+ this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
484
491
 
485
- const dollyStart = new Vector2();
486
- const dollyEnd = new Vector2();
487
- const dollyDelta = new Vector2();
492
+ if ( prevZoom !== this.object.zoom ) {
488
493
 
489
- const dollyDirection = new Vector3();
490
- const mouse = new Vector2();
491
- let performCursorZoom = false;
494
+ this.object.updateProjectionMatrix();
495
+ zoomChanged = true;
492
496
 
493
- const pointers = [];
494
- const pointerPositions = {};
497
+ }
495
498
 
496
- let controlActive = false;
499
+ }
497
500
 
498
- function getAutoRotationAngle( deltaTime ) {
501
+ this._scale = 1;
502
+ this._performCursorZoom = false;
499
503
 
500
- if ( deltaTime !== null ) {
504
+ // update condition is:
505
+ // min(camera displacement, camera rotation in radians)^2 > EPS
506
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
501
507
 
502
- return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime;
508
+ if ( zoomChanged ||
509
+ this._lastPosition.distanceToSquared( this.object.position ) > _EPS ||
510
+ 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS ||
511
+ this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) {
503
512
 
504
- } else {
513
+ this.dispatchEvent( _changeEvent );
505
514
 
506
- return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
515
+ this._lastPosition.copy( this.object.position );
516
+ this._lastQuaternion.copy( this.object.quaternion );
517
+ this._lastTargetPosition.copy( this.target );
507
518
 
508
- }
519
+ return true;
509
520
 
510
521
  }
511
522
 
512
- function getZoomScale( delta ) {
523
+ return false;
513
524
 
514
- const normalizedDelta = Math.abs( delta * 0.01 );
515
- return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta );
516
-
517
- }
525
+ }
518
526
 
519
- function rotateLeft( angle ) {
527
+ _getAutoRotationAngle( deltaTime ) {
520
528
 
521
- sphericalDelta.theta -= angle;
529
+ if ( deltaTime !== null ) {
522
530
 
523
- }
531
+ return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime;
524
532
 
525
- function rotateUp( angle ) {
533
+ } else {
526
534
 
527
- sphericalDelta.phi -= angle;
535
+ return _twoPI / 60 / 60 * this.autoRotateSpeed;
528
536
 
529
537
  }
530
538
 
531
- const panLeft = function () {
539
+ }
532
540
 
533
- const v = new Vector3();
541
+ _getZoomScale( delta ) {
534
542
 
535
- return function panLeft( distance, objectMatrix ) {
543
+ const normalizedDelta = Math.abs( delta * 0.01 );
544
+ return Math.pow( 0.95, this.zoomSpeed * normalizedDelta );
536
545
 
537
- v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
538
- v.multiplyScalar( - distance );
546
+ }
539
547
 
540
- panOffset.add( v );
548
+ _rotateLeft( angle ) {
541
549
 
542
- };
550
+ this._sphericalDelta.theta -= angle;
543
551
 
544
- }();
552
+ }
545
553
 
546
- const panUp = function () {
554
+ _rotateUp( angle ) {
547
555
 
548
- const v = new Vector3();
556
+ this._sphericalDelta.phi -= angle;
549
557
 
550
- return function panUp( distance, objectMatrix ) {
558
+ }
551
559
 
552
- if ( scope.screenSpacePanning === true ) {
560
+ _panLeft( distance, objectMatrix ) {
553
561
 
554
- v.setFromMatrixColumn( objectMatrix, 1 );
562
+ _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
563
+ _v.multiplyScalar( - distance );
555
564
 
556
- } else {
565
+ this._panOffset.add( _v );
557
566
 
558
- v.setFromMatrixColumn( objectMatrix, 0 );
559
- v.crossVectors( scope.object.up, v );
567
+ }
560
568
 
561
- }
569
+ _panUp( distance, objectMatrix ) {
562
570
 
563
- v.multiplyScalar( distance );
571
+ if ( this.screenSpacePanning === true ) {
564
572
 
565
- panOffset.add( v );
573
+ _v.setFromMatrixColumn( objectMatrix, 1 );
566
574
 
567
- };
575
+ } else {
568
576
 
569
- }();
577
+ _v.setFromMatrixColumn( objectMatrix, 0 );
578
+ _v.crossVectors( this.object.up, _v );
570
579
 
571
- // deltaX and deltaY are in pixels; right and down are positive
572
- const pan = function () {
580
+ }
573
581
 
574
- const offset = new Vector3();
582
+ _v.multiplyScalar( distance );
575
583
 
576
- return function pan( deltaX, deltaY ) {
584
+ this._panOffset.add( _v );
577
585
 
578
- const element = scope.domElement;
586
+ }
579
587
 
580
- if ( scope.object.isPerspectiveCamera ) {
588
+ // deltaX and deltaY are in pixels; right and down are positive
589
+ _pan( deltaX, deltaY ) {
581
590
 
582
- // perspective
583
- const position = scope.object.position;
584
- offset.copy( position ).sub( scope.target );
585
- let targetDistance = offset.length();
591
+ const element = this.domElement;
586
592
 
587
- // half of the fov is center to top of screen
588
- targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
593
+ if ( this.object.isPerspectiveCamera ) {
589
594
 
590
- // we use only clientHeight here so aspect ratio does not distort speed
591
- panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
592
- panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
595
+ // perspective
596
+ const position = this.object.position;
597
+ _v.copy( position ).sub( this.target );
598
+ let targetDistance = _v.length();
593
599
 
594
- } else if ( scope.object.isOrthographicCamera ) {
600
+ // half of the fov is center to top of screen
601
+ targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 );
595
602
 
596
- // orthographic
597
- panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
598
- panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
603
+ // we use only clientHeight here so aspect ratio does not distort speed
604
+ this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix );
605
+ this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix );
599
606
 
600
- } else {
607
+ } else if ( this.object.isOrthographicCamera ) {
601
608
 
602
- // camera neither orthographic nor perspective
603
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
604
- scope.enablePan = false;
609
+ // orthographic
610
+ this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix );
611
+ this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix );
605
612
 
606
- }
613
+ } else {
607
614
 
608
- };
615
+ // camera neither orthographic nor perspective
616
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
617
+ this.enablePan = false;
609
618
 
610
- }();
619
+ }
611
620
 
612
- function dollyOut( dollyScale ) {
621
+ }
613
622
 
614
- if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
623
+ _dollyOut( dollyScale ) {
615
624
 
616
- scale /= dollyScale;
625
+ if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {
617
626
 
618
- } else {
627
+ this._scale /= dollyScale;
619
628
 
620
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
621
- scope.enableZoom = false;
629
+ } else {
622
630
 
623
- }
631
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
632
+ this.enableZoom = false;
624
633
 
625
634
  }
626
635
 
627
- function dollyIn( dollyScale ) {
636
+ }
628
637
 
629
- if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
638
+ _dollyIn( dollyScale ) {
630
639
 
631
- scale *= dollyScale;
640
+ if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {
632
641
 
633
- } else {
642
+ this._scale *= dollyScale;
634
643
 
635
- console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
636
- scope.enableZoom = false;
644
+ } else {
637
645
 
638
- }
646
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
647
+ this.enableZoom = false;
639
648
 
640
649
  }
641
650
 
642
- function updateZoomParameters( x, y ) {
651
+ }
643
652
 
644
- if ( ! scope.zoomToCursor ) {
653
+ _updateZoomParameters( x, y ) {
645
654
 
646
- return;
647
-
648
- }
655
+ if ( ! this.zoomToCursor ) {
649
656
 
650
- performCursorZoom = true;
657
+ return;
651
658
 
652
- const rect = scope.domElement.getBoundingClientRect();
653
- const dx = x - rect.left;
654
- const dy = y - rect.top;
655
- const w = rect.width;
656
- const h = rect.height;
659
+ }
657
660
 
658
- mouse.x = ( dx / w ) * 2 - 1;
659
- mouse.y = - ( dy / h ) * 2 + 1;
661
+ this._performCursorZoom = true;
660
662
 
661
- dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize();
663
+ const rect = this.domElement.getBoundingClientRect();
664
+ const dx = x - rect.left;
665
+ const dy = y - rect.top;
666
+ const w = rect.width;
667
+ const h = rect.height;
662
668
 
663
- }
669
+ this._mouse.x = ( dx / w ) * 2 - 1;
670
+ this._mouse.y = - ( dy / h ) * 2 + 1;
664
671
 
665
- function clampDistance( dist ) {
672
+ this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize();
666
673
 
667
- return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );
674
+ }
668
675
 
669
- }
676
+ _clampDistance( dist ) {
670
677
 
671
- //
672
- // event callbacks - update the object state
673
- //
674
-
675
- function handleMouseDownRotate( event ) {
678
+ return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) );
676
679
 
677
- rotateStart.set( event.clientX, event.clientY );
680
+ }
678
681
 
679
- }
682
+ //
683
+ // event callbacks - update the object state
684
+ //
680
685
 
681
- function handleMouseDownDolly( event ) {
686
+ _handleMouseDownRotate( event ) {
682
687
 
683
- updateZoomParameters( event.clientX, event.clientX );
684
- dollyStart.set( event.clientX, event.clientY );
688
+ this._rotateStart.set( event.clientX, event.clientY );
685
689
 
686
- }
690
+ }
687
691
 
688
- function handleMouseDownPan( event ) {
692
+ _handleMouseDownDolly( event ) {
689
693
 
690
- panStart.set( event.clientX, event.clientY );
694
+ this._updateZoomParameters( event.clientX, event.clientX );
695
+ this._dollyStart.set( event.clientX, event.clientY );
691
696
 
692
- }
697
+ }
693
698
 
694
- function handleMouseMoveRotate( event ) {
699
+ _handleMouseDownPan( event ) {
695
700
 
696
- rotateEnd.set( event.clientX, event.clientY );
701
+ this._panStart.set( event.clientX, event.clientY );
697
702
 
698
- rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
703
+ }
699
704
 
700
- const element = scope.domElement;
705
+ _handleMouseMoveRotate( event ) {
701
706
 
702
- rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
707
+ this._rotateEnd.set( event.clientX, event.clientY );
703
708
 
704
- rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
709
+ this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );
705
710
 
706
- rotateStart.copy( rotateEnd );
711
+ const element = this.domElement;
707
712
 
708
- scope.update();
713
+ this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height
709
714
 
710
- }
715
+ this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );
711
716
 
712
- function handleMouseMoveDolly( event ) {
717
+ this._rotateStart.copy( this._rotateEnd );
713
718
 
714
- dollyEnd.set( event.clientX, event.clientY );
719
+ this.update();
715
720
 
716
- dollyDelta.subVectors( dollyEnd, dollyStart );
721
+ }
717
722
 
718
- if ( dollyDelta.y > 0 ) {
723
+ _handleMouseMoveDolly( event ) {
719
724
 
720
- dollyOut( getZoomScale( dollyDelta.y ) );
725
+ this._dollyEnd.set( event.clientX, event.clientY );
721
726
 
722
- } else if ( dollyDelta.y < 0 ) {
727
+ this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );
723
728
 
724
- dollyIn( getZoomScale( dollyDelta.y ) );
729
+ if ( this._dollyDelta.y > 0 ) {
725
730
 
726
- }
731
+ this._dollyOut( this._getZoomScale( this._dollyDelta.y ) );
727
732
 
728
- dollyStart.copy( dollyEnd );
733
+ } else if ( this._dollyDelta.y < 0 ) {
729
734
 
730
- scope.update();
735
+ this._dollyIn( this._getZoomScale( this._dollyDelta.y ) );
731
736
 
732
737
  }
733
738
 
734
- function handleMouseMovePan( event ) {
739
+ this._dollyStart.copy( this._dollyEnd );
735
740
 
736
- panEnd.set( event.clientX, event.clientY );
741
+ this.update();
737
742
 
738
- panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
743
+ }
739
744
 
740
- pan( panDelta.x, panDelta.y );
745
+ _handleMouseMovePan( event ) {
741
746
 
742
- panStart.copy( panEnd );
747
+ this._panEnd.set( event.clientX, event.clientY );
743
748
 
744
- scope.update();
749
+ this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );
745
750
 
746
- }
751
+ this._pan( this._panDelta.x, this._panDelta.y );
747
752
 
748
- function handleMouseWheel( event ) {
753
+ this._panStart.copy( this._panEnd );
749
754
 
750
- updateZoomParameters( event.clientX, event.clientY );
755
+ this.update();
751
756
 
752
- if ( event.deltaY < 0 ) {
757
+ }
753
758
 
754
- dollyIn( getZoomScale( event.deltaY ) );
759
+ _handleMouseWheel( event ) {
755
760
 
756
- } else if ( event.deltaY > 0 ) {
761
+ this._updateZoomParameters( event.clientX, event.clientY );
757
762
 
758
- dollyOut( getZoomScale( event.deltaY ) );
763
+ if ( event.deltaY < 0 ) {
759
764
 
760
- }
765
+ this._dollyIn( this._getZoomScale( event.deltaY ) );
761
766
 
762
- scope.update();
767
+ } else if ( event.deltaY > 0 ) {
768
+
769
+ this._dollyOut( this._getZoomScale( event.deltaY ) );
763
770
 
764
771
  }
765
772
 
766
- function handleKeyDown( event ) {
773
+ this.update();
767
774
 
768
- let needsUpdate = false;
775
+ }
769
776
 
770
- switch ( event.code ) {
777
+ _handleKeyDown( event ) {
771
778
 
772
- case scope.keys.UP:
779
+ let needsUpdate = false;
773
780
 
774
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
781
+ switch ( event.code ) {
775
782
 
776
- rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
783
+ case this.keys.UP:
777
784
 
778
- } else {
785
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
779
786
 
780
- pan( 0, scope.keyPanSpeed );
787
+ this._rotateUp( _twoPI * this.rotateSpeed / this.domElement.clientHeight );
781
788
 
782
- }
789
+ } else {
783
790
 
784
- needsUpdate = true;
785
- break;
791
+ this._pan( 0, this.keyPanSpeed );
786
792
 
787
- case scope.keys.BOTTOM:
793
+ }
788
794
 
789
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
795
+ needsUpdate = true;
796
+ break;
790
797
 
791
- rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
798
+ case this.keys.BOTTOM:
792
799
 
793
- } else {
800
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
794
801
 
795
- pan( 0, - scope.keyPanSpeed );
802
+ this._rotateUp( - _twoPI * this.rotateSpeed / this.domElement.clientHeight );
796
803
 
797
- }
804
+ } else {
798
805
 
799
- needsUpdate = true;
800
- break;
806
+ this._pan( 0, - this.keyPanSpeed );
801
807
 
802
- case scope.keys.LEFT:
808
+ }
803
809
 
804
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
810
+ needsUpdate = true;
811
+ break;
805
812
 
806
- rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
813
+ case this.keys.LEFT:
807
814
 
808
- } else {
815
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
809
816
 
810
- pan( scope.keyPanSpeed, 0 );
817
+ this._rotateLeft( _twoPI * this.rotateSpeed / this.domElement.clientHeight );
811
818
 
812
- }
819
+ } else {
813
820
 
814
- needsUpdate = true;
815
- break;
821
+ this._pan( this.keyPanSpeed, 0 );
816
822
 
817
- case scope.keys.RIGHT:
823
+ }
818
824
 
819
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
825
+ needsUpdate = true;
826
+ break;
820
827
 
821
- rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
828
+ case this.keys.RIGHT:
822
829
 
823
- } else {
830
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
824
831
 
825
- pan( - scope.keyPanSpeed, 0 );
832
+ this._rotateLeft( - _twoPI * this.rotateSpeed / this.domElement.clientHeight );
826
833
 
827
- }
834
+ } else {
828
835
 
829
- needsUpdate = true;
830
- break;
836
+ this._pan( - this.keyPanSpeed, 0 );
831
837
 
832
- }
838
+ }
833
839
 
834
- if ( needsUpdate ) {
840
+ needsUpdate = true;
841
+ break;
835
842
 
836
- // prevent the browser from scrolling on cursor keys
837
- event.preventDefault();
843
+ }
838
844
 
839
- scope.update();
845
+ if ( needsUpdate ) {
840
846
 
841
- }
847
+ // prevent the browser from scrolling on cursor keys
848
+ event.preventDefault();
842
849
 
850
+ this.update();
843
851
 
844
852
  }
845
853
 
846
- function handleTouchStartRotate( event ) {
847
854
 
848
- if ( pointers.length === 1 ) {
855
+ }
856
+
857
+ _handleTouchStartRotate( event ) {
849
858
 
850
- rotateStart.set( event.pageX, event.pageY );
859
+ if ( this._pointers.length === 1 ) {
851
860
 
852
- } else {
861
+ this._rotateStart.set( event.pageX, event.pageY );
853
862
 
854
- const position = getSecondPointerPosition( event );
863
+ } else {
855
864
 
856
- const x = 0.5 * ( event.pageX + position.x );
857
- const y = 0.5 * ( event.pageY + position.y );
865
+ const position = this._getSecondPointerPosition( event );
858
866
 
859
- rotateStart.set( x, y );
867
+ const x = 0.5 * ( event.pageX + position.x );
868
+ const y = 0.5 * ( event.pageY + position.y );
860
869
 
861
- }
870
+ this._rotateStart.set( x, y );
862
871
 
863
872
  }
864
873
 
865
- function handleTouchStartPan( event ) {
874
+ }
866
875
 
867
- if ( pointers.length === 1 ) {
876
+ _handleTouchStartPan( event ) {
868
877
 
869
- panStart.set( event.pageX, event.pageY );
878
+ if ( this._pointers.length === 1 ) {
870
879
 
871
- } else {
880
+ this._panStart.set( event.pageX, event.pageY );
872
881
 
873
- const position = getSecondPointerPosition( event );
882
+ } else {
874
883
 
875
- const x = 0.5 * ( event.pageX + position.x );
876
- const y = 0.5 * ( event.pageY + position.y );
884
+ const position = this._getSecondPointerPosition( event );
877
885
 
878
- panStart.set( x, y );
886
+ const x = 0.5 * ( event.pageX + position.x );
887
+ const y = 0.5 * ( event.pageY + position.y );
879
888
 
880
- }
889
+ this._panStart.set( x, y );
881
890
 
882
891
  }
883
892
 
884
- function handleTouchStartDolly( event ) {
885
-
886
- const position = getSecondPointerPosition( event );
887
-
888
- const dx = event.pageX - position.x;
889
- const dy = event.pageY - position.y;
890
-
891
- const distance = Math.sqrt( dx * dx + dy * dy );
892
-
893
- dollyStart.set( 0, distance );
894
-
895
- }
893
+ }
896
894
 
897
- function handleTouchStartDollyPan( event ) {
895
+ _handleTouchStartDolly( event ) {
898
896
 
899
- if ( scope.enableZoom ) handleTouchStartDolly( event );
897
+ const position = this._getSecondPointerPosition( event );
900
898
 
901
- if ( scope.enablePan ) handleTouchStartPan( event );
899
+ const dx = event.pageX - position.x;
900
+ const dy = event.pageY - position.y;
902
901
 
903
- }
902
+ const distance = Math.sqrt( dx * dx + dy * dy );
904
903
 
905
- function handleTouchStartDollyRotate( event ) {
904
+ this._dollyStart.set( 0, distance );
906
905
 
907
- if ( scope.enableZoom ) handleTouchStartDolly( event );
906
+ }
908
907
 
909
- if ( scope.enableRotate ) handleTouchStartRotate( event );
908
+ _handleTouchStartDollyPan( event ) {
910
909
 
911
- }
910
+ if ( this.enableZoom ) this._handleTouchStartDolly( event );
912
911
 
913
- function handleTouchMoveRotate( event ) {
912
+ if ( this.enablePan ) this._handleTouchStartPan( event );
914
913
 
915
- if ( pointers.length == 1 ) {
914
+ }
916
915
 
917
- rotateEnd.set( event.pageX, event.pageY );
916
+ _handleTouchStartDollyRotate( event ) {
918
917
 
919
- } else {
918
+ if ( this.enableZoom ) this._handleTouchStartDolly( event );
920
919
 
921
- const position = getSecondPointerPosition( event );
920
+ if ( this.enableRotate ) this._handleTouchStartRotate( event );
922
921
 
923
- const x = 0.5 * ( event.pageX + position.x );
924
- const y = 0.5 * ( event.pageY + position.y );
922
+ }
925
923
 
926
- rotateEnd.set( x, y );
924
+ _handleTouchMoveRotate( event ) {
927
925
 
928
- }
926
+ if ( this._pointers.length == 1 ) {
929
927
 
930
- rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
928
+ this._rotateEnd.set( event.pageX, event.pageY );
931
929
 
932
- const element = scope.domElement;
930
+ } else {
933
931
 
934
- rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
932
+ const position = this._getSecondPointerPosition( event );
935
933
 
936
- rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
934
+ const x = 0.5 * ( event.pageX + position.x );
935
+ const y = 0.5 * ( event.pageY + position.y );
937
936
 
938
- rotateStart.copy( rotateEnd );
937
+ this._rotateEnd.set( x, y );
939
938
 
940
939
  }
941
940
 
942
- function handleTouchMovePan( event ) {
941
+ this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );
943
942
 
944
- if ( pointers.length === 1 ) {
943
+ const element = this.domElement;
945
944
 
946
- panEnd.set( event.pageX, event.pageY );
945
+ this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height
947
946
 
948
- } else {
947
+ this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );
949
948
 
950
- const position = getSecondPointerPosition( event );
949
+ this._rotateStart.copy( this._rotateEnd );
951
950
 
952
- const x = 0.5 * ( event.pageX + position.x );
953
- const y = 0.5 * ( event.pageY + position.y );
951
+ }
954
952
 
955
- panEnd.set( x, y );
953
+ _handleTouchMovePan( event ) {
956
954
 
957
- }
955
+ if ( this._pointers.length === 1 ) {
958
956
 
959
- panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
957
+ this._panEnd.set( event.pageX, event.pageY );
960
958
 
961
- pan( panDelta.x, panDelta.y );
959
+ } else {
962
960
 
963
- panStart.copy( panEnd );
961
+ const position = this._getSecondPointerPosition( event );
964
962
 
965
- }
963
+ const x = 0.5 * ( event.pageX + position.x );
964
+ const y = 0.5 * ( event.pageY + position.y );
966
965
 
967
- function handleTouchMoveDolly( event ) {
966
+ this._panEnd.set( x, y );
968
967
 
969
- const position = getSecondPointerPosition( event );
968
+ }
970
969
 
971
- const dx = event.pageX - position.x;
972
- const dy = event.pageY - position.y;
970
+ this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );
973
971
 
974
- const distance = Math.sqrt( dx * dx + dy * dy );
972
+ this._pan( this._panDelta.x, this._panDelta.y );
975
973
 
976
- dollyEnd.set( 0, distance );
974
+ this._panStart.copy( this._panEnd );
977
975
 
978
- dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
976
+ }
979
977
 
980
- dollyOut( dollyDelta.y );
978
+ _handleTouchMoveDolly( event ) {
981
979
 
982
- dollyStart.copy( dollyEnd );
980
+ const position = this._getSecondPointerPosition( event );
983
981
 
984
- const centerX = ( event.pageX + position.x ) * 0.5;
985
- const centerY = ( event.pageY + position.y ) * 0.5;
982
+ const dx = event.pageX - position.x;
983
+ const dy = event.pageY - position.y;
986
984
 
987
- updateZoomParameters( centerX, centerY );
985
+ const distance = Math.sqrt( dx * dx + dy * dy );
988
986
 
989
- }
987
+ this._dollyEnd.set( 0, distance );
990
988
 
991
- function handleTouchMoveDollyPan( event ) {
989
+ this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) );
992
990
 
993
- if ( scope.enableZoom ) handleTouchMoveDolly( event );
991
+ this._dollyOut( this._dollyDelta.y );
994
992
 
995
- if ( scope.enablePan ) handleTouchMovePan( event );
993
+ this._dollyStart.copy( this._dollyEnd );
996
994
 
997
- }
995
+ const centerX = ( event.pageX + position.x ) * 0.5;
996
+ const centerY = ( event.pageY + position.y ) * 0.5;
998
997
 
999
- function handleTouchMoveDollyRotate( event ) {
998
+ this._updateZoomParameters( centerX, centerY );
1000
999
 
1001
- if ( scope.enableZoom ) handleTouchMoveDolly( event );
1000
+ }
1002
1001
 
1003
- if ( scope.enableRotate ) handleTouchMoveRotate( event );
1002
+ _handleTouchMoveDollyPan( event ) {
1004
1003
 
1005
- }
1004
+ if ( this.enableZoom ) this._handleTouchMoveDolly( event );
1006
1005
 
1007
- //
1008
- // event handlers - FSM: listen for events and reset state
1009
- //
1006
+ if ( this.enablePan ) this._handleTouchMovePan( event );
1010
1007
 
1011
- function onPointerDown( event ) {
1008
+ }
1012
1009
 
1013
- if ( scope.enabled === false ) return;
1010
+ _handleTouchMoveDollyRotate( event ) {
1014
1011
 
1015
- if ( pointers.length === 0 ) {
1012
+ if ( this.enableZoom ) this._handleTouchMoveDolly( event );
1016
1013
 
1017
- scope.domElement.setPointerCapture( event.pointerId );
1014
+ if ( this.enableRotate ) this._handleTouchMoveRotate( event );
1018
1015
 
1019
- scope.domElement.addEventListener( 'pointermove', onPointerMove );
1020
- scope.domElement.addEventListener( 'pointerup', onPointerUp );
1016
+ }
1021
1017
 
1022
- }
1018
+ // pointers
1023
1019
 
1024
- //
1020
+ _addPointer( event ) {
1025
1021
 
1026
- if ( isTrackingPointer( event ) ) return;
1022
+ this._pointers.push( event.pointerId );
1027
1023
 
1028
- //
1024
+ }
1029
1025
 
1030
- addPointer( event );
1026
+ _removePointer( event ) {
1031
1027
 
1032
- if ( event.pointerType === 'touch' ) {
1028
+ delete this._pointerPositions[ event.pointerId ];
1033
1029
 
1034
- onTouchStart( event );
1030
+ for ( let i = 0; i < this._pointers.length; i ++ ) {
1035
1031
 
1036
- } else {
1032
+ if ( this._pointers[ i ] == event.pointerId ) {
1037
1033
 
1038
- onMouseDown( event );
1034
+ this._pointers.splice( i, 1 );
1035
+ return;
1039
1036
 
1040
1037
  }
1041
1038
 
1042
1039
  }
1043
1040
 
1044
- function onPointerMove( event ) {
1045
-
1046
- if ( scope.enabled === false ) return;
1041
+ }
1047
1042
 
1048
- if ( event.pointerType === 'touch' ) {
1043
+ _isTrackingPointer( event ) {
1049
1044
 
1050
- onTouchMove( event );
1045
+ for ( let i = 0; i < this._pointers.length; i ++ ) {
1051
1046
 
1052
- } else {
1047
+ if ( this._pointers[ i ] == event.pointerId ) return true;
1053
1048
 
1054
- onMouseMove( event );
1049
+ }
1055
1050
 
1056
- }
1051
+ return false;
1057
1052
 
1058
- }
1053
+ }
1059
1054
 
1060
- function onPointerUp( event ) {
1055
+ _trackPointer( event ) {
1061
1056
 
1062
- removePointer( event );
1057
+ let position = this._pointerPositions[ event.pointerId ];
1063
1058
 
1064
- switch ( pointers.length ) {
1059
+ if ( position === undefined ) {
1065
1060
 
1066
- case 0:
1061
+ position = new Vector2();
1062
+ this._pointerPositions[ event.pointerId ] = position;
1067
1063
 
1068
- scope.domElement.releasePointerCapture( event.pointerId );
1064
+ }
1069
1065
 
1070
- scope.domElement.removeEventListener( 'pointermove', onPointerMove );
1071
- scope.domElement.removeEventListener( 'pointerup', onPointerUp );
1066
+ position.set( event.pageX, event.pageY );
1072
1067
 
1073
- scope.dispatchEvent( _endEvent );
1068
+ }
1074
1069
 
1075
- state = STATE.NONE;
1070
+ _getSecondPointerPosition( event ) {
1076
1071
 
1077
- break;
1072
+ const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ];
1078
1073
 
1079
- case 1:
1074
+ return this._pointerPositions[ pointerId ];
1080
1075
 
1081
- const pointerId = pointers[ 0 ];
1082
- const position = pointerPositions[ pointerId ];
1076
+ }
1083
1077
 
1084
- // minimal placeholder event - allows state correction on pointer-up
1085
- onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );
1078
+ //
1086
1079
 
1087
- break;
1080
+ _customWheelEvent( event ) {
1088
1081
 
1089
- }
1082
+ const mode = event.deltaMode;
1090
1083
 
1091
- }
1084
+ // minimal wheel event altered to meet delta-zoom demand
1085
+ const newEvent = {
1086
+ clientX: event.clientX,
1087
+ clientY: event.clientY,
1088
+ deltaY: event.deltaY,
1089
+ };
1092
1090
 
1093
- function onMouseDown( event ) {
1091
+ switch ( mode ) {
1094
1092
 
1095
- let mouseAction;
1093
+ case 1: // LINE_MODE
1094
+ newEvent.deltaY *= 16;
1095
+ break;
1096
1096
 
1097
- switch ( event.button ) {
1097
+ case 2: // PAGE_MODE
1098
+ newEvent.deltaY *= 100;
1099
+ break;
1098
1100
 
1099
- case 0:
1101
+ }
1100
1102
 
1101
- mouseAction = scope.mouseButtons.LEFT;
1102
- break;
1103
+ // detect if event was triggered by pinching
1104
+ if ( event.ctrlKey && ! this._controlActive ) {
1103
1105
 
1104
- case 1:
1106
+ newEvent.deltaY *= 10;
1105
1107
 
1106
- mouseAction = scope.mouseButtons.MIDDLE;
1107
- break;
1108
+ }
1108
1109
 
1109
- case 2:
1110
+ return newEvent;
1110
1111
 
1111
- mouseAction = scope.mouseButtons.RIGHT;
1112
- break;
1112
+ }
1113
1113
 
1114
- default:
1114
+ }
1115
1115
 
1116
- mouseAction = - 1;
1116
+ function onPointerDown( event ) {
1117
1117
 
1118
- }
1118
+ if ( this.enabled === false ) return;
1119
1119
 
1120
- switch ( mouseAction ) {
1120
+ if ( this._pointers.length === 0 ) {
1121
1121
 
1122
- case MOUSE.DOLLY:
1122
+ this.domElement.setPointerCapture( event.pointerId );
1123
1123
 
1124
- if ( scope.enableZoom === false ) return;
1124
+ this.domElement.addEventListener( 'pointermove', this._onPointerMove );
1125
+ this.domElement.addEventListener( 'pointerup', this._onPointerUp );
1125
1126
 
1126
- handleMouseDownDolly( event );
1127
+ }
1127
1128
 
1128
- state = STATE.DOLLY;
1129
+ //
1129
1130
 
1130
- break;
1131
+ if ( this._isTrackingPointer( event ) ) return;
1131
1132
 
1132
- case MOUSE.ROTATE:
1133
+ //
1133
1134
 
1134
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1135
+ this._addPointer( event );
1135
1136
 
1136
- if ( scope.enablePan === false ) return;
1137
+ if ( event.pointerType === 'touch' ) {
1137
1138
 
1138
- handleMouseDownPan( event );
1139
+ this._onTouchStart( event );
1139
1140
 
1140
- state = STATE.PAN;
1141
+ } else {
1141
1142
 
1142
- } else {
1143
+ this._onMouseDown( event );
1143
1144
 
1144
- if ( scope.enableRotate === false ) return;
1145
+ }
1145
1146
 
1146
- handleMouseDownRotate( event );
1147
+ }
1147
1148
 
1148
- state = STATE.ROTATE;
1149
+ function onPointerMove( event ) {
1149
1150
 
1150
- }
1151
+ if ( this.enabled === false ) return;
1151
1152
 
1152
- break;
1153
+ if ( event.pointerType === 'touch' ) {
1153
1154
 
1154
- case MOUSE.PAN:
1155
+ this._onTouchMove( event );
1155
1156
 
1156
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1157
+ } else {
1157
1158
 
1158
- if ( scope.enableRotate === false ) return;
1159
+ this._onMouseMove( event );
1159
1160
 
1160
- handleMouseDownRotate( event );
1161
+ }
1161
1162
 
1162
- state = STATE.ROTATE;
1163
+ }
1163
1164
 
1164
- } else {
1165
+ function onPointerUp( event ) {
1165
1166
 
1166
- if ( scope.enablePan === false ) return;
1167
+ this._removePointer( event );
1167
1168
 
1168
- handleMouseDownPan( event );
1169
+ switch ( this._pointers.length ) {
1169
1170
 
1170
- state = STATE.PAN;
1171
+ case 0:
1171
1172
 
1172
- }
1173
+ this.domElement.releasePointerCapture( event.pointerId );
1173
1174
 
1174
- break;
1175
+ this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
1176
+ this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
1175
1177
 
1176
- default:
1178
+ this.dispatchEvent( _endEvent );
1177
1179
 
1178
- state = STATE.NONE;
1180
+ this.state = _STATE.NONE;
1179
1181
 
1180
- }
1182
+ break;
1181
1183
 
1182
- if ( state !== STATE.NONE ) {
1184
+ case 1:
1183
1185
 
1184
- scope.dispatchEvent( _startEvent );
1186
+ const pointerId = this._pointers[ 0 ];
1187
+ const position = this._pointerPositions[ pointerId ];
1185
1188
 
1186
- }
1189
+ // minimal placeholder event - allows state correction on pointer-up
1190
+ this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );
1187
1191
 
1188
- }
1192
+ break;
1189
1193
 
1190
- function onMouseMove( event ) {
1194
+ }
1191
1195
 
1192
- switch ( state ) {
1196
+ }
1193
1197
 
1194
- case STATE.ROTATE:
1198
+ function onMouseDown( event ) {
1195
1199
 
1196
- if ( scope.enableRotate === false ) return;
1200
+ let mouseAction;
1197
1201
 
1198
- handleMouseMoveRotate( event );
1202
+ switch ( event.button ) {
1199
1203
 
1200
- break;
1204
+ case 0:
1201
1205
 
1202
- case STATE.DOLLY:
1206
+ mouseAction = this.mouseButtons.LEFT;
1207
+ break;
1203
1208
 
1204
- if ( scope.enableZoom === false ) return;
1209
+ case 1:
1205
1210
 
1206
- handleMouseMoveDolly( event );
1211
+ mouseAction = this.mouseButtons.MIDDLE;
1212
+ break;
1207
1213
 
1208
- break;
1214
+ case 2:
1209
1215
 
1210
- case STATE.PAN:
1216
+ mouseAction = this.mouseButtons.RIGHT;
1217
+ break;
1211
1218
 
1212
- if ( scope.enablePan === false ) return;
1219
+ default:
1213
1220
 
1214
- handleMouseMovePan( event );
1221
+ mouseAction = - 1;
1215
1222
 
1216
- break;
1223
+ }
1217
1224
 
1218
- }
1225
+ switch ( mouseAction ) {
1219
1226
 
1220
- }
1227
+ case MOUSE.DOLLY:
1221
1228
 
1222
- function onMouseWheel( event ) {
1229
+ if ( this.enableZoom === false ) return;
1223
1230
 
1224
- if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
1231
+ this._handleMouseDownDolly( event );
1225
1232
 
1226
- event.preventDefault();
1233
+ this.state = _STATE.DOLLY;
1227
1234
 
1228
- scope.dispatchEvent( _startEvent );
1235
+ break;
1229
1236
 
1230
- handleMouseWheel( customWheelEvent( event ) );
1237
+ case MOUSE.ROTATE:
1231
1238
 
1232
- scope.dispatchEvent( _endEvent );
1239
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1233
1240
 
1234
- }
1241
+ if ( this.enablePan === false ) return;
1235
1242
 
1236
- function customWheelEvent( event ) {
1243
+ this._handleMouseDownPan( event );
1237
1244
 
1238
- const mode = event.deltaMode;
1245
+ this.state = _STATE.PAN;
1239
1246
 
1240
- // minimal wheel event altered to meet delta-zoom demand
1241
- const newEvent = {
1242
- clientX: event.clientX,
1243
- clientY: event.clientY,
1244
- deltaY: event.deltaY,
1245
- };
1247
+ } else {
1246
1248
 
1247
- switch ( mode ) {
1249
+ if ( this.enableRotate === false ) return;
1248
1250
 
1249
- case 1: // LINE_MODE
1250
- newEvent.deltaY *= 16;
1251
- break;
1251
+ this._handleMouseDownRotate( event );
1252
1252
 
1253
- case 2: // PAGE_MODE
1254
- newEvent.deltaY *= 100;
1255
- break;
1253
+ this.state = _STATE.ROTATE;
1256
1254
 
1257
1255
  }
1258
1256
 
1259
- // detect if event was triggered by pinching
1260
- if ( event.ctrlKey && ! controlActive ) {
1257
+ break;
1261
1258
 
1262
- newEvent.deltaY *= 10;
1259
+ case MOUSE.PAN:
1263
1260
 
1264
- }
1261
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1265
1262
 
1266
- return newEvent;
1263
+ if ( this.enableRotate === false ) return;
1267
1264
 
1268
- }
1265
+ this._handleMouseDownRotate( event );
1269
1266
 
1270
- function interceptControlDown( event ) {
1267
+ this.state = _STATE.ROTATE;
1271
1268
 
1272
- if ( event.key === 'Control' ) {
1273
-
1274
- controlActive = true;
1269
+ } else {
1275
1270
 
1271
+ if ( this.enablePan === false ) return;
1276
1272
 
1277
- const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
1273
+ this._handleMouseDownPan( event );
1278
1274
 
1279
- document.addEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } );
1275
+ this.state = _STATE.PAN;
1280
1276
 
1281
1277
  }
1282
1278
 
1283
- }
1284
-
1285
- function interceptControlUp( event ) {
1279
+ break;
1286
1280
 
1287
- if ( event.key === 'Control' ) {
1281
+ default:
1288
1282
 
1289
- controlActive = false;
1283
+ this.state = _STATE.NONE;
1290
1284
 
1285
+ }
1291
1286
 
1292
- const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
1287
+ if ( this.state !== _STATE.NONE ) {
1293
1288
 
1294
- document.removeEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } );
1289
+ this.dispatchEvent( _startEvent );
1295
1290
 
1296
- }
1291
+ }
1297
1292
 
1298
- }
1293
+ }
1299
1294
 
1300
- function onKeyDown( event ) {
1295
+ function onMouseMove( event ) {
1301
1296
 
1302
- if ( scope.enabled === false || scope.enablePan === false ) return;
1297
+ switch ( this.state ) {
1303
1298
 
1304
- handleKeyDown( event );
1299
+ case _STATE.ROTATE:
1305
1300
 
1306
- }
1301
+ if ( this.enableRotate === false ) return;
1307
1302
 
1308
- function onTouchStart( event ) {
1303
+ this._handleMouseMoveRotate( event );
1309
1304
 
1310
- trackPointer( event );
1305
+ break;
1311
1306
 
1312
- switch ( pointers.length ) {
1307
+ case _STATE.DOLLY:
1313
1308
 
1314
- case 1:
1309
+ if ( this.enableZoom === false ) return;
1315
1310
 
1316
- switch ( scope.touches.ONE ) {
1311
+ this._handleMouseMoveDolly( event );
1317
1312
 
1318
- case TOUCH.ROTATE:
1313
+ break;
1319
1314
 
1320
- if ( scope.enableRotate === false ) return;
1315
+ case _STATE.PAN:
1321
1316
 
1322
- handleTouchStartRotate( event );
1317
+ if ( this.enablePan === false ) return;
1323
1318
 
1324
- state = STATE.TOUCH_ROTATE;
1319
+ this._handleMouseMovePan( event );
1325
1320
 
1326
- break;
1321
+ break;
1327
1322
 
1328
- case TOUCH.PAN:
1323
+ }
1329
1324
 
1330
- if ( scope.enablePan === false ) return;
1325
+ }
1331
1326
 
1332
- handleTouchStartPan( event );
1327
+ function onMouseWheel( event ) {
1333
1328
 
1334
- state = STATE.TOUCH_PAN;
1329
+ if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return;
1335
1330
 
1336
- break;
1331
+ event.preventDefault();
1337
1332
 
1338
- default:
1333
+ this.dispatchEvent( _startEvent );
1339
1334
 
1340
- state = STATE.NONE;
1335
+ this._handleMouseWheel( this._customWheelEvent( event ) );
1341
1336
 
1342
- }
1337
+ this.dispatchEvent( _endEvent );
1343
1338
 
1344
- break;
1339
+ }
1345
1340
 
1346
- case 2:
1341
+ function onKeyDown( event ) {
1347
1342
 
1348
- switch ( scope.touches.TWO ) {
1343
+ if ( this.enabled === false || this.enablePan === false ) return;
1349
1344
 
1350
- case TOUCH.DOLLY_PAN:
1345
+ this._handleKeyDown( event );
1351
1346
 
1352
- if ( scope.enableZoom === false && scope.enablePan === false ) return;
1347
+ }
1353
1348
 
1354
- handleTouchStartDollyPan( event );
1349
+ function onTouchStart( event ) {
1355
1350
 
1356
- state = STATE.TOUCH_DOLLY_PAN;
1351
+ this._trackPointer( event );
1357
1352
 
1358
- break;
1353
+ switch ( this._pointers.length ) {
1359
1354
 
1360
- case TOUCH.DOLLY_ROTATE:
1355
+ case 1:
1361
1356
 
1362
- if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1357
+ switch ( this.touches.ONE ) {
1363
1358
 
1364
- handleTouchStartDollyRotate( event );
1359
+ case TOUCH.ROTATE:
1365
1360
 
1366
- state = STATE.TOUCH_DOLLY_ROTATE;
1361
+ if ( this.enableRotate === false ) return;
1367
1362
 
1368
- break;
1363
+ this._handleTouchStartRotate( event );
1369
1364
 
1370
- default:
1365
+ this.state = _STATE.TOUCH_ROTATE;
1371
1366
 
1372
- state = STATE.NONE;
1367
+ break;
1373
1368
 
1374
- }
1369
+ case TOUCH.PAN:
1375
1370
 
1376
- break;
1371
+ if ( this.enablePan === false ) return;
1377
1372
 
1378
- default:
1373
+ this._handleTouchStartPan( event );
1379
1374
 
1380
- state = STATE.NONE;
1375
+ this.state = _STATE.TOUCH_PAN;
1381
1376
 
1382
- }
1377
+ break;
1383
1378
 
1384
- if ( state !== STATE.NONE ) {
1379
+ default:
1385
1380
 
1386
- scope.dispatchEvent( _startEvent );
1381
+ this.state = _STATE.NONE;
1387
1382
 
1388
1383
  }
1389
1384
 
1390
- }
1385
+ break;
1391
1386
 
1392
- function onTouchMove( event ) {
1387
+ case 2:
1393
1388
 
1394
- trackPointer( event );
1389
+ switch ( this.touches.TWO ) {
1395
1390
 
1396
- switch ( state ) {
1391
+ case TOUCH.DOLLY_PAN:
1397
1392
 
1398
- case STATE.TOUCH_ROTATE:
1393
+ if ( this.enableZoom === false && this.enablePan === false ) return;
1399
1394
 
1400
- if ( scope.enableRotate === false ) return;
1395
+ this._handleTouchStartDollyPan( event );
1401
1396
 
1402
- handleTouchMoveRotate( event );
1403
-
1404
- scope.update();
1397
+ this.state = _STATE.TOUCH_DOLLY_PAN;
1405
1398
 
1406
1399
  break;
1407
1400
 
1408
- case STATE.TOUCH_PAN:
1401
+ case TOUCH.DOLLY_ROTATE:
1409
1402
 
1410
- if ( scope.enablePan === false ) return;
1403
+ if ( this.enableZoom === false && this.enableRotate === false ) return;
1411
1404
 
1412
- handleTouchMovePan( event );
1405
+ this._handleTouchStartDollyRotate( event );
1413
1406
 
1414
- scope.update();
1407
+ this.state = _STATE.TOUCH_DOLLY_ROTATE;
1415
1408
 
1416
1409
  break;
1417
1410
 
1418
- case STATE.TOUCH_DOLLY_PAN:
1411
+ default:
1419
1412
 
1420
- if ( scope.enableZoom === false && scope.enablePan === false ) return;
1413
+ this.state = _STATE.NONE;
1421
1414
 
1422
- handleTouchMoveDollyPan( event );
1415
+ }
1423
1416
 
1424
- scope.update();
1417
+ break;
1425
1418
 
1426
- break;
1419
+ default:
1427
1420
 
1428
- case STATE.TOUCH_DOLLY_ROTATE:
1421
+ this.state = _STATE.NONE;
1429
1422
 
1430
- if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1423
+ }
1431
1424
 
1432
- handleTouchMoveDollyRotate( event );
1425
+ if ( this.state !== _STATE.NONE ) {
1433
1426
 
1434
- scope.update();
1427
+ this.dispatchEvent( _startEvent );
1435
1428
 
1436
- break;
1429
+ }
1437
1430
 
1438
- default:
1431
+ }
1439
1432
 
1440
- state = STATE.NONE;
1433
+ function onTouchMove( event ) {
1441
1434
 
1442
- }
1435
+ this._trackPointer( event );
1443
1436
 
1444
- }
1437
+ switch ( this.state ) {
1445
1438
 
1446
- function onContextMenu( event ) {
1439
+ case _STATE.TOUCH_ROTATE:
1447
1440
 
1448
- if ( scope.enabled === false ) return;
1441
+ if ( this.enableRotate === false ) return;
1449
1442
 
1450
- event.preventDefault();
1443
+ this._handleTouchMoveRotate( event );
1451
1444
 
1452
- }
1445
+ this.update();
1453
1446
 
1454
- function addPointer( event ) {
1447
+ break;
1455
1448
 
1456
- pointers.push( event.pointerId );
1449
+ case _STATE.TOUCH_PAN:
1457
1450
 
1458
- }
1451
+ if ( this.enablePan === false ) return;
1459
1452
 
1460
- function removePointer( event ) {
1453
+ this._handleTouchMovePan( event );
1461
1454
 
1462
- delete pointerPositions[ event.pointerId ];
1455
+ this.update();
1463
1456
 
1464
- for ( let i = 0; i < pointers.length; i ++ ) {
1457
+ break;
1465
1458
 
1466
- if ( pointers[ i ] == event.pointerId ) {
1459
+ case _STATE.TOUCH_DOLLY_PAN:
1467
1460
 
1468
- pointers.splice( i, 1 );
1469
- return;
1461
+ if ( this.enableZoom === false && this.enablePan === false ) return;
1470
1462
 
1471
- }
1463
+ this._handleTouchMoveDollyPan( event );
1472
1464
 
1473
- }
1465
+ this.update();
1474
1466
 
1475
- }
1467
+ break;
1476
1468
 
1477
- function isTrackingPointer( event ) {
1469
+ case _STATE.TOUCH_DOLLY_ROTATE:
1478
1470
 
1479
- for ( let i = 0; i < pointers.length; i ++ ) {
1471
+ if ( this.enableZoom === false && this.enableRotate === false ) return;
1480
1472
 
1481
- if ( pointers[ i ] == event.pointerId ) return true;
1473
+ this._handleTouchMoveDollyRotate( event );
1482
1474
 
1483
- }
1475
+ this.update();
1484
1476
 
1485
- return false;
1477
+ break;
1486
1478
 
1487
- }
1479
+ default:
1488
1480
 
1489
- function trackPointer( event ) {
1481
+ this.state = _STATE.NONE;
1490
1482
 
1491
- let position = pointerPositions[ event.pointerId ];
1483
+ }
1492
1484
 
1493
- if ( position === undefined ) {
1485
+ }
1494
1486
 
1495
- position = new Vector2();
1496
- pointerPositions[ event.pointerId ] = position;
1487
+ function onContextMenu( event ) {
1497
1488
 
1498
- }
1489
+ if ( this.enabled === false ) return;
1499
1490
 
1500
- position.set( event.pageX, event.pageY );
1491
+ event.preventDefault();
1501
1492
 
1502
- }
1493
+ }
1503
1494
 
1504
- function getSecondPointerPosition( event ) {
1495
+ function interceptControlDown( event ) {
1505
1496
 
1506
- const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ];
1497
+ if ( event.key === 'Control' ) {
1507
1498
 
1508
- return pointerPositions[ pointerId ];
1499
+ this._controlActive = true;
1509
1500
 
1510
- }
1501
+ const document = this.domElement.getRootNode(); // offscreen canvas compatibility
1511
1502
 
1512
- //
1503
+ document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } );
1513
1504
 
1514
- scope.domElement.addEventListener( 'contextmenu', onContextMenu );
1505
+ }
1506
+
1507
+ }
1515
1508
 
1516
- scope.domElement.addEventListener( 'pointerdown', onPointerDown );
1517
- scope.domElement.addEventListener( 'pointercancel', onPointerUp );
1518
- scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
1509
+ function interceptControlUp( event ) {
1519
1510
 
1520
- const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
1511
+ if ( event.key === 'Control' ) {
1521
1512
 
1522
- document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } );
1513
+ this._controlActive = false;
1523
1514
 
1524
- // force an update at start
1515
+ const document = this.domElement.getRootNode(); // offscreen canvas compatibility
1525
1516
 
1526
- this.update();
1517
+ document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } );
1527
1518
 
1528
1519
  }
1529
1520