super-three 0.147.1 → 0.154.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 (896) hide show
  1. package/LICENSE +1 -1
  2. package/build/three.cjs +37190 -21678
  3. package/build/three.js +37182 -21669
  4. package/build/three.min.js +4 -2
  5. package/build/three.module.js +7656 -6548
  6. package/build/three.module.min.js +7 -0
  7. package/examples/jsm/animation/AnimationClipCreator.js +5 -3
  8. package/examples/jsm/cameras/CinematicCamera.js +1 -2
  9. package/examples/jsm/capabilities/WebGPU.js +17 -5
  10. package/examples/jsm/controls/ArcballControls.js +1835 -1827
  11. package/examples/jsm/controls/FlyControls.js +25 -9
  12. package/examples/jsm/controls/MapControls.js +28 -0
  13. package/examples/jsm/controls/OrbitControls.js +25 -46
  14. package/examples/jsm/controls/PointerLockControls.js +70 -65
  15. package/examples/jsm/controls/TrackballControls.js +18 -4
  16. package/examples/jsm/controls/TransformControls.js +0 -1
  17. package/examples/jsm/csm/CSM.js +12 -6
  18. package/examples/jsm/csm/CSMShader.js +7 -6
  19. package/examples/jsm/effects/AnaglyphEffect.js +6 -20
  20. package/examples/jsm/effects/AsciiEffect.js +3 -6
  21. package/examples/jsm/effects/OutlineEffect.js +2 -16
  22. package/examples/jsm/effects/ParallaxBarrierEffect.js +3 -0
  23. package/examples/jsm/environments/RoomEnvironment.js +6 -2
  24. package/examples/jsm/exporters/DRACOExporter.js +53 -11
  25. package/examples/jsm/exporters/EXRExporter.js +3 -9
  26. package/examples/jsm/exporters/GLTFExporter.js +597 -112
  27. package/examples/jsm/exporters/KTX2Exporter.js +34 -23
  28. package/examples/jsm/exporters/MMDExporter.js +1 -1
  29. package/examples/jsm/exporters/PLYExporter.js +8 -1
  30. package/examples/jsm/exporters/STLExporter.js +8 -4
  31. package/examples/jsm/exporters/USDZExporter.js +184 -124
  32. package/examples/jsm/geometries/ConvexGeometry.js +0 -6
  33. package/examples/jsm/geometries/ParametricGeometry.js +10 -0
  34. package/examples/jsm/helpers/OctreeHelper.js +1 -0
  35. package/examples/jsm/helpers/ViewHelper.js +71 -57
  36. package/examples/jsm/interactive/HTMLMesh.js +16 -3
  37. package/examples/jsm/interactive/InteractiveGroup.js +1 -1
  38. package/examples/{js → jsm}/libs/basis/README.md +4 -4
  39. package/examples/jsm/libs/draco/draco_decoder.js +34 -0
  40. package/examples/jsm/libs/draco/draco_decoder.wasm +0 -0
  41. package/examples/jsm/libs/draco/draco_wasm_wrapper.js +117 -0
  42. package/examples/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  43. package/examples/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  44. package/examples/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  45. package/examples/jsm/libs/lil-gui.module.min.js +2 -2
  46. package/examples/jsm/libs/tween.module.js +803 -0
  47. package/examples/jsm/lights/IESSpotLight.js +25 -0
  48. package/examples/jsm/lights/LightProbeGenerator.js +11 -9
  49. package/examples/jsm/lines/LineMaterial.js +1 -1
  50. package/examples/jsm/lines/LineSegments2.js +12 -10
  51. package/examples/jsm/loaders/3DMLoader.js +9 -6
  52. package/examples/jsm/loaders/3MFLoader.js +17 -12
  53. package/examples/jsm/loaders/AMFLoader.js +7 -4
  54. package/examples/jsm/loaders/BVHLoader.js +2 -2
  55. package/examples/jsm/loaders/ColladaLoader.js +19 -17
  56. package/examples/jsm/loaders/DRACOLoader.js +69 -18
  57. package/examples/jsm/loaders/EXRLoader.js +23 -24
  58. package/examples/jsm/loaders/FBXLoader.js +41 -52
  59. package/examples/jsm/loaders/GCodeLoader.js +1 -2
  60. package/examples/jsm/loaders/GLTFLoader.js +452 -282
  61. package/examples/jsm/loaders/HDRCubeTextureLoader.js +4 -4
  62. package/examples/jsm/loaders/IESLoader.js +337 -0
  63. package/examples/jsm/loaders/KMZLoader.js +4 -4
  64. package/examples/jsm/loaders/KTX2Loader.js +145 -69
  65. package/examples/jsm/loaders/LDrawLoader.js +24 -25
  66. package/examples/jsm/loaders/LWOLoader.js +12 -29
  67. package/examples/jsm/loaders/LottieLoader.js +3 -1
  68. package/examples/jsm/loaders/MMDLoader.js +49 -18
  69. package/examples/jsm/loaders/MTLLoader.js +2 -2
  70. package/examples/jsm/loaders/MaterialXLoader.js +9 -3
  71. package/examples/jsm/loaders/NRRDLoader.js +33 -5
  72. package/examples/jsm/loaders/OBJLoader.js +1 -1
  73. package/examples/jsm/loaders/PCDLoader.js +26 -12
  74. package/examples/jsm/loaders/PDBLoader.js +7 -2
  75. package/examples/jsm/loaders/PLYLoader.js +261 -106
  76. package/examples/jsm/loaders/RGBELoader.js +3 -3
  77. package/examples/jsm/loaders/STLLoader.js +9 -5
  78. package/examples/jsm/loaders/SVGLoader.js +36 -27
  79. package/examples/jsm/loaders/TTFLoader.js +1 -8
  80. package/examples/jsm/loaders/TiltLoader.js +13 -7
  81. package/examples/jsm/loaders/USDZLoader.js +5 -5
  82. package/examples/jsm/loaders/VOXLoader.js +8 -2
  83. package/examples/jsm/loaders/VRMLLoader.js +60 -20
  84. package/examples/jsm/loaders/VTKLoader.js +19 -10
  85. package/examples/jsm/loaders/XYZLoader.js +9 -3
  86. package/examples/jsm/loaders/lwo/IFFParser.js +19 -19
  87. package/examples/jsm/materials/MeshGouraudMaterial.js +2 -5
  88. package/examples/jsm/math/Lut.js +5 -4
  89. package/examples/jsm/math/MeshSurfaceSampler.js +82 -43
  90. package/examples/jsm/misc/ConvexObjectBreaker.js +2 -8
  91. package/examples/jsm/misc/GPUComputationRenderer.js +6 -5
  92. package/examples/jsm/misc/MD2Character.js +2 -2
  93. package/examples/jsm/misc/MD2CharacterComplex.js +2 -2
  94. package/examples/jsm/misc/ProgressiveLightMap.js +12 -10
  95. package/examples/jsm/misc/RollerCoaster.js +7 -1
  96. package/examples/jsm/misc/Volume.js +20 -5
  97. package/examples/jsm/misc/VolumeSlice.js +3 -1
  98. package/examples/jsm/modifiers/CurveModifier.js +1 -0
  99. package/examples/jsm/modifiers/TessellateModifier.js +19 -19
  100. package/examples/jsm/nodes/Nodes.js +149 -335
  101. package/examples/jsm/nodes/accessors/BitangentNode.js +43 -16
  102. package/examples/jsm/nodes/accessors/BufferAttributeNode.js +99 -0
  103. package/examples/jsm/nodes/accessors/BufferNode.js +6 -0
  104. package/examples/jsm/nodes/accessors/CameraNode.js +36 -5
  105. package/examples/jsm/nodes/accessors/CubeTextureNode.js +27 -33
  106. package/examples/jsm/nodes/accessors/ExtendedMaterialNode.js +64 -0
  107. package/examples/jsm/nodes/accessors/InstanceNode.js +39 -26
  108. package/examples/jsm/nodes/accessors/MaterialNode.js +164 -39
  109. package/examples/jsm/nodes/accessors/MaterialReferenceNode.js +16 -0
  110. package/examples/jsm/nodes/accessors/ModelNode.js +13 -0
  111. package/examples/jsm/nodes/accessors/ModelViewProjectionNode.js +13 -14
  112. package/examples/jsm/nodes/accessors/MorphNode.js +70 -0
  113. package/examples/jsm/nodes/accessors/NormalNode.js +36 -19
  114. package/examples/jsm/nodes/accessors/Object3DNode.js +40 -12
  115. package/examples/jsm/nodes/accessors/PointUVNode.js +6 -1
  116. package/examples/jsm/nodes/accessors/PositionNode.js +38 -23
  117. package/examples/jsm/nodes/accessors/ReferenceNode.js +21 -14
  118. package/examples/jsm/nodes/accessors/ReflectVectorNode.js +11 -7
  119. package/examples/jsm/nodes/accessors/SceneNode.js +46 -0
  120. package/examples/jsm/nodes/accessors/SkinningNode.js +55 -71
  121. package/examples/jsm/nodes/accessors/StorageBufferNode.js +6 -0
  122. package/examples/jsm/nodes/accessors/TangentNode.js +28 -20
  123. package/examples/jsm/nodes/accessors/TextureBicubicNode.js +94 -0
  124. package/examples/jsm/nodes/accessors/TextureNode.js +143 -13
  125. package/examples/jsm/nodes/accessors/TextureSizeNode.js +35 -0
  126. package/examples/jsm/nodes/accessors/UVNode.js +7 -1
  127. package/examples/jsm/nodes/accessors/UserDataNode.js +6 -0
  128. package/examples/jsm/nodes/code/CodeNode.js +78 -0
  129. package/examples/jsm/nodes/code/ExpressionNode.js +37 -0
  130. package/examples/jsm/nodes/{core → code}/FunctionCallNode.js +15 -1
  131. package/examples/jsm/nodes/{core → code}/FunctionNode.js +31 -9
  132. package/examples/jsm/nodes/code/ScriptableNode.js +488 -0
  133. package/examples/jsm/nodes/code/ScriptableValueNode.js +167 -0
  134. package/examples/jsm/nodes/core/ArrayUniformNode.js +3 -0
  135. package/examples/jsm/nodes/core/AttributeNode.js +17 -9
  136. package/examples/jsm/nodes/core/BypassNode.js +11 -4
  137. package/examples/jsm/nodes/core/CacheNode.js +46 -0
  138. package/examples/jsm/nodes/core/ConstNode.js +3 -0
  139. package/examples/jsm/nodes/core/ContextNode.js +10 -1
  140. package/examples/jsm/nodes/core/IndexNode.js +66 -0
  141. package/examples/jsm/nodes/core/InputNode.js +26 -5
  142. package/examples/jsm/nodes/core/LightingModel.js +17 -0
  143. package/examples/jsm/nodes/core/Node.js +128 -37
  144. package/examples/jsm/nodes/core/NodeAttribute.js +2 -1
  145. package/examples/jsm/nodes/core/NodeBuilder.js +287 -118
  146. package/examples/jsm/nodes/core/NodeCache.js +26 -0
  147. package/examples/jsm/nodes/core/NodeFrame.js +56 -5
  148. package/examples/jsm/nodes/core/NodeUtils.js +115 -28
  149. package/examples/jsm/nodes/core/PropertyNode.js +23 -2
  150. package/examples/jsm/nodes/core/StackNode.js +99 -0
  151. package/examples/jsm/nodes/core/TempNode.js +11 -3
  152. package/examples/jsm/nodes/core/UniformNode.js +16 -1
  153. package/examples/jsm/nodes/core/VarNode.js +19 -29
  154. package/examples/jsm/nodes/core/VaryingNode.js +15 -2
  155. package/examples/jsm/nodes/core/constants.js +6 -0
  156. package/examples/jsm/nodes/display/BlendModeNode.js +33 -19
  157. package/examples/jsm/nodes/display/ColorAdjustmentNode.js +36 -20
  158. package/examples/jsm/nodes/display/ColorSpaceNode.js +60 -47
  159. package/examples/jsm/nodes/display/FrontFacingNode.js +7 -1
  160. package/examples/jsm/nodes/display/NormalMapNode.js +33 -20
  161. package/examples/jsm/nodes/display/PosterizeNode.js +10 -3
  162. package/examples/jsm/nodes/display/ToneMappingNode.js +101 -11
  163. package/examples/jsm/nodes/display/ViewportDepthNode.js +69 -0
  164. package/examples/jsm/nodes/display/ViewportDepthTextureNode.js +34 -0
  165. package/examples/jsm/nodes/display/ViewportNode.js +27 -18
  166. package/examples/jsm/nodes/display/ViewportSharedTextureNode.js +31 -0
  167. package/examples/jsm/nodes/display/ViewportTextureNode.js +75 -0
  168. package/examples/jsm/nodes/fog/FogExp2Node.js +35 -0
  169. package/examples/jsm/nodes/fog/FogNode.js +12 -6
  170. package/examples/jsm/nodes/fog/FogRangeNode.js +12 -5
  171. package/examples/jsm/nodes/functions/BSDF/BRDF_BlinnPhong.js +30 -0
  172. package/examples/jsm/nodes/functions/BSDF/BRDF_GGX.js +15 -16
  173. package/examples/jsm/nodes/functions/BSDF/BRDF_Lambert.js +3 -3
  174. package/examples/jsm/nodes/functions/BSDF/BRDF_Sheen.js +43 -0
  175. package/examples/jsm/nodes/functions/BSDF/DFGApprox.js +9 -7
  176. package/examples/jsm/nodes/functions/BSDF/D_GGX.js +5 -5
  177. package/examples/jsm/nodes/functions/BSDF/EnvironmentBRDF.js +13 -0
  178. package/examples/jsm/nodes/functions/BSDF/F_Schlick.js +4 -6
  179. package/examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated.js +8 -6
  180. package/examples/jsm/nodes/functions/PhongLightingModel.js +28 -0
  181. package/examples/jsm/nodes/functions/PhysicalLightingModel.js +155 -45
  182. package/examples/jsm/nodes/functions/material/getGeometryRoughness.js +5 -4
  183. package/examples/jsm/nodes/functions/material/getRoughness.js +6 -6
  184. package/examples/jsm/nodes/geometry/RangeNode.js +50 -55
  185. package/examples/jsm/nodes/gpgpu/ComputeNode.js +9 -2
  186. package/examples/jsm/nodes/lighting/AONode.js +5 -3
  187. package/examples/jsm/nodes/lighting/AmbientLightNode.js +27 -0
  188. package/examples/jsm/nodes/lighting/AnalyticLightNode.js +114 -5
  189. package/examples/jsm/nodes/lighting/DirectionalLightNode.js +43 -0
  190. package/examples/jsm/nodes/lighting/EnvironmentNode.js +153 -38
  191. package/examples/jsm/nodes/lighting/HemisphereLightNode.js +15 -10
  192. package/examples/jsm/nodes/lighting/IESSpotLightNode.js +39 -0
  193. package/examples/jsm/nodes/lighting/LightNode.js +57 -0
  194. package/examples/jsm/nodes/lighting/LightUtils.js +17 -0
  195. package/examples/jsm/nodes/lighting/LightingContextNode.js +41 -17
  196. package/examples/jsm/nodes/lighting/LightingNode.js +3 -1
  197. package/examples/jsm/nodes/lighting/LightsNode.js +18 -10
  198. package/examples/jsm/nodes/lighting/PointLightNode.js +71 -0
  199. package/examples/jsm/nodes/lighting/SpotLightNode.js +92 -0
  200. package/examples/jsm/nodes/loaders/NodeLoader.js +5 -4
  201. package/examples/jsm/nodes/loaders/NodeMaterialLoader.js +4 -20
  202. package/examples/jsm/nodes/materials/LineBasicNodeMaterial.js +6 -24
  203. package/examples/jsm/nodes/materials/Materials.js +11 -55
  204. package/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js +5 -26
  205. package/examples/jsm/nodes/materials/MeshNormalNodeMaterial.js +38 -0
  206. package/examples/jsm/nodes/materials/MeshPhongNodeMaterial.js +65 -0
  207. package/examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js +47 -6
  208. package/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js +22 -110
  209. package/examples/jsm/nodes/materials/NodeMaterial.js +340 -67
  210. package/examples/jsm/nodes/materials/PointsNodeMaterial.js +7 -10
  211. package/examples/jsm/nodes/materials/SpriteNodeMaterial.js +29 -32
  212. package/examples/jsm/nodes/materialx/MaterialXNodes.js +31 -22
  213. package/examples/jsm/nodes/materialx/lib/mx_hsv.js +3 -3
  214. package/examples/jsm/nodes/materialx/lib/mx_noise.js +14 -13
  215. package/examples/jsm/nodes/materialx/lib/mx_transform_color.js +4 -3
  216. package/examples/jsm/nodes/math/CondNode.js +43 -17
  217. package/examples/jsm/nodes/math/MathNode.js +184 -73
  218. package/examples/jsm/nodes/math/OperatorNode.js +53 -3
  219. package/examples/jsm/nodes/procedural/CheckerNode.js +16 -8
  220. package/examples/jsm/nodes/shadernode/ShaderNode.js +206 -63
  221. package/examples/jsm/nodes/utils/ArrayElementNode.js +4 -2
  222. package/examples/jsm/nodes/utils/ConvertNode.js +19 -1
  223. package/examples/jsm/nodes/utils/DiscardNode.js +26 -0
  224. package/examples/jsm/nodes/utils/EquirectUVNode.js +10 -4
  225. package/examples/jsm/nodes/utils/JoinNode.js +4 -1
  226. package/examples/jsm/nodes/utils/LoopNode.js +186 -0
  227. package/examples/jsm/nodes/utils/MatcapUVNode.js +11 -4
  228. package/examples/jsm/nodes/utils/MaxMipLevelNode.js +18 -5
  229. package/examples/jsm/nodes/utils/OscNode.js +21 -14
  230. package/examples/jsm/nodes/utils/PackingNode.js +55 -0
  231. package/examples/jsm/nodes/utils/RemapNode.js +13 -5
  232. package/examples/jsm/nodes/utils/RotateUVNode.js +18 -7
  233. package/examples/jsm/nodes/utils/SpecularMIPLevelNode.js +37 -0
  234. package/examples/jsm/nodes/utils/SplitNode.js +7 -5
  235. package/examples/jsm/nodes/utils/SpriteSheetUVNode.js +15 -30
  236. package/examples/jsm/nodes/utils/TimerNode.js +16 -6
  237. package/examples/jsm/nodes/utils/TriplanarTexturesNode.js +21 -10
  238. package/examples/jsm/objects/GroundProjectedSkybox.js +172 -0
  239. package/examples/jsm/objects/Lensflare.js +3 -4
  240. package/examples/jsm/objects/MarchingCubes.js +5 -1
  241. package/examples/jsm/objects/Reflector.js +8 -5
  242. package/examples/jsm/objects/Refractor.js +5 -5
  243. package/examples/jsm/objects/ShadowMesh.js +3 -3
  244. package/examples/jsm/objects/Sky.js +1 -1
  245. package/examples/jsm/objects/Water.js +2 -1
  246. package/examples/jsm/objects/Water2.js +1 -1
  247. package/examples/jsm/offscreen/scene.js +1 -0
  248. package/examples/jsm/physics/AmmoPhysics.js +27 -28
  249. package/examples/jsm/physics/RapierPhysics.js +199 -0
  250. package/examples/jsm/postprocessing/AfterimagePass.js +3 -2
  251. package/examples/jsm/postprocessing/BloomPass.js +7 -4
  252. package/examples/jsm/postprocessing/BokehPass.js +3 -8
  253. package/examples/jsm/postprocessing/DotScreenPass.js +1 -2
  254. package/examples/jsm/postprocessing/EffectComposer.js +7 -95
  255. package/examples/jsm/postprocessing/FilmPass.js +1 -2
  256. package/examples/jsm/postprocessing/GlitchPass.js +0 -2
  257. package/examples/jsm/postprocessing/HalftonePass.js +0 -6
  258. package/examples/jsm/postprocessing/OutlinePass.js +8 -10
  259. package/examples/jsm/postprocessing/OutputPass.js +72 -0
  260. package/examples/jsm/postprocessing/Pass.js +2 -0
  261. package/examples/jsm/postprocessing/RenderPixelatedPass.js +4 -3
  262. package/examples/jsm/postprocessing/SAOPass.js +4 -26
  263. package/examples/jsm/postprocessing/SMAAPass.js +5 -8
  264. package/examples/jsm/postprocessing/SSAARenderPass.js +5 -5
  265. package/examples/jsm/postprocessing/SSAOPass.js +4 -14
  266. package/examples/jsm/postprocessing/SSRPass.js +3 -7
  267. package/examples/jsm/postprocessing/SavePass.js +5 -4
  268. package/examples/jsm/postprocessing/ShaderPass.js +1 -0
  269. package/examples/jsm/postprocessing/TAARenderPass.js +17 -4
  270. package/examples/jsm/postprocessing/TexturePass.js +2 -3
  271. package/examples/jsm/postprocessing/UnrealBloomPass.js +13 -21
  272. package/examples/jsm/renderers/CSS2DRenderer.js +6 -1
  273. package/examples/jsm/renderers/CSS3DRenderer.js +27 -5
  274. package/examples/jsm/renderers/{webgpu/WebGPUAnimation.js → common/Animation.js} +4 -4
  275. package/examples/jsm/renderers/common/Attributes.js +75 -0
  276. package/examples/jsm/renderers/common/Backend.js +162 -0
  277. package/examples/jsm/renderers/common/Background.js +134 -0
  278. package/examples/jsm/renderers/common/Binding.js +11 -0
  279. package/examples/jsm/renderers/common/Bindings.js +169 -0
  280. package/examples/jsm/renderers/common/Buffer.js +38 -0
  281. package/examples/jsm/renderers/{webgpu/WebGPUBufferUtils.js → common/BufferUtils.js} +2 -2
  282. package/examples/jsm/renderers/common/ChainMap.js +89 -0
  283. package/examples/jsm/renderers/common/ComputePipeline.js +17 -0
  284. package/examples/jsm/renderers/common/Constants.js +14 -0
  285. package/examples/jsm/renderers/common/CubeRenderTarget.js +65 -0
  286. package/examples/jsm/renderers/common/DataMap.js +54 -0
  287. package/examples/jsm/renderers/common/Geometries.js +215 -0
  288. package/examples/jsm/renderers/{webgpu/WebGPUInfo.js → common/Info.js} +2 -3
  289. package/examples/jsm/renderers/common/Pipeline.js +13 -0
  290. package/examples/jsm/renderers/common/Pipelines.js +321 -0
  291. package/examples/jsm/renderers/common/ProgrammableStage.js +18 -0
  292. package/examples/jsm/renderers/common/RenderContext.js +37 -0
  293. package/examples/jsm/renderers/common/RenderContexts.js +38 -0
  294. package/examples/jsm/renderers/{webgpu/WebGPURenderLists.js → common/RenderList.js} +26 -47
  295. package/examples/jsm/renderers/common/RenderLists.js +38 -0
  296. package/examples/jsm/renderers/common/RenderObject.js +113 -0
  297. package/examples/jsm/renderers/common/RenderObjects.js +92 -0
  298. package/examples/jsm/renderers/common/RenderPipeline.js +16 -0
  299. package/examples/jsm/renderers/common/RenderTarget.js +15 -0
  300. package/examples/jsm/renderers/common/Renderer.js +864 -0
  301. package/examples/jsm/renderers/common/SampledTexture.js +80 -0
  302. package/examples/jsm/renderers/common/Sampler.js +18 -0
  303. package/examples/jsm/renderers/common/StorageBuffer.js +17 -0
  304. package/examples/jsm/renderers/common/Textures.js +206 -0
  305. package/examples/jsm/renderers/{webgpu/WebGPUUniform.js → common/Uniform.js} +13 -9
  306. package/examples/jsm/renderers/common/UniformBuffer.js +15 -0
  307. package/examples/jsm/renderers/{webgpu/WebGPUUniformsGroup.js → common/UniformsGroup.js} +13 -13
  308. package/examples/jsm/renderers/common/nodes/NodeRender.js +302 -0
  309. package/examples/jsm/renderers/{webgpu/nodes/WebGPUNodeSampledTexture.js → common/nodes/NodeSampledTexture.js} +4 -4
  310. package/examples/jsm/renderers/{webgpu/nodes/WebGPUNodeSampler.js → common/nodes/NodeSampler.js} +3 -3
  311. package/examples/jsm/renderers/{webgpu/nodes/WebGPUNodeUniform.js → common/nodes/NodeUniform.js} +1 -1
  312. package/examples/jsm/renderers/common/nodes/Nodes.js +319 -0
  313. package/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +340 -0
  314. package/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +58 -50
  315. package/examples/jsm/renderers/webgpu/WebGPUBackend.js +847 -0
  316. package/examples/jsm/renderers/webgpu/WebGPURenderer.js +15 -987
  317. package/examples/jsm/renderers/webgpu/nodes/{WebGPUNodeBuilder.js → WGSLNodeBuilder.js} +139 -92
  318. package/examples/jsm/{nodes/parsers → renderers/webgpu/nodes}/WGSLNodeFunction.js +2 -2
  319. package/examples/jsm/{nodes/parsers → renderers/webgpu/nodes}/WGSLNodeParser.js +1 -1
  320. package/examples/jsm/renderers/webgpu/utils/WebGPUAttributeUtils.js +255 -0
  321. package/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js +145 -0
  322. package/examples/jsm/renderers/webgpu/{constants.js → utils/WebGPUConstants.js} +73 -12
  323. package/examples/jsm/renderers/webgpu/{WebGPURenderPipeline.js → utils/WebGPUPipelineUtils.js} +141 -296
  324. package/examples/jsm/renderers/webgpu/{WebGPUTextureUtils.js → utils/WebGPUTextureMipmapUtils.js} +5 -25
  325. package/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js +937 -0
  326. package/examples/jsm/renderers/webgpu/utils/WebGPUUtils.js +92 -0
  327. package/examples/jsm/shaders/BleachBypassShader.js +2 -0
  328. package/examples/jsm/shaders/BlendShader.js +1 -2
  329. package/examples/jsm/shaders/BokehShader2.js +3 -0
  330. package/examples/jsm/shaders/ColorifyShader.js +2 -0
  331. package/examples/jsm/shaders/ConvolutionShader.js +2 -0
  332. package/examples/jsm/shaders/CopyShader.js +4 -2
  333. package/examples/jsm/shaders/DotScreenShader.js +2 -0
  334. package/examples/jsm/shaders/ExposureShader.js +44 -0
  335. package/examples/jsm/shaders/FilmShader.js +2 -0
  336. package/examples/jsm/shaders/GammaCorrectionShader.js +2 -0
  337. package/examples/jsm/shaders/HorizontalBlurShader.js +2 -0
  338. package/examples/jsm/shaders/MMDToonShader.js +15 -4
  339. package/examples/jsm/shaders/OutputShader.js +61 -0
  340. package/examples/jsm/shaders/RGBShiftShader.js +4 -2
  341. package/examples/jsm/shaders/SepiaShader.js +2 -0
  342. package/examples/jsm/shaders/ToonShader.js +13 -5
  343. package/examples/jsm/shaders/VelocityShader.js +1 -1
  344. package/examples/jsm/shaders/VerticalBlurShader.js +2 -0
  345. package/examples/jsm/shaders/VignetteShader.js +2 -0
  346. package/examples/jsm/shaders/WaterRefractionShader.js +5 -2
  347. package/examples/jsm/utils/BufferGeometryUtils.js +54 -36
  348. package/examples/jsm/utils/LDrawUtils.js +4 -4
  349. package/examples/jsm/utils/PackedPhongMaterial.js +5 -78
  350. package/examples/jsm/utils/SceneUtils.js +8 -4
  351. package/examples/jsm/utils/SkeletonUtils.js +27 -210
  352. package/examples/jsm/utils/TextureUtils.js +86 -0
  353. package/examples/jsm/webxr/OculusHandPointerModel.js +34 -13
  354. package/examples/jsm/webxr/VRButton.js +1 -2
  355. package/examples/jsm/webxr/XRButton.js +198 -0
  356. package/examples/jsm/webxr/XRHandPrimitiveModel.js +1 -0
  357. package/examples/jsm/webxr/XRPlanes.js +100 -0
  358. package/package.json +35 -87
  359. package/src/Three.Legacy.js +0 -319
  360. package/src/Three.js +4 -3
  361. package/src/animation/AnimationUtils.js +13 -1
  362. package/src/animation/PropertyBinding.js +2 -2
  363. package/src/audio/Audio.js +7 -2
  364. package/src/audio/PositionalAudio.js +8 -0
  365. package/src/cameras/Camera.js +5 -0
  366. package/src/cameras/CubeCamera.js +76 -13
  367. package/src/cameras/OrthographicCamera.js +1 -1
  368. package/src/cameras/PerspectiveCamera.js +1 -1
  369. package/src/constants.js +22 -2
  370. package/src/core/BufferAttribute.js +146 -29
  371. package/src/core/BufferGeometry.js +2 -15
  372. package/src/core/GLBufferAttribute.js +2 -0
  373. package/src/core/InterleavedBufferAttribute.js +1 -1
  374. package/src/core/Object3D.js +37 -8
  375. package/src/extras/DataUtils.js +7 -1
  376. package/src/extras/ImageUtils.js +2 -2
  377. package/src/extras/PMREMGenerator.js +5 -5
  378. package/src/extras/core/Curve.js +1 -1
  379. package/src/extras/curves/LineCurve.js +6 -4
  380. package/src/extras/curves/LineCurve3.js +13 -0
  381. package/src/geometries/BoxGeometry.js +10 -0
  382. package/src/geometries/CircleGeometry.js +11 -1
  383. package/src/geometries/ConeGeometry.js +1 -1
  384. package/src/geometries/CylinderGeometry.js +11 -1
  385. package/src/geometries/EdgesGeometry.js +10 -0
  386. package/src/geometries/ExtrudeGeometry.js +11 -1
  387. package/src/geometries/LatheGeometry.js +10 -0
  388. package/src/geometries/PlaneGeometry.js +10 -0
  389. package/src/geometries/PolyhedronGeometry.js +10 -0
  390. package/src/geometries/RingGeometry.js +11 -1
  391. package/src/geometries/ShapeGeometry.js +10 -0
  392. package/src/geometries/SphereGeometry.js +12 -2
  393. package/src/geometries/TorusGeometry.js +11 -1
  394. package/src/geometries/TorusKnotGeometry.js +10 -0
  395. package/src/geometries/TubeGeometry.js +10 -0
  396. package/src/geometries/WireframeGeometry.js +10 -0
  397. package/src/helpers/Box3Helper.js +1 -2
  398. package/src/lights/DirectionalLight.js +1 -1
  399. package/src/lights/HemisphereLight.js +1 -1
  400. package/src/lights/SpotLight.js +1 -1
  401. package/src/loaders/AudioLoader.js +14 -8
  402. package/src/loaders/CubeTextureLoader.js +2 -0
  403. package/src/loaders/DataTextureLoader.js +5 -1
  404. package/src/loaders/Loader.js +2 -0
  405. package/src/loaders/MaterialLoader.js +9 -0
  406. package/src/loaders/ObjectLoader.js +10 -2
  407. package/src/materials/LineBasicMaterial.js +4 -0
  408. package/src/materials/Material.js +19 -4
  409. package/src/materials/MeshDistanceMaterial.js +0 -9
  410. package/src/materials/MeshPhysicalMaterial.js +32 -6
  411. package/src/materials/ShaderMaterial.js +6 -1
  412. package/src/math/Box2.js +3 -2
  413. package/src/math/Box3.js +49 -64
  414. package/src/math/Color.js +88 -66
  415. package/src/math/ColorManagement.js +75 -16
  416. package/src/math/Euler.js +2 -11
  417. package/src/math/Frustum.js +29 -5
  418. package/src/math/MathUtils.js +43 -1
  419. package/src/math/Matrix3.js +26 -6
  420. package/src/math/Matrix4.js +74 -19
  421. package/src/math/Plane.js +2 -2
  422. package/src/math/Quaternion.js +6 -0
  423. package/src/math/Ray.js +7 -7
  424. package/src/math/Triangle.js +37 -7
  425. package/src/math/Vector2.js +16 -0
  426. package/src/math/Vector3.js +10 -0
  427. package/src/math/interpolants/CubicInterpolant.js +1 -2
  428. package/src/objects/InstancedMesh.js +82 -1
  429. package/src/objects/Mesh.js +108 -76
  430. package/src/objects/Skeleton.js +1 -3
  431. package/src/objects/SkinnedMesh.js +123 -8
  432. package/src/objects/Sprite.js +1 -1
  433. package/src/renderers/WebGLCubeRenderTarget.js +12 -3
  434. package/src/renderers/WebGLMultipleRenderTargets.js +4 -2
  435. package/src/renderers/WebGLRenderTarget.js +14 -2
  436. package/src/renderers/WebGLRenderer.js +1439 -1252
  437. package/src/renderers/shaders/ShaderChunk/alphahash_fragment.glsl.js +7 -0
  438. package/src/renderers/shaders/ShaderChunk/alphahash_pars_fragment.glsl.js +68 -0
  439. package/src/renderers/shaders/ShaderChunk/alphamap_fragment.glsl.js +1 -1
  440. package/src/renderers/shaders/ShaderChunk/aomap_fragment.glsl.js +1 -1
  441. package/src/renderers/shaders/ShaderChunk/begin_vertex.glsl.js +6 -0
  442. package/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js +0 -271
  443. package/src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl.js +5 -5
  444. package/src/renderers/shaders/ShaderChunk/clearcoat_normal_fragment_maps.glsl.js +2 -10
  445. package/src/renderers/shaders/ShaderChunk/clearcoat_pars_fragment.glsl.js +5 -5
  446. package/src/renderers/shaders/ShaderChunk/common.glsl.js +38 -0
  447. package/src/renderers/shaders/ShaderChunk/cube_uv_reflection_fragment.glsl.js +1 -1
  448. package/src/renderers/shaders/ShaderChunk/displacementmap_vertex.glsl.js +1 -1
  449. package/src/renderers/shaders/ShaderChunk/emissivemap_fragment.glsl.js +1 -1
  450. package/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +26 -3
  451. package/src/renderers/shaders/ShaderChunk/lightmap_fragment.glsl.js +1 -1
  452. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +3 -3
  453. package/src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js +10 -2
  454. package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +11 -11
  455. package/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +39 -13
  456. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +306 -10
  457. package/src/renderers/shaders/ShaderChunk/map_fragment.glsl.js +1 -11
  458. package/src/renderers/shaders/ShaderChunk/map_particle_fragment.glsl.js +9 -1
  459. package/src/renderers/shaders/ShaderChunk/map_particle_pars_fragment.glsl.js +10 -2
  460. package/src/renderers/shaders/ShaderChunk/metalnessmap_fragment.glsl.js +1 -1
  461. package/src/renderers/shaders/ShaderChunk/normal_fragment_begin.glsl.js +40 -10
  462. package/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js +5 -13
  463. package/src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl.js +7 -7
  464. package/src/renderers/shaders/ShaderChunk/{output_fragment.glsl.js → opaque_fragment.glsl.js} +1 -2
  465. package/src/renderers/shaders/ShaderChunk/packing.glsl.js +11 -5
  466. package/src/renderers/shaders/ShaderChunk/roughnessmap_fragment.glsl.js +1 -1
  467. package/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +4 -11
  468. package/src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl.js +2 -2
  469. package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +39 -34
  470. package/src/renderers/shaders/ShaderChunk/specularmap_fragment.glsl.js +1 -1
  471. package/src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl.js +1 -1
  472. package/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js +5 -5
  473. package/src/renderers/shaders/ShaderChunk/transmission_pars_fragment.glsl.js +101 -16
  474. package/src/renderers/shaders/ShaderChunk/uv_pars_fragment.glsl.js +113 -1
  475. package/src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl.js +135 -7
  476. package/src/renderers/shaders/ShaderChunk/uv_vertex.glsl.js +117 -2
  477. package/src/renderers/shaders/ShaderChunk.js +10 -12
  478. package/src/renderers/shaders/ShaderLib/background.glsl.js +1 -9
  479. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +1 -1
  480. package/src/renderers/shaders/ShaderLib/cube.glsl.js +1 -1
  481. package/src/renderers/shaders/ShaderLib/depth.glsl.js +2 -0
  482. package/src/renderers/shaders/ShaderLib/distanceRGBA.glsl.js +2 -0
  483. package/src/renderers/shaders/ShaderLib/equirect.glsl.js +1 -1
  484. package/src/renderers/shaders/ShaderLib/linedashed.glsl.js +7 -2
  485. package/src/renderers/shaders/ShaderLib/meshbasic.glsl.js +5 -6
  486. package/src/renderers/shaders/ShaderLib/meshlambert.glsl.js +4 -5
  487. package/src/renderers/shaders/ShaderLib/meshmatcap.glsl.js +4 -2
  488. package/src/renderers/shaders/ShaderLib/meshnormal.glsl.js +3 -3
  489. package/src/renderers/shaders/ShaderLib/meshphong.glsl.js +4 -5
  490. package/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +20 -14
  491. package/src/renderers/shaders/ShaderLib/meshtoon.glsl.js +4 -5
  492. package/src/renderers/shaders/ShaderLib/points.glsl.js +17 -2
  493. package/src/renderers/shaders/ShaderLib/shadow.glsl.js +6 -1
  494. package/src/renderers/shaders/ShaderLib/sprite.glsl.js +4 -2
  495. package/src/renderers/shaders/ShaderLib.js +18 -4
  496. package/src/renderers/shaders/UniformsLib.js +27 -15
  497. package/src/renderers/shaders/UniformsUtils.js +12 -3
  498. package/src/renderers/webgl/WebGLBackground.js +23 -12
  499. package/src/renderers/webgl/WebGLBindingStates.js +13 -5
  500. package/src/renderers/webgl/WebGLCapabilities.js +1 -2
  501. package/src/renderers/webgl/WebGLClipping.js +7 -3
  502. package/src/renderers/webgl/WebGLGeometries.js +12 -0
  503. package/src/renderers/webgl/WebGLInfo.js +0 -1
  504. package/src/renderers/webgl/WebGLLights.js +2 -2
  505. package/src/renderers/webgl/WebGLMaterials.js +123 -234
  506. package/src/renderers/webgl/WebGLMorphtargets.js +1 -1
  507. package/src/renderers/webgl/WebGLObjects.js +23 -3
  508. package/src/renderers/webgl/WebGLProgram.js +154 -53
  509. package/src/renderers/webgl/WebGLPrograms.js +213 -130
  510. package/src/renderers/webgl/WebGLRenderStates.js +2 -2
  511. package/src/renderers/webgl/WebGLShadowMap.js +40 -27
  512. package/src/renderers/webgl/WebGLState.js +23 -9
  513. package/src/renderers/webgl/WebGLTextures.js +39 -19
  514. package/src/renderers/webgl/WebGLUniformsGroups.js +74 -33
  515. package/src/renderers/webgl/WebGLUtils.js +41 -29
  516. package/src/renderers/webvr/WebVRManager.js +12 -0
  517. package/src/renderers/webxr/WebXRController.js +3 -0
  518. package/src/renderers/webxr/WebXRManager.js +96 -134
  519. package/src/scenes/Scene.js +2 -18
  520. package/src/textures/CompressedTexture.js +2 -2
  521. package/src/textures/CubeTexture.js +2 -2
  522. package/src/textures/DataTexture.js +2 -2
  523. package/src/textures/DepthTexture.js +22 -0
  524. package/src/textures/FramebufferTexture.js +1 -3
  525. package/src/textures/Source.js +4 -0
  526. package/src/textures/Texture.js +42 -13
  527. package/src/utils.js +13 -1
  528. package/examples/fonts/open-sans/open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff +0 -0
  529. package/examples/fonts/open-sans/open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff2 +0 -0
  530. package/examples/fonts/open-sans/open-sans.css +0 -9
  531. package/examples/fonts/tabler-icons/fonts/tabler-icons.eot +0 -0
  532. package/examples/fonts/tabler-icons/fonts/tabler-icons.svg +0 -3966
  533. package/examples/fonts/tabler-icons/fonts/tabler-icons.ttf +0 -0
  534. package/examples/fonts/tabler-icons/fonts/tabler-icons.woff +0 -0
  535. package/examples/fonts/tabler-icons/fonts/tabler-icons.woff2 +0 -0
  536. package/examples/fonts/tabler-icons/tabler-icons.min.css +0 -4
  537. package/examples/js/animation/AnimationClipCreator.js +0 -89
  538. package/examples/js/animation/CCDIKSolver.js +0 -416
  539. package/examples/js/animation/MMDAnimationHelper.js +0 -1046
  540. package/examples/js/animation/MMDPhysics.js +0 -1174
  541. package/examples/js/cameras/CinematicCamera.js +0 -168
  542. package/examples/js/controls/ArcballControls.js +0 -2770
  543. package/examples/js/controls/DragControls.js +0 -205
  544. package/examples/js/controls/FirstPersonControls.js +0 -312
  545. package/examples/js/controls/FlyControls.js +0 -321
  546. package/examples/js/controls/OrbitControls.js +0 -1101
  547. package/examples/js/controls/PointerLockControls.js +0 -144
  548. package/examples/js/controls/TrackballControls.js +0 -729
  549. package/examples/js/controls/TransformControls.js +0 -1301
  550. package/examples/js/csm/CSM.js +0 -347
  551. package/examples/js/csm/CSMFrustum.js +0 -127
  552. package/examples/js/csm/CSMHelper.js +0 -165
  553. package/examples/js/csm/CSMShader.js +0 -253
  554. package/examples/js/curves/CurveExtras.js +0 -348
  555. package/examples/js/curves/NURBSCurve.js +0 -63
  556. package/examples/js/curves/NURBSSurface.js +0 -48
  557. package/examples/js/curves/NURBSUtils.js +0 -439
  558. package/examples/js/effects/AnaglyphEffect.js +0 -86
  559. package/examples/js/effects/AsciiEffect.js +0 -260
  560. package/examples/js/effects/OutlineEffect.js +0 -450
  561. package/examples/js/effects/ParallaxBarrierEffect.js +0 -62
  562. package/examples/js/effects/PeppersGhostEffect.js +0 -139
  563. package/examples/js/effects/StereoEffect.js +0 -46
  564. package/examples/js/environments/DebugEnvironment.js +0 -53
  565. package/examples/js/environments/RoomEnvironment.js +0 -124
  566. package/examples/js/exporters/ColladaExporter.js +0 -487
  567. package/examples/js/exporters/DRACOExporter.js +0 -212
  568. package/examples/js/exporters/EXRExporter.js +0 -455
  569. package/examples/js/exporters/GLTFExporter.js +0 -2367
  570. package/examples/js/exporters/MMDExporter.js +0 -187
  571. package/examples/js/exporters/OBJExporter.js +0 -260
  572. package/examples/js/exporters/PLYExporter.js +0 -427
  573. package/examples/js/exporters/STLExporter.js +0 -188
  574. package/examples/js/exporters/USDZExporter.js +0 -608
  575. package/examples/js/geometries/BoxLineGeometry.js +0 -59
  576. package/examples/js/geometries/ConvexGeometry.js +0 -53
  577. package/examples/js/geometries/DecalGeometry.js +0 -324
  578. package/examples/js/geometries/LightningStrike.js +0 -861
  579. package/examples/js/geometries/ParametricGeometries.js +0 -216
  580. package/examples/js/geometries/ParametricGeometry.js +0 -121
  581. package/examples/js/geometries/RoundedBoxGeometry.js +0 -142
  582. package/examples/js/geometries/TeapotGeometry.js +0 -335
  583. package/examples/js/geometries/TextGeometry.js +0 -53
  584. package/examples/js/helpers/LightProbeHelper.js +0 -48
  585. package/examples/js/helpers/OctreeHelper.js +0 -76
  586. package/examples/js/helpers/PositionalAudioHelper.js +0 -91
  587. package/examples/js/helpers/RectAreaLightHelper.js +0 -73
  588. package/examples/js/helpers/VertexNormalsHelper.js +0 -74
  589. package/examples/js/helpers/VertexTangentsHelper.js +0 -68
  590. package/examples/js/helpers/ViewHelper.js +0 -281
  591. package/examples/js/interactive/HTMLMesh.js +0 -497
  592. package/examples/js/interactive/InteractiveGroup.js +0 -95
  593. package/examples/js/interactive/SelectionBox.js +0 -195
  594. package/examples/js/interactive/SelectionHelper.js +0 -83
  595. package/examples/js/libs/chevrotain.min.js +0 -3
  596. package/examples/js/libs/draco/draco_decoder.js +0 -52
  597. package/examples/js/libs/draco/draco_decoder.wasm +0 -0
  598. package/examples/js/libs/draco/draco_wasm_wrapper.js +0 -104
  599. package/examples/js/libs/draco/gltf/draco_decoder.js +0 -48
  600. package/examples/js/libs/draco/gltf/draco_decoder.wasm +0 -0
  601. package/examples/js/libs/draco/gltf/draco_wasm_wrapper.js +0 -104
  602. package/examples/js/libs/fflate.min.js +0 -7
  603. package/examples/js/libs/ktx-parse.umd.js +0 -1
  604. package/examples/js/libs/meshopt_decoder.js +0 -188
  605. package/examples/js/libs/opentype.min.js +0 -1
  606. package/examples/js/libs/stats.min.js +0 -5
  607. package/examples/js/lights/LightProbeGenerator.js +0 -221
  608. package/examples/js/lights/RectAreaLightUniformsLib.js +0 -60
  609. package/examples/js/lines/Line2.js +0 -19
  610. package/examples/js/lines/LineGeometry.js +0 -69
  611. package/examples/js/lines/LineMaterial.js +0 -635
  612. package/examples/js/lines/LineSegments2.js +0 -313
  613. package/examples/js/lines/LineSegmentsGeometry.js +0 -198
  614. package/examples/js/lines/Wireframe.js +0 -47
  615. package/examples/js/lines/WireframeGeometry2.js +0 -20
  616. package/examples/js/loaders/3DMLoader.js +0 -1273
  617. package/examples/js/loaders/3MFLoader.js +0 -1306
  618. package/examples/js/loaders/AMFLoader.js +0 -504
  619. package/examples/js/loaders/BVHLoader.js +0 -395
  620. package/examples/js/loaders/BasisTextureLoader.js +0 -706
  621. package/examples/js/loaders/ColladaLoader.js +0 -3690
  622. package/examples/js/loaders/DDSLoader.js +0 -244
  623. package/examples/js/loaders/DRACOLoader.js +0 -511
  624. package/examples/js/loaders/EXRLoader.js +0 -2039
  625. package/examples/js/loaders/FBXLoader.js +0 -3681
  626. package/examples/js/loaders/FontLoader.js +0 -160
  627. package/examples/js/loaders/GCodeLoader.js +0 -255
  628. package/examples/js/loaders/GLTFLoader.js +0 -3862
  629. package/examples/js/loaders/HDRCubeTextureLoader.js +0 -87
  630. package/examples/js/loaders/KMZLoader.js +0 -121
  631. package/examples/js/loaders/KTXLoader.js +0 -159
  632. package/examples/js/loaders/LDrawLoader.js +0 -2263
  633. package/examples/js/loaders/LUT3dlLoader.js +0 -135
  634. package/examples/js/loaders/LUTCubeLoader.js +0 -132
  635. package/examples/js/loaders/LWOLoader.js +0 -901
  636. package/examples/js/loaders/LogLuvLoader.js +0 -715
  637. package/examples/js/loaders/LottieLoader.js +0 -62
  638. package/examples/js/loaders/MD2Loader.js +0 -248
  639. package/examples/js/loaders/MDDLoader.js +0 -91
  640. package/examples/js/loaders/MMDLoader.js +0 -1915
  641. package/examples/js/loaders/MTLLoader.js +0 -472
  642. package/examples/js/loaders/NRRDLoader.js +0 -609
  643. package/examples/js/loaders/OBJLoader.js +0 -789
  644. package/examples/js/loaders/PCDLoader.js +0 -413
  645. package/examples/js/loaders/PDBLoader.js +0 -317
  646. package/examples/js/loaders/PLYLoader.js +0 -576
  647. package/examples/js/loaders/PRWMLoader.js +0 -249
  648. package/examples/js/loaders/PVRLoader.js +0 -218
  649. package/examples/js/loaders/RGBELoader.js +0 -442
  650. package/examples/js/loaders/RGBMLoader.js +0 -1354
  651. package/examples/js/loaders/STLLoader.js +0 -364
  652. package/examples/js/loaders/SVGLoader.js +0 -2783
  653. package/examples/js/loaders/TDSLoader.js +0 -992
  654. package/examples/js/loaders/TGALoader.js +0 -484
  655. package/examples/js/loaders/TIFFLoader.js +0 -30
  656. package/examples/js/loaders/TTFLoader.js +0 -203
  657. package/examples/js/loaders/TiltLoader.js +0 -459
  658. package/examples/js/loaders/VOXLoader.js +0 -240
  659. package/examples/js/loaders/VRMLLoader.js +0 -3140
  660. package/examples/js/loaders/VTKLoader.js +0 -1078
  661. package/examples/js/loaders/XYZLoader.js +0 -89
  662. package/examples/js/loaders/lwo/IFFParser.js +0 -1067
  663. package/examples/js/loaders/lwo/LWO2Parser.js +0 -397
  664. package/examples/js/loaders/lwo/LWO3Parser.js +0 -360
  665. package/examples/js/materials/MeshGouraudMaterial.js +0 -387
  666. package/examples/js/math/Capsule.js +0 -103
  667. package/examples/js/math/ColorConverter.js +0 -33
  668. package/examples/js/math/ConvexHull.js +0 -1154
  669. package/examples/js/math/ImprovedNoise.js +0 -66
  670. package/examples/js/math/Lut.js +0 -164
  671. package/examples/js/math/MeshSurfaceSampler.js +0 -171
  672. package/examples/js/math/OBB.js +0 -382
  673. package/examples/js/math/Octree.js +0 -410
  674. package/examples/js/math/SimplexNoise.js +0 -445
  675. package/examples/js/misc/ConvexObjectBreaker.js +0 -474
  676. package/examples/js/misc/GPUComputationRenderer.js +0 -393
  677. package/examples/js/misc/Gyroscope.js +0 -56
  678. package/examples/js/misc/MD2Character.js +0 -235
  679. package/examples/js/misc/MD2CharacterComplex.js +0 -513
  680. package/examples/js/misc/MorphAnimMesh.js +0 -63
  681. package/examples/js/misc/MorphBlendMesh.js +0 -265
  682. package/examples/js/misc/ProgressiveLightMap.js +0 -335
  683. package/examples/js/misc/RollerCoaster.js +0 -430
  684. package/examples/js/misc/TubePainter.js +0 -167
  685. package/examples/js/misc/Volume.js +0 -437
  686. package/examples/js/misc/VolumeSlice.js +0 -214
  687. package/examples/js/modifiers/CurveModifier.js +0 -309
  688. package/examples/js/modifiers/EdgeSplitModifier.js +0 -228
  689. package/examples/js/modifiers/SimplifyModifier.js +0 -465
  690. package/examples/js/modifiers/TessellateModifier.js +0 -276
  691. package/examples/js/objects/GroundProjectedEnv.js +0 -181
  692. package/examples/js/objects/Lensflare.js +0 -356
  693. package/examples/js/objects/LightningStorm.js +0 -206
  694. package/examples/js/objects/MarchingCubes.js +0 -759
  695. package/examples/js/objects/Reflector.js +0 -216
  696. package/examples/js/objects/ReflectorForSSRPass.js +0 -315
  697. package/examples/js/objects/Refractor.js +0 -283
  698. package/examples/js/objects/ShadowMesh.js +0 -59
  699. package/examples/js/objects/Sky.js +0 -218
  700. package/examples/js/objects/Water.js +0 -292
  701. package/examples/js/objects/Water2.js +0 -307
  702. package/examples/js/physics/AmmoPhysics.js +0 -259
  703. package/examples/js/physics/OimoPhysics.js +0 -217
  704. package/examples/js/postprocessing/AdaptiveToneMappingPass.js +0 -335
  705. package/examples/js/postprocessing/AfterimagePass.js +0 -77
  706. package/examples/js/postprocessing/BloomPass.js +0 -135
  707. package/examples/js/postprocessing/BokehPass.js +0 -120
  708. package/examples/js/postprocessing/ClearPass.js +0 -39
  709. package/examples/js/postprocessing/CubeTexturePass.js +0 -60
  710. package/examples/js/postprocessing/DotScreenPass.js +0 -51
  711. package/examples/js/postprocessing/EffectComposer.js +0 -272
  712. package/examples/js/postprocessing/FilmPass.js +0 -52
  713. package/examples/js/postprocessing/GlitchPass.js +0 -104
  714. package/examples/js/postprocessing/HalftonePass.js +0 -75
  715. package/examples/js/postprocessing/LUTPass.js +0 -171
  716. package/examples/js/postprocessing/MaskPass.js +0 -95
  717. package/examples/js/postprocessing/OutlinePass.js +0 -598
  718. package/examples/js/postprocessing/Pass.js +0 -72
  719. package/examples/js/postprocessing/RenderPass.js +0 -70
  720. package/examples/js/postprocessing/RenderPixelatedPass.js +0 -215
  721. package/examples/js/postprocessing/SAOPass.js +0 -374
  722. package/examples/js/postprocessing/SMAAPass.js +0 -170
  723. package/examples/js/postprocessing/SSAARenderPass.js +0 -156
  724. package/examples/js/postprocessing/SSAOPass.js +0 -365
  725. package/examples/js/postprocessing/SSRPass.js +0 -567
  726. package/examples/js/postprocessing/SavePass.js +0 -59
  727. package/examples/js/postprocessing/ShaderPass.js +0 -64
  728. package/examples/js/postprocessing/TAARenderPass.js +0 -130
  729. package/examples/js/postprocessing/TexturePass.js +0 -49
  730. package/examples/js/postprocessing/UnrealBloomPass.js +0 -375
  731. package/examples/js/renderers/CSS2DRenderer.js +0 -178
  732. package/examples/js/renderers/CSS3DRenderer.js +0 -237
  733. package/examples/js/renderers/Projector.js +0 -818
  734. package/examples/js/renderers/SVGRenderer.js +0 -491
  735. package/examples/js/shaders/ACESFilmicToneMappingShader.js +0 -89
  736. package/examples/js/shaders/AfterimageShader.js +0 -60
  737. package/examples/js/shaders/BasicShader.js +0 -27
  738. package/examples/js/shaders/BleachBypassShader.js +0 -62
  739. package/examples/js/shaders/BlendShader.js +0 -54
  740. package/examples/js/shaders/BokehShader.js +0 -156
  741. package/examples/js/shaders/BokehShader2.js +0 -419
  742. package/examples/js/shaders/BrightnessContrastShader.js +0 -58
  743. package/examples/js/shaders/ColorCorrectionShader.js +0 -52
  744. package/examples/js/shaders/ColorifyShader.js +0 -47
  745. package/examples/js/shaders/ConvolutionShader.js +0 -92
  746. package/examples/js/shaders/CopyShader.js +0 -45
  747. package/examples/js/shaders/DOFMipMapShader.js +0 -60
  748. package/examples/js/shaders/DepthLimitedBlurShader.js +0 -173
  749. package/examples/js/shaders/DigitalGlitch.js +0 -127
  750. package/examples/js/shaders/DotScreenShader.js +0 -72
  751. package/examples/js/shaders/FXAAShader.js +0 -284
  752. package/examples/js/shaders/FilmShader.js +0 -110
  753. package/examples/js/shaders/FocusShader.js +0 -95
  754. package/examples/js/shaders/FreiChenShader.js +0 -93
  755. package/examples/js/shaders/GammaCorrectionShader.js +0 -41
  756. package/examples/js/shaders/GodRaysShader.js +0 -284
  757. package/examples/js/shaders/HalftoneShader.js +0 -336
  758. package/examples/js/shaders/HorizontalBlurShader.js +0 -59
  759. package/examples/js/shaders/HorizontalTiltShiftShader.js +0 -65
  760. package/examples/js/shaders/HueSaturationShader.js +0 -69
  761. package/examples/js/shaders/KaleidoShader.js +0 -60
  762. package/examples/js/shaders/LuminosityHighPassShader.js +0 -67
  763. package/examples/js/shaders/LuminosityShader.js +0 -46
  764. package/examples/js/shaders/MMDToonShader.js +0 -94
  765. package/examples/js/shaders/MirrorShader.js +0 -56
  766. package/examples/js/shaders/NormalMapShader.js +0 -55
  767. package/examples/js/shaders/RGBShiftShader.js +0 -56
  768. package/examples/js/shaders/SAOShader.js +0 -209
  769. package/examples/js/shaders/SMAAShader.js +0 -454
  770. package/examples/js/shaders/SSAOShader.js +0 -295
  771. package/examples/js/shaders/SSRShader.js +0 -381
  772. package/examples/js/shaders/SepiaShader.js +0 -52
  773. package/examples/js/shaders/SobelOperatorShader.js +0 -88
  774. package/examples/js/shaders/SubsurfaceScatteringShader.js +0 -49
  775. package/examples/js/shaders/TechnicolorShader.js +0 -43
  776. package/examples/js/shaders/ToneMapShader.js +0 -84
  777. package/examples/js/shaders/ToonShader.js +0 -335
  778. package/examples/js/shaders/TriangleBlurShader.js +0 -70
  779. package/examples/js/shaders/UnpackDepthRGBAShader.js +0 -47
  780. package/examples/js/shaders/VelocityShader.js +0 -126
  781. package/examples/js/shaders/VerticalBlurShader.js +0 -59
  782. package/examples/js/shaders/VerticalTiltShiftShader.js +0 -65
  783. package/examples/js/shaders/VignetteShader.js +0 -53
  784. package/examples/js/shaders/VolumeShader.js +0 -296
  785. package/examples/js/shaders/WaterRefractionShader.js +0 -84
  786. package/examples/js/textures/FlakesTexture.js +0 -40
  787. package/examples/js/utils/BufferGeometryUtils.js +0 -1160
  788. package/examples/js/utils/CameraUtils.js +0 -71
  789. package/examples/js/utils/GPUStatsPanel.js +0 -125
  790. package/examples/js/utils/GeometryCompressionUtils.js +0 -549
  791. package/examples/js/utils/GeometryUtils.js +0 -168
  792. package/examples/js/utils/LDrawUtils.js +0 -179
  793. package/examples/js/utils/PackedPhongMaterial.js +0 -109
  794. package/examples/js/utils/SceneUtils.js +0 -214
  795. package/examples/js/utils/ShadowMapViewer.js +0 -183
  796. package/examples/js/utils/SkeletonUtils.js +0 -493
  797. package/examples/js/utils/UVsDebug.js +0 -143
  798. package/examples/js/utils/WorkerPool.js +0 -105
  799. package/examples/jsm/exporters/ColladaExporter.js +0 -713
  800. package/examples/jsm/geometries/LightningStrike.js +0 -1017
  801. package/examples/jsm/libs/OimoPhysics/OimoPhysics.js +0 -37071
  802. package/examples/jsm/libs/OimoPhysics/index.js +0 -43
  803. package/examples/jsm/libs/flow.module.js +0 -4552
  804. package/examples/jsm/libs/tween.module.min.js +0 -3
  805. package/examples/jsm/loaders/BasisTextureLoader.js +0 -790
  806. package/examples/jsm/loaders/IFCLoader.js +0 -2431
  807. package/examples/jsm/loaders/PRWMLoader.js +0 -299
  808. package/examples/jsm/loaders/ifc/web-ifc-api.js +0 -47504
  809. package/examples/jsm/loaders/ifc/web-ifc.wasm +0 -0
  810. package/examples/jsm/node-editor/NodeEditor.js +0 -857
  811. package/examples/jsm/node-editor/accessors/MatcapUVEditor.js +0 -14
  812. package/examples/jsm/node-editor/accessors/NormalEditor.js +0 -30
  813. package/examples/jsm/node-editor/accessors/PositionEditor.js +0 -30
  814. package/examples/jsm/node-editor/accessors/UVEditor.js +0 -25
  815. package/examples/jsm/node-editor/core/BaseNode.js +0 -96
  816. package/examples/jsm/node-editor/core/DataFile.js +0 -59
  817. package/examples/jsm/node-editor/core/FileEditor.js +0 -20
  818. package/examples/jsm/node-editor/core/FileURLEditor.js +0 -29
  819. package/examples/jsm/node-editor/display/BlendEditor.js +0 -44
  820. package/examples/jsm/node-editor/display/NormalMapEditor.js +0 -49
  821. package/examples/jsm/node-editor/examples/animate-uv.json +0 -1
  822. package/examples/jsm/node-editor/examples/fake-top-light.json +0 -1
  823. package/examples/jsm/node-editor/examples/matcap.json +0 -1
  824. package/examples/jsm/node-editor/examples/oscillator-color.json +0 -1
  825. package/examples/jsm/node-editor/examples/rim.json +0 -1
  826. package/examples/jsm/node-editor/inputs/ColorEditor.js +0 -96
  827. package/examples/jsm/node-editor/inputs/FloatEditor.js +0 -23
  828. package/examples/jsm/node-editor/inputs/SliderEditor.js +0 -67
  829. package/examples/jsm/node-editor/inputs/TextureEditor.js +0 -155
  830. package/examples/jsm/node-editor/inputs/Vector2Editor.js +0 -28
  831. package/examples/jsm/node-editor/inputs/Vector3Editor.js +0 -30
  832. package/examples/jsm/node-editor/inputs/Vector4Editor.js +0 -37
  833. package/examples/jsm/node-editor/materials/BasicMaterialEditor.js +0 -84
  834. package/examples/jsm/node-editor/materials/PointsMaterialEditor.js +0 -102
  835. package/examples/jsm/node-editor/materials/StandardMaterialEditor.js +0 -118
  836. package/examples/jsm/node-editor/math/AngleEditor.js +0 -40
  837. package/examples/jsm/node-editor/math/DotEditor.js +0 -35
  838. package/examples/jsm/node-editor/math/InvertEditor.js +0 -39
  839. package/examples/jsm/node-editor/math/LimiterEditor.js +0 -62
  840. package/examples/jsm/node-editor/math/NormalizeEditor.js +0 -28
  841. package/examples/jsm/node-editor/math/OperatorEditor.js +0 -63
  842. package/examples/jsm/node-editor/math/PowerEditor.js +0 -44
  843. package/examples/jsm/node-editor/math/TrigonometryEditor.js +0 -45
  844. package/examples/jsm/node-editor/procedural/CheckerEditor.js +0 -27
  845. package/examples/jsm/node-editor/scene/MeshEditor.js +0 -102
  846. package/examples/jsm/node-editor/scene/Object3DEditor.js +0 -160
  847. package/examples/jsm/node-editor/scene/PointsEditor.js +0 -99
  848. package/examples/jsm/node-editor/utils/JoinEditor.js +0 -58
  849. package/examples/jsm/node-editor/utils/OscillatorEditor.js +0 -43
  850. package/examples/jsm/node-editor/utils/PreviewEditor.js +0 -170
  851. package/examples/jsm/node-editor/utils/SplitEditor.js +0 -39
  852. package/examples/jsm/node-editor/utils/TimerEditor.js +0 -58
  853. package/examples/jsm/nodes/core/CodeNode.js +0 -50
  854. package/examples/jsm/nodes/core/ExpressionNode.js +0 -32
  855. package/examples/jsm/nodes/core/InstanceIndexNode.js +0 -21
  856. package/examples/jsm/nodes/functions/light/getDistanceAttenuation.js +0 -22
  857. package/examples/jsm/nodes/lighting/PunctualLightNode.js +0 -68
  858. package/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +0 -301
  859. package/examples/jsm/nodes/shadernode/ShaderNodeElements.js +0 -149
  860. package/examples/jsm/objects/GroundProjectedEnv.js +0 -186
  861. package/examples/jsm/objects/LightningStorm.js +0 -245
  862. package/examples/jsm/physics/OimoPhysics.js +0 -231
  863. package/examples/jsm/postprocessing/AdaptiveToneMappingPass.js +0 -369
  864. package/examples/jsm/renderers/webgpu/WebGPUAttributes.js +0 -187
  865. package/examples/jsm/renderers/webgpu/WebGPUBackground.js +0 -173
  866. package/examples/jsm/renderers/webgpu/WebGPUBinding.js +0 -22
  867. package/examples/jsm/renderers/webgpu/WebGPUBindings.js +0 -255
  868. package/examples/jsm/renderers/webgpu/WebGPUBuffer.js +0 -43
  869. package/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js +0 -78
  870. package/examples/jsm/renderers/webgpu/WebGPUGeometries.js +0 -82
  871. package/examples/jsm/renderers/webgpu/WebGPUObjects.js +0 -36
  872. package/examples/jsm/renderers/webgpu/WebGPUProgrammableStage.js +0 -22
  873. package/examples/jsm/renderers/webgpu/WebGPUProperties.js +0 -38
  874. package/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js +0 -296
  875. package/examples/jsm/renderers/webgpu/WebGPURenderStates.js +0 -66
  876. package/examples/jsm/renderers/webgpu/WebGPUSampledTexture.js +0 -73
  877. package/examples/jsm/renderers/webgpu/WebGPUSampler.js +0 -29
  878. package/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js +0 -20
  879. package/examples/jsm/renderers/webgpu/WebGPUTextureRenderer.js +0 -40
  880. package/examples/jsm/renderers/webgpu/WebGPUTextures.js +0 -794
  881. package/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js +0 -18
  882. package/examples/jsm/renderers/webgpu/WebGPUUtils.js +0 -81
  883. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js +0 -83
  884. package/examples/jsm/shaders/ToneMapShader.js +0 -73
  885. package/src/renderers/shaders/ShaderChunk/uv2_pars_fragment.glsl.js +0 -7
  886. package/src/renderers/shaders/ShaderChunk/uv2_pars_vertex.glsl.js +0 -10
  887. package/src/renderers/shaders/ShaderChunk/uv2_vertex.glsl.js +0 -7
  888. /package/examples/{js → jsm}/libs/ammo.wasm.js +0 -0
  889. /package/examples/{js → jsm}/libs/ammo.wasm.wasm +0 -0
  890. /package/examples/{js → jsm}/libs/basis/basis_transcoder.js +0 -0
  891. /package/examples/{js → jsm}/libs/basis/basis_transcoder.wasm +0 -0
  892. /package/examples/{js → jsm}/libs/draco/README.md +0 -0
  893. /package/examples/{js → jsm}/libs/draco/draco_encoder.js +0 -0
  894. /package/examples/{js → jsm}/libs/draco/gltf/draco_encoder.js +0 -0
  895. /package/src/renderers/shaders/ShaderChunk/{encodings_fragment.glsl.js → colorspace_fragment.glsl.js} +0 -0
  896. /package/src/renderers/shaders/ShaderChunk/{encodings_pars_fragment.glsl.js → colorspace_pars_fragment.glsl.js} +0 -0
@@ -199,6 +199,7 @@ class ArcballControls extends EventDispatcher {
199
199
  this.cursorZoom = false; //if wheel zoom should be cursor centered
200
200
  this.minFov = 5;
201
201
  this.maxFov = 90;
202
+ this.rotateSpeed = 1;
202
203
 
203
204
  this.enabled = true;
204
205
  this.enablePan = true;
@@ -230,2986 +231,2993 @@ class ArcballControls extends EventDispatcher {
230
231
 
231
232
  this.initializeMouseActions();
232
233
 
233
- this.domElement.addEventListener( 'contextmenu', this.onContextMenu );
234
- this.domElement.addEventListener( 'wheel', this.onWheel );
235
- this.domElement.addEventListener( 'pointerdown', this.onPointerDown );
236
- this.domElement.addEventListener( 'pointercancel', this.onPointerCancel );
234
+ this._onContextMenu = onContextMenu.bind( this );
235
+ this._onWheel = onWheel.bind( this );
236
+ this._onPointerUp = onPointerUp.bind( this );
237
+ this._onPointerMove = onPointerMove.bind( this );
238
+ this._onPointerDown = onPointerDown.bind( this );
239
+ this._onPointerCancel = onPointerCancel.bind( this );
240
+ this._onWindowResize = onWindowResize.bind( this );
237
241
 
238
- window.addEventListener( 'resize', this.onWindowResize );
242
+ this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
243
+ this.domElement.addEventListener( 'wheel', this._onWheel );
244
+ this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
245
+ this.domElement.addEventListener( 'pointercancel', this._onPointerCancel );
239
246
 
240
- }
241
-
242
- //listeners
243
-
244
- onWindowResize = () => {
245
-
246
- const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
247
- this._tbRadius = this.calculateTbRadius( this.camera );
248
-
249
- const newRadius = this._tbRadius / scale;
250
- const curve = new EllipseCurve( 0, 0, newRadius, newRadius );
251
- const points = curve.getPoints( this._curvePts );
252
- const curveGeometry = new BufferGeometry().setFromPoints( points );
247
+ window.addEventListener( 'resize', this._onWindowResize );
253
248
 
249
+ }
254
250
 
255
- for ( const gizmo in this._gizmos.children ) {
251
+ onSinglePanStart( event, operation ) {
256
252
 
257
- this._gizmos.children[ gizmo ].geometry = curveGeometry;
253
+ if ( this.enabled ) {
258
254
 
259
- }
255
+ this.dispatchEvent( _startEvent );
260
256
 
261
- this.dispatchEvent( _changeEvent );
257
+ this.setCenter( event.clientX, event.clientY );
262
258
 
263
- };
259
+ switch ( operation ) {
264
260
 
265
- onContextMenu = ( event ) => {
261
+ case 'PAN':
266
262
 
267
- if ( ! this.enabled ) {
263
+ if ( ! this.enablePan ) {
268
264
 
269
- return;
265
+ return;
270
266
 
271
- }
267
+ }
272
268
 
273
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
269
+ if ( this._animationId != - 1 ) {
274
270
 
275
- if ( this.mouseActions[ i ].mouse == 2 ) {
271
+ cancelAnimationFrame( this._animationId );
272
+ this._animationId = - 1;
273
+ this._timeStart = - 1;
276
274
 
277
- //prevent only if button 2 is actually used
278
- event.preventDefault();
279
- break;
275
+ this.activateGizmos( false );
276
+ this.dispatchEvent( _changeEvent );
280
277
 
281
- }
278
+ }
282
279
 
283
- }
280
+ this.updateTbState( STATE.PAN, true );
281
+ this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
282
+ if ( this.enableGrid ) {
284
283
 
285
- };
284
+ this.drawGrid();
285
+ this.dispatchEvent( _changeEvent );
286
286
 
287
- onPointerCancel = () => {
287
+ }
288
288
 
289
- this._touchStart.splice( 0, this._touchStart.length );
290
- this._touchCurrent.splice( 0, this._touchCurrent.length );
291
- this._input = INPUT.NONE;
289
+ break;
292
290
 
293
- };
291
+ case 'ROTATE':
294
292
 
295
- onPointerDown = ( event ) => {
293
+ if ( ! this.enableRotate ) {
296
294
 
297
- if ( event.button == 0 && event.isPrimary ) {
295
+ return;
298
296
 
299
- this._downValid = true;
300
- this._downEvents.push( event );
301
- this._downStart = performance.now();
297
+ }
302
298
 
303
- } else {
299
+ if ( this._animationId != - 1 ) {
304
300
 
305
- this._downValid = false;
301
+ cancelAnimationFrame( this._animationId );
302
+ this._animationId = - 1;
303
+ this._timeStart = - 1;
306
304
 
307
- }
305
+ }
308
306
 
309
- if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
307
+ this.updateTbState( STATE.ROTATE, true );
308
+ this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
309
+ this.activateGizmos( true );
310
+ if ( this.enableAnimations ) {
310
311
 
311
- this._touchStart.push( event );
312
- this._touchCurrent.push( event );
312
+ this._timePrev = this._timeCurrent = performance.now();
313
+ this._angleCurrent = this._anglePrev = 0;
314
+ this._cursorPosPrev.copy( this._startCursorPosition );
315
+ this._cursorPosCurr.copy( this._cursorPosPrev );
316
+ this._wCurr = 0;
317
+ this._wPrev = this._wCurr;
313
318
 
314
- switch ( this._input ) {
319
+ }
315
320
 
316
- case INPUT.NONE:
321
+ this.dispatchEvent( _changeEvent );
322
+ break;
317
323
 
318
- //singleStart
319
- this._input = INPUT.ONE_FINGER;
320
- this.onSinglePanStart( event, 'ROTATE' );
324
+ case 'FOV':
321
325
 
322
- window.addEventListener( 'pointermove', this.onPointerMove );
323
- window.addEventListener( 'pointerup', this.onPointerUp );
326
+ if ( ! this.camera.isPerspectiveCamera || ! this.enableZoom ) {
324
327
 
325
- break;
328
+ return;
326
329
 
327
- case INPUT.ONE_FINGER:
328
- case INPUT.ONE_FINGER_SWITCHED:
330
+ }
329
331
 
330
- //doubleStart
331
- this._input = INPUT.TWO_FINGER;
332
+ if ( this._animationId != - 1 ) {
332
333
 
333
- this.onRotateStart();
334
- this.onPinchStart();
335
- this.onDoublePanStart();
334
+ cancelAnimationFrame( this._animationId );
335
+ this._animationId = - 1;
336
+ this._timeStart = - 1;
336
337
 
337
- break;
338
+ this.activateGizmos( false );
339
+ this.dispatchEvent( _changeEvent );
338
340
 
339
- case INPUT.TWO_FINGER:
341
+ }
340
342
 
341
- //multipleStart
342
- this._input = INPUT.MULT_FINGER;
343
- this.onTriplePanStart( event );
343
+ this.updateTbState( STATE.FOV, true );
344
+ this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
345
+ this._currentCursorPosition.copy( this._startCursorPosition );
344
346
  break;
345
347
 
346
- }
347
-
348
- } else if ( event.pointerType != 'touch' && this._input == INPUT.NONE ) {
349
-
350
- let modifier = null;
348
+ case 'ZOOM':
351
349
 
352
- if ( event.ctrlKey || event.metaKey ) {
350
+ if ( ! this.enableZoom ) {
353
351
 
354
- modifier = 'CTRL';
352
+ return;
355
353
 
356
- } else if ( event.shiftKey ) {
354
+ }
357
355
 
358
- modifier = 'SHIFT';
356
+ if ( this._animationId != - 1 ) {
359
357
 
360
- }
358
+ cancelAnimationFrame( this._animationId );
359
+ this._animationId = - 1;
360
+ this._timeStart = - 1;
361
361
 
362
- this._mouseOp = this.getOpFromAction( event.button, modifier );
363
- if ( this._mouseOp != null ) {
362
+ this.activateGizmos( false );
363
+ this.dispatchEvent( _changeEvent );
364
364
 
365
- window.addEventListener( 'pointermove', this.onPointerMove );
366
- window.addEventListener( 'pointerup', this.onPointerUp );
365
+ }
367
366
 
368
- //singleStart
369
- this._input = INPUT.CURSOR;
370
- this._button = event.button;
371
- this.onSinglePanStart( event, this._mouseOp );
367
+ this.updateTbState( STATE.SCALE, true );
368
+ this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
369
+ this._currentCursorPosition.copy( this._startCursorPosition );
370
+ break;
372
371
 
373
372
  }
374
373
 
375
374
  }
376
375
 
377
- };
376
+ }
378
377
 
379
- onPointerMove = ( event ) => {
378
+ onSinglePanMove( event, opState ) {
380
379
 
381
- if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
380
+ if ( this.enabled ) {
382
381
 
383
- switch ( this._input ) {
382
+ const restart = opState != this._state;
383
+ this.setCenter( event.clientX, event.clientY );
384
384
 
385
- case INPUT.ONE_FINGER:
385
+ switch ( opState ) {
386
386
 
387
- //singleMove
388
- this.updateTouchEvent( event );
387
+ case STATE.PAN:
389
388
 
390
- this.onSinglePanMove( event, STATE.ROTATE );
391
- break;
389
+ if ( this.enablePan ) {
392
390
 
393
- case INPUT.ONE_FINGER_SWITCHED:
391
+ if ( restart ) {
394
392
 
395
- const movement = this.calculatePointersDistance( this._touchCurrent[ 0 ], event ) * this._devPxRatio;
393
+ //switch to pan operation
396
394
 
397
- if ( movement >= this._switchSensibility ) {
395
+ this.dispatchEvent( _endEvent );
396
+ this.dispatchEvent( _startEvent );
398
397
 
399
- //singleMove
400
- this._input = INPUT.ONE_FINGER;
401
- this.updateTouchEvent( event );
398
+ this.updateTbState( opState, true );
399
+ this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
400
+ if ( this.enableGrid ) {
402
401
 
403
- this.onSinglePanStart( event, 'ROTATE' );
404
- break;
402
+ this.drawGrid();
405
403
 
406
- }
404
+ }
407
405
 
408
- break;
406
+ this.activateGizmos( false );
409
407
 
410
- case INPUT.TWO_FINGER:
408
+ } else {
411
409
 
412
- //rotate/pan/pinchMove
413
- this.updateTouchEvent( event );
410
+ //continue with pan operation
411
+ this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
412
+ this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) );
413
+
414
+ }
414
415
 
415
- this.onRotateMove();
416
- this.onPinchMove();
417
- this.onDoublePanMove();
416
+ }
418
417
 
419
418
  break;
420
419
 
421
- case INPUT.MULT_FINGER:
422
-
423
- //multMove
424
- this.updateTouchEvent( event );
420
+ case STATE.ROTATE:
425
421
 
426
- this.onTriplePanMove( event );
427
- break;
422
+ if ( this.enableRotate ) {
428
423
 
429
- }
424
+ if ( restart ) {
430
425
 
431
- } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
426
+ //switch to rotate operation
432
427
 
433
- let modifier = null;
428
+ this.dispatchEvent( _endEvent );
429
+ this.dispatchEvent( _startEvent );
434
430
 
435
- if ( event.ctrlKey || event.metaKey ) {
431
+ this.updateTbState( opState, true );
432
+ this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
436
433
 
437
- modifier = 'CTRL';
434
+ if ( this.enableGrid ) {
438
435
 
439
- } else if ( event.shiftKey ) {
436
+ this.disposeGrid();
440
437
 
441
- modifier = 'SHIFT';
438
+ }
442
439
 
443
- }
440
+ this.activateGizmos( true );
444
441
 
445
- const mouseOpState = this.getOpStateFromAction( this._button, modifier );
442
+ } else {
446
443
 
447
- if ( mouseOpState != null ) {
444
+ //continue with rotate operation
445
+ this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
448
446
 
449
- this.onSinglePanMove( event, mouseOpState );
447
+ const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition );
448
+ const angle = this._startCursorPosition.angleTo( this._currentCursorPosition );
449
+ const amount = Math.max( distance / this._tbRadius, angle ) * this.rotateSpeed; //effective rotation angle
450
450
 
451
- }
451
+ this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) );
452
452
 
453
- }
453
+ if ( this.enableAnimations ) {
454
454
 
455
- //checkDistance
456
- if ( this._downValid ) {
455
+ this._timePrev = this._timeCurrent;
456
+ this._timeCurrent = performance.now();
457
+ this._anglePrev = this._angleCurrent;
458
+ this._angleCurrent = amount;
459
+ this._cursorPosPrev.copy( this._cursorPosCurr );
460
+ this._cursorPosCurr.copy( this._currentCursorPosition );
461
+ this._wPrev = this._wCurr;
462
+ this._wCurr = this.calculateAngularSpeed( this._anglePrev, this._angleCurrent, this._timePrev, this._timeCurrent );
457
463
 
458
- const movement = this.calculatePointersDistance( this._downEvents[ this._downEvents.length - 1 ], event ) * this._devPxRatio;
459
- if ( movement > this._movementThreshold ) {
464
+ }
460
465
 
461
- this._downValid = false;
466
+ }
462
467
 
463
- }
468
+ }
464
469
 
465
- }
470
+ break;
466
471
 
467
- };
472
+ case STATE.SCALE:
468
473
 
469
- onPointerUp = ( event ) => {
474
+ if ( this.enableZoom ) {
470
475
 
471
- if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
476
+ if ( restart ) {
472
477
 
473
- const nTouch = this._touchCurrent.length;
478
+ //switch to zoom operation
474
479
 
475
- for ( let i = 0; i < nTouch; i ++ ) {
480
+ this.dispatchEvent( _endEvent );
481
+ this.dispatchEvent( _startEvent );
476
482
 
477
- if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
483
+ this.updateTbState( opState, true );
484
+ this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
485
+ this._currentCursorPosition.copy( this._startCursorPosition );
478
486
 
479
- this._touchCurrent.splice( i, 1 );
480
- this._touchStart.splice( i, 1 );
481
- break;
487
+ if ( this.enableGrid ) {
482
488
 
483
- }
489
+ this.disposeGrid();
484
490
 
485
- }
491
+ }
486
492
 
487
- switch ( this._input ) {
493
+ this.activateGizmos( false );
488
494
 
489
- case INPUT.ONE_FINGER:
490
- case INPUT.ONE_FINGER_SWITCHED:
495
+ } else {
491
496
 
492
- //singleEnd
493
- window.removeEventListener( 'pointermove', this.onPointerMove );
494
- window.removeEventListener( 'pointerup', this.onPointerUp );
497
+ //continue with zoom operation
498
+ const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
499
+ this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
495
500
 
496
- this._input = INPUT.NONE;
497
- this.onSinglePanEnd();
501
+ const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
498
502
 
499
- break;
503
+ let size = 1;
500
504
 
501
- case INPUT.TWO_FINGER:
505
+ if ( movement < 0 ) {
502
506
 
503
- //doubleEnd
504
- this.onDoublePanEnd( event );
505
- this.onPinchEnd( event );
506
- this.onRotateEnd( event );
507
+ size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
507
508
 
508
- //switching to singleStart
509
- this._input = INPUT.ONE_FINGER_SWITCHED;
509
+ } else if ( movement > 0 ) {
510
510
 
511
- break;
511
+ size = Math.pow( this.scaleFactor, movement * screenNotches );
512
512
 
513
- case INPUT.MULT_FINGER:
513
+ }
514
514
 
515
- if ( this._touchCurrent.length == 0 ) {
515
+ this._v3_1.setFromMatrixPosition( this._gizmoMatrixState );
516
516
 
517
- window.removeEventListener( 'pointermove', this.onPointerMove );
518
- window.removeEventListener( 'pointerup', this.onPointerUp );
517
+ this.applyTransformMatrix( this.scale( size, this._v3_1 ) );
519
518
 
520
- //multCancel
521
- this._input = INPUT.NONE;
522
- this.onTriplePanEnd();
519
+ }
523
520
 
524
521
  }
525
522
 
526
523
  break;
527
524
 
528
- }
525
+ case STATE.FOV:
529
526
 
530
- } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
527
+ if ( this.enableZoom && this.camera.isPerspectiveCamera ) {
531
528
 
532
- window.removeEventListener( 'pointermove', this.onPointerMove );
533
- window.removeEventListener( 'pointerup', this.onPointerUp );
529
+ if ( restart ) {
534
530
 
535
- this._input = INPUT.NONE;
536
- this.onSinglePanEnd();
537
- this._button = - 1;
531
+ //switch to fov operation
538
532
 
539
- }
533
+ this.dispatchEvent( _endEvent );
534
+ this.dispatchEvent( _startEvent );
540
535
 
541
- if ( event.isPrimary ) {
536
+ this.updateTbState( opState, true );
537
+ this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
538
+ this._currentCursorPosition.copy( this._startCursorPosition );
542
539
 
543
- if ( this._downValid ) {
540
+ if ( this.enableGrid ) {
544
541
 
545
- const downTime = event.timeStamp - this._downEvents[ this._downEvents.length - 1 ].timeStamp;
542
+ this.disposeGrid();
546
543
 
547
- if ( downTime <= this._maxDownTime ) {
544
+ }
548
545
 
549
- if ( this._nclicks == 0 ) {
546
+ this.activateGizmos( false );
550
547
 
551
- //first valid click detected
552
- this._nclicks = 1;
553
- this._clickStart = performance.now();
548
+ } else {
554
549
 
555
- } else {
550
+ //continue with fov operation
551
+ const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
552
+ this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
556
553
 
557
- const clickInterval = event.timeStamp - this._clickStart;
558
- const movement = this.calculatePointersDistance( this._downEvents[ 1 ], this._downEvents[ 0 ] ) * this._devPxRatio;
554
+ const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
559
555
 
560
- if ( clickInterval <= this._maxInterval && movement <= this._posThreshold ) {
556
+ let size = 1;
561
557
 
562
- //second valid click detected
563
- //fire double tap and reset values
564
- this._nclicks = 0;
565
- this._downEvents.splice( 0, this._downEvents.length );
566
- this.onDoubleTap( event );
558
+ if ( movement < 0 ) {
567
559
 
568
- } else {
560
+ size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
569
561
 
570
- //new 'first click'
571
- this._nclicks = 1;
572
- this._downEvents.shift();
573
- this._clickStart = performance.now();
562
+ } else if ( movement > 0 ) {
574
563
 
575
- }
564
+ size = Math.pow( this.scaleFactor, movement * screenNotches );
576
565
 
577
- }
566
+ }
578
567
 
579
- } else {
568
+ this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
569
+ const x = this._v3_1.distanceTo( this._gizmos.position );
570
+ let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
580
571
 
581
- this._downValid = false;
582
- this._nclicks = 0;
583
- this._downEvents.splice( 0, this._downEvents.length );
572
+ //check min and max distance
573
+ xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
584
574
 
585
- }
575
+ const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 );
586
576
 
587
- } else {
577
+ //calculate new fov
578
+ let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
588
579
 
589
- this._nclicks = 0;
590
- this._downEvents.splice( 0, this._downEvents.length );
580
+ //check min and max fov
581
+ newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov );
591
582
 
592
- }
583
+ const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
584
+ size = x / newDistance;
585
+ this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
593
586
 
594
- }
587
+ this.setFov( newFov );
588
+ this.applyTransformMatrix( this.scale( size, this._v3_2, false ) );
589
+
590
+ //adjusting distance
591
+ _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x );
592
+ this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
593
+
594
+ }
595
595
 
596
- };
596
+ }
597
597
 
598
- onWheel = ( event ) => {
598
+ break;
599
599
 
600
- if ( this.enabled && this.enableZoom ) {
600
+ }
601
+
602
+ this.dispatchEvent( _changeEvent );
603
+
604
+ }
605
+
606
+ }
601
607
 
602
- let modifier = null;
608
+ onSinglePanEnd() {
603
609
 
604
- if ( event.ctrlKey || event.metaKey ) {
610
+ if ( this._state == STATE.ROTATE ) {
605
611
 
606
- modifier = 'CTRL';
607
612
 
608
- } else if ( event.shiftKey ) {
613
+ if ( ! this.enableRotate ) {
609
614
 
610
- modifier = 'SHIFT';
615
+ return;
611
616
 
612
617
  }
613
618
 
614
- const mouseOp = this.getOpFromAction( 'WHEEL', modifier );
619
+ if ( this.enableAnimations ) {
615
620
 
616
- if ( mouseOp != null ) {
621
+ //perform rotation animation
622
+ const deltaTime = ( performance.now() - this._timeCurrent );
623
+ if ( deltaTime < 120 ) {
617
624
 
618
- event.preventDefault();
619
- this.dispatchEvent( _startEvent );
625
+ const w = Math.abs( ( this._wPrev + this._wCurr ) / 2 );
620
626
 
621
- const notchDeltaY = 125; //distance of one notch of mouse wheel
622
- let sgn = event.deltaY / notchDeltaY;
627
+ const self = this;
628
+ this._animationId = window.requestAnimationFrame( function ( t ) {
623
629
 
624
- let size = 1;
630
+ self.updateTbState( STATE.ANIMATION_ROTATE, true );
631
+ const rotationAxis = self.calculateRotationAxis( self._cursorPosPrev, self._cursorPosCurr );
625
632
 
626
- if ( sgn > 0 ) {
633
+ self.onRotationAnim( t, rotationAxis, Math.min( w, self.wMax ) );
627
634
 
628
- size = 1 / this.scaleFactor;
635
+ } );
629
636
 
630
- } else if ( sgn < 0 ) {
637
+ } else {
631
638
 
632
- size = this.scaleFactor;
639
+ //cursor has been standing still for over 120 ms since last movement
640
+ this.updateTbState( STATE.IDLE, false );
641
+ this.activateGizmos( false );
642
+ this.dispatchEvent( _changeEvent );
633
643
 
634
644
  }
635
645
 
636
- switch ( mouseOp ) {
646
+ } else {
637
647
 
638
- case 'ZOOM':
648
+ this.updateTbState( STATE.IDLE, false );
649
+ this.activateGizmos( false );
650
+ this.dispatchEvent( _changeEvent );
639
651
 
640
- this.updateTbState( STATE.SCALE, true );
652
+ }
641
653
 
642
- if ( sgn > 0 ) {
654
+ } else if ( this._state == STATE.PAN || this._state == STATE.IDLE ) {
643
655
 
644
- size = 1 / ( Math.pow( this.scaleFactor, sgn ) );
656
+ this.updateTbState( STATE.IDLE, false );
645
657
 
646
- } else if ( sgn < 0 ) {
658
+ if ( this.enableGrid ) {
647
659
 
648
- size = Math.pow( this.scaleFactor, - sgn );
660
+ this.disposeGrid();
649
661
 
650
- }
662
+ }
651
663
 
652
- if ( this.cursorZoom && this.enablePan ) {
664
+ this.activateGizmos( false );
665
+ this.dispatchEvent( _changeEvent );
653
666
 
654
- let scalePoint;
655
667
 
656
- if ( this.camera.isOrthographicCamera ) {
668
+ }
657
669
 
658
- scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position );
670
+ this.dispatchEvent( _endEvent );
659
671
 
660
- } else if ( this.camera.isPerspectiveCamera ) {
672
+ }
661
673
 
662
- scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position );
674
+ onDoubleTap( event ) {
663
675
 
664
- }
676
+ if ( this.enabled && this.enablePan && this.scene != null ) {
665
677
 
666
- this.applyTransformMatrix( this.scale( size, scalePoint ) );
678
+ this.dispatchEvent( _startEvent );
667
679
 
668
- } else {
680
+ this.setCenter( event.clientX, event.clientY );
681
+ const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.camera );
669
682
 
670
- this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
683
+ if ( hitP != null && this.enableAnimations ) {
671
684
 
672
- }
685
+ const self = this;
686
+ if ( this._animationId != - 1 ) {
673
687
 
674
- if ( this._grid != null ) {
688
+ window.cancelAnimationFrame( this._animationId );
675
689
 
676
- this.disposeGrid();
677
- this.drawGrid();
690
+ }
678
691
 
679
- }
692
+ this._timeStart = - 1;
693
+ this._animationId = window.requestAnimationFrame( function ( t ) {
680
694
 
681
- this.updateTbState( STATE.IDLE, false );
695
+ self.updateTbState( STATE.ANIMATION_FOCUS, true );
696
+ self.onFocusAnim( t, hitP, self._cameraMatrixState, self._gizmoMatrixState );
682
697
 
683
- this.dispatchEvent( _changeEvent );
684
- this.dispatchEvent( _endEvent );
698
+ } );
685
699
 
686
- break;
700
+ } else if ( hitP != null && ! this.enableAnimations ) {
687
701
 
688
- case 'FOV':
702
+ this.updateTbState( STATE.FOCUS, true );
703
+ this.focus( hitP, this.scaleFactor );
704
+ this.updateTbState( STATE.IDLE, false );
705
+ this.dispatchEvent( _changeEvent );
689
706
 
690
- if ( this.camera.isPerspectiveCamera ) {
707
+ }
691
708
 
692
- this.updateTbState( STATE.FOV, true );
709
+ }
693
710
 
711
+ this.dispatchEvent( _endEvent );
694
712
 
695
- //Vertigo effect
713
+ }
696
714
 
697
- // fov / 2
698
- // |\
699
- // | \
700
- // | \
701
- // x | \
702
- // | \
703
- // | \
704
- // | _ _ _\
705
- // y
715
+ onDoublePanStart() {
706
716
 
707
- //check for iOs shift shortcut
708
- if ( event.deltaX != 0 ) {
717
+ if ( this.enabled && this.enablePan ) {
709
718
 
710
- sgn = event.deltaX / notchDeltaY;
719
+ this.dispatchEvent( _startEvent );
711
720
 
712
- size = 1;
721
+ this.updateTbState( STATE.PAN, true );
713
722
 
714
- if ( sgn > 0 ) {
723
+ this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
724
+ this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) );
725
+ this._currentCursorPosition.copy( this._startCursorPosition );
715
726
 
716
- size = 1 / ( Math.pow( this.scaleFactor, sgn ) );
727
+ this.activateGizmos( false );
717
728
 
718
- } else if ( sgn < 0 ) {
729
+ }
719
730
 
720
- size = Math.pow( this.scaleFactor, - sgn );
731
+ }
721
732
 
722
- }
733
+ onDoublePanMove() {
723
734
 
724
- }
735
+ if ( this.enabled && this.enablePan ) {
725
736
 
726
- this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
727
- const x = this._v3_1.distanceTo( this._gizmos.position );
728
- let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
737
+ this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
729
738
 
730
- //check min and max distance
731
- xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
739
+ if ( this._state != STATE.PAN ) {
732
740
 
733
- const y = x * Math.tan( MathUtils.DEG2RAD * this.camera.fov * 0.5 );
741
+ this.updateTbState( STATE.PAN, true );
742
+ this._startCursorPosition.copy( this._currentCursorPosition );
734
743
 
735
- //calculate new fov
736
- let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
744
+ }
737
745
 
738
- //check min and max fov
739
- if ( newFov > this.maxFov ) {
746
+ this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) );
747
+ this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) );
748
+ this.dispatchEvent( _changeEvent );
740
749
 
741
- newFov = this.maxFov;
750
+ }
742
751
 
743
- } else if ( newFov < this.minFov ) {
752
+ }
744
753
 
745
- newFov = this.minFov;
754
+ onDoublePanEnd() {
746
755
 
747
- }
756
+ this.updateTbState( STATE.IDLE, false );
757
+ this.dispatchEvent( _endEvent );
748
758
 
749
- const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
750
- size = x / newDistance;
759
+ }
751
760
 
752
- this.setFov( newFov );
753
- this.applyTransformMatrix( this.scale( size, this._gizmos.position, false ) );
761
+ onRotateStart() {
754
762
 
755
- }
763
+ if ( this.enabled && this.enableRotate ) {
756
764
 
757
- if ( this._grid != null ) {
765
+ this.dispatchEvent( _startEvent );
758
766
 
759
- this.disposeGrid();
760
- this.drawGrid();
767
+ this.updateTbState( STATE.ZROTATE, true );
761
768
 
762
- }
769
+ //this._startFingerRotation = event.rotation;
763
770
 
764
- this.updateTbState( STATE.IDLE, false );
771
+ this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
772
+ this._currentFingerRotation = this._startFingerRotation;
765
773
 
766
- this.dispatchEvent( _changeEvent );
767
- this.dispatchEvent( _endEvent );
774
+ this.camera.getWorldDirection( this._rotationAxis ); //rotation axis
768
775
 
769
- break;
776
+ if ( ! this.enablePan && ! this.enableZoom ) {
770
777
 
771
- }
778
+ this.activateGizmos( true );
772
779
 
773
780
  }
774
781
 
775
782
  }
776
783
 
777
- };
784
+ }
778
785
 
779
- onSinglePanStart = ( event, operation ) => {
786
+ onRotateMove() {
780
787
 
781
- if ( this.enabled ) {
788
+ if ( this.enabled && this.enableRotate ) {
782
789
 
783
- this.dispatchEvent( _startEvent );
790
+ this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
791
+ let rotationPoint;
784
792
 
785
- this.setCenter( event.clientX, event.clientY );
793
+ if ( this._state != STATE.ZROTATE ) {
786
794
 
787
- switch ( operation ) {
795
+ this.updateTbState( STATE.ZROTATE, true );
796
+ this._startFingerRotation = this._currentFingerRotation;
788
797
 
789
- case 'PAN':
798
+ }
790
799
 
791
- if ( ! this.enablePan ) {
800
+ //this._currentFingerRotation = event.rotation;
801
+ this._currentFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
792
802
 
793
- return;
803
+ if ( ! this.enablePan ) {
794
804
 
795
- }
805
+ rotationPoint = new Vector3().setFromMatrixPosition( this._gizmoMatrixState );
796
806
 
797
- if ( this._animationId != - 1 ) {
807
+ } else {
798
808
 
799
- cancelAnimationFrame( this._animationId );
800
- this._animationId = - 1;
801
- this._timeStart = - 1;
809
+ this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
810
+ rotationPoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._v3_2 );
802
811
 
803
- this.activateGizmos( false );
804
- this.dispatchEvent( _changeEvent );
812
+ }
805
813
 
806
- }
814
+ const amount = MathUtils.DEG2RAD * ( this._startFingerRotation - this._currentFingerRotation );
807
815
 
808
- this.updateTbState( STATE.PAN, true );
809
- this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
810
- if ( this.enableGrid ) {
816
+ this.applyTransformMatrix( this.zRotate( rotationPoint, amount ) );
817
+ this.dispatchEvent( _changeEvent );
811
818
 
812
- this.drawGrid();
813
- this.dispatchEvent( _changeEvent );
819
+ }
814
820
 
815
- }
821
+ }
816
822
 
817
- break;
823
+ onRotateEnd() {
818
824
 
819
- case 'ROTATE':
825
+ this.updateTbState( STATE.IDLE, false );
826
+ this.activateGizmos( false );
827
+ this.dispatchEvent( _endEvent );
820
828
 
821
- if ( ! this.enableRotate ) {
829
+ }
822
830
 
823
- return;
831
+ onPinchStart() {
824
832
 
825
- }
833
+ if ( this.enabled && this.enableZoom ) {
826
834
 
827
- if ( this._animationId != - 1 ) {
835
+ this.dispatchEvent( _startEvent );
836
+ this.updateTbState( STATE.SCALE, true );
828
837
 
829
- cancelAnimationFrame( this._animationId );
830
- this._animationId = - 1;
831
- this._timeStart = - 1;
838
+ this._startFingerDistance = this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] );
839
+ this._currentFingerDistance = this._startFingerDistance;
832
840
 
833
- }
841
+ this.activateGizmos( false );
834
842
 
835
- this.updateTbState( STATE.ROTATE, true );
836
- this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
837
- this.activateGizmos( true );
838
- if ( this.enableAnimations ) {
843
+ }
839
844
 
840
- this._timePrev = this._timeCurrent = performance.now();
841
- this._angleCurrent = this._anglePrev = 0;
842
- this._cursorPosPrev.copy( this._startCursorPosition );
843
- this._cursorPosCurr.copy( this._cursorPosPrev );
844
- this._wCurr = 0;
845
- this._wPrev = this._wCurr;
845
+ }
846
846
 
847
- }
847
+ onPinchMove() {
848
848
 
849
- this.dispatchEvent( _changeEvent );
850
- break;
849
+ if ( this.enabled && this.enableZoom ) {
851
850
 
852
- case 'FOV':
851
+ this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
852
+ const minDistance = 12; //minimum distance between fingers (in css pixels)
853
853
 
854
- if ( ! this.camera.isPerspectiveCamera || ! this.enableZoom ) {
854
+ if ( this._state != STATE.SCALE ) {
855
855
 
856
- return;
856
+ this._startFingerDistance = this._currentFingerDistance;
857
+ this.updateTbState( STATE.SCALE, true );
857
858
 
858
- }
859
+ }
859
860
 
860
- if ( this._animationId != - 1 ) {
861
+ this._currentFingerDistance = Math.max( this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ), minDistance * this._devPxRatio );
862
+ const amount = this._currentFingerDistance / this._startFingerDistance;
861
863
 
862
- cancelAnimationFrame( this._animationId );
863
- this._animationId = - 1;
864
- this._timeStart = - 1;
864
+ let scalePoint;
865
865
 
866
- this.activateGizmos( false );
867
- this.dispatchEvent( _changeEvent );
866
+ if ( ! this.enablePan ) {
868
867
 
869
- }
868
+ scalePoint = this._gizmos.position;
870
869
 
871
- this.updateTbState( STATE.FOV, true );
872
- this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
873
- this._currentCursorPosition.copy( this._startCursorPosition );
874
- break;
870
+ } else {
875
871
 
876
- case 'ZOOM':
872
+ if ( this.camera.isOrthographicCamera ) {
877
873
 
878
- if ( ! this.enableZoom ) {
874
+ scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement )
875
+ .applyQuaternion( this.camera.quaternion )
876
+ .multiplyScalar( 1 / this.camera.zoom )
877
+ .add( this._gizmos.position );
879
878
 
880
- return;
879
+ } else if ( this.camera.isPerspectiveCamera ) {
881
880
 
882
- }
881
+ scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement )
882
+ .applyQuaternion( this.camera.quaternion )
883
+ .add( this._gizmos.position );
883
884
 
884
- if ( this._animationId != - 1 ) {
885
+ }
885
886
 
886
- cancelAnimationFrame( this._animationId );
887
- this._animationId = - 1;
888
- this._timeStart = - 1;
887
+ }
889
888
 
890
- this.activateGizmos( false );
891
- this.dispatchEvent( _changeEvent );
889
+ this.applyTransformMatrix( this.scale( amount, scalePoint ) );
890
+ this.dispatchEvent( _changeEvent );
892
891
 
893
- }
892
+ }
894
893
 
895
- this.updateTbState( STATE.SCALE, true );
896
- this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
897
- this._currentCursorPosition.copy( this._startCursorPosition );
898
- break;
894
+ }
899
895
 
900
- }
896
+ onPinchEnd() {
901
897
 
902
- }
898
+ this.updateTbState( STATE.IDLE, false );
899
+ this.dispatchEvent( _endEvent );
903
900
 
904
- };
901
+ }
905
902
 
906
- onSinglePanMove = ( event, opState ) => {
903
+ onTriplePanStart() {
907
904
 
908
- if ( this.enabled ) {
905
+ if ( this.enabled && this.enableZoom ) {
909
906
 
910
- const restart = opState != this._state;
911
- this.setCenter( event.clientX, event.clientY );
907
+ this.dispatchEvent( _startEvent );
912
908
 
913
- switch ( opState ) {
909
+ this.updateTbState( STATE.SCALE, true );
914
910
 
915
- case STATE.PAN:
911
+ //const center = event.center;
912
+ let clientX = 0;
913
+ let clientY = 0;
914
+ const nFingers = this._touchCurrent.length;
916
915
 
917
- if ( this.enablePan ) {
916
+ for ( let i = 0; i < nFingers; i ++ ) {
918
917
 
919
- if ( restart ) {
918
+ clientX += this._touchCurrent[ i ].clientX;
919
+ clientY += this._touchCurrent[ i ].clientY;
920
920
 
921
- //switch to pan operation
921
+ }
922
922
 
923
- this.dispatchEvent( _endEvent );
924
- this.dispatchEvent( _startEvent );
923
+ this.setCenter( clientX / nFingers, clientY / nFingers );
925
924
 
926
- this.updateTbState( opState, true );
927
- this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
928
- if ( this.enableGrid ) {
925
+ this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
926
+ this._currentCursorPosition.copy( this._startCursorPosition );
929
927
 
930
- this.drawGrid();
928
+ }
931
929
 
932
- }
930
+ }
933
931
 
934
- this.activateGizmos( false );
932
+ onTriplePanMove() {
935
933
 
936
- } else {
934
+ if ( this.enabled && this.enableZoom ) {
937
935
 
938
- //continue with pan operation
939
- this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) );
940
- this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) );
936
+ // fov / 2
937
+ // |\
938
+ // | \
939
+ // | \
940
+ // x | \
941
+ // | \
942
+ // | \
943
+ // | _ _ _\
944
+ // y
941
945
 
942
- }
946
+ //const center = event.center;
947
+ let clientX = 0;
948
+ let clientY = 0;
949
+ const nFingers = this._touchCurrent.length;
943
950
 
944
- }
951
+ for ( let i = 0; i < nFingers; i ++ ) {
945
952
 
946
- break;
953
+ clientX += this._touchCurrent[ i ].clientX;
954
+ clientY += this._touchCurrent[ i ].clientY;
947
955
 
948
- case STATE.ROTATE:
956
+ }
949
957
 
950
- if ( this.enableRotate ) {
958
+ this.setCenter( clientX / nFingers, clientY / nFingers );
951
959
 
952
- if ( restart ) {
960
+ const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
961
+ this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
953
962
 
954
- //switch to rotate operation
963
+ const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
955
964
 
956
- this.dispatchEvent( _endEvent );
957
- this.dispatchEvent( _startEvent );
965
+ let size = 1;
958
966
 
959
- this.updateTbState( opState, true );
960
- this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
967
+ if ( movement < 0 ) {
961
968
 
962
- if ( this.enableGrid ) {
969
+ size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
963
970
 
964
- this.disposeGrid();
971
+ } else if ( movement > 0 ) {
965
972
 
966
- }
973
+ size = Math.pow( this.scaleFactor, movement * screenNotches );
967
974
 
968
- this.activateGizmos( true );
975
+ }
969
976
 
970
- } else {
977
+ this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
978
+ const x = this._v3_1.distanceTo( this._gizmos.position );
979
+ let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
971
980
 
972
- //continue with rotate operation
973
- this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) );
981
+ //check min and max distance
982
+ xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
974
983
 
975
- const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition );
976
- const angle = this._startCursorPosition.angleTo( this._currentCursorPosition );
977
- const amount = Math.max( distance / this._tbRadius, angle ); //effective rotation angle
984
+ const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 );
978
985
 
979
- this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) );
986
+ //calculate new fov
987
+ let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
980
988
 
981
- if ( this.enableAnimations ) {
989
+ //check min and max fov
990
+ newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov );
982
991
 
983
- this._timePrev = this._timeCurrent;
984
- this._timeCurrent = performance.now();
985
- this._anglePrev = this._angleCurrent;
986
- this._angleCurrent = amount;
987
- this._cursorPosPrev.copy( this._cursorPosCurr );
988
- this._cursorPosCurr.copy( this._currentCursorPosition );
989
- this._wPrev = this._wCurr;
990
- this._wCurr = this.calculateAngularSpeed( this._anglePrev, this._angleCurrent, this._timePrev, this._timeCurrent );
992
+ const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
993
+ size = x / newDistance;
994
+ this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
991
995
 
992
- }
996
+ this.setFov( newFov );
997
+ this.applyTransformMatrix( this.scale( size, this._v3_2, false ) );
993
998
 
994
- }
999
+ //adjusting distance
1000
+ _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x );
1001
+ this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
995
1002
 
996
- }
1003
+ this.dispatchEvent( _changeEvent );
997
1004
 
998
- break;
1005
+ }
999
1006
 
1000
- case STATE.SCALE:
1007
+ }
1001
1008
 
1002
- if ( this.enableZoom ) {
1009
+ onTriplePanEnd() {
1003
1010
 
1004
- if ( restart ) {
1011
+ this.updateTbState( STATE.IDLE, false );
1012
+ this.dispatchEvent( _endEvent );
1013
+ //this.dispatchEvent( _changeEvent );
1005
1014
 
1006
- //switch to zoom operation
1015
+ }
1007
1016
 
1008
- this.dispatchEvent( _endEvent );
1009
- this.dispatchEvent( _startEvent );
1017
+ /**
1018
+ * Set _center's x/y coordinates
1019
+ * @param {Number} clientX
1020
+ * @param {Number} clientY
1021
+ */
1022
+ setCenter( clientX, clientY ) {
1010
1023
 
1011
- this.updateTbState( opState, true );
1012
- this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
1013
- this._currentCursorPosition.copy( this._startCursorPosition );
1024
+ _center.x = clientX;
1025
+ _center.y = clientY;
1014
1026
 
1015
- if ( this.enableGrid ) {
1027
+ }
1016
1028
 
1017
- this.disposeGrid();
1029
+ /**
1030
+ * Set default mouse actions
1031
+ */
1032
+ initializeMouseActions() {
1018
1033
 
1019
- }
1034
+ this.setMouseAction( 'PAN', 0, 'CTRL' );
1035
+ this.setMouseAction( 'PAN', 2 );
1020
1036
 
1021
- this.activateGizmos( false );
1037
+ this.setMouseAction( 'ROTATE', 0 );
1022
1038
 
1023
- } else {
1039
+ this.setMouseAction( 'ZOOM', 'WHEEL' );
1040
+ this.setMouseAction( 'ZOOM', 1 );
1024
1041
 
1025
- //continue with zoom operation
1026
- const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
1027
- this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
1042
+ this.setMouseAction( 'FOV', 'WHEEL', 'SHIFT' );
1043
+ this.setMouseAction( 'FOV', 1, 'SHIFT' );
1028
1044
 
1029
- const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
1030
1045
 
1031
- let size = 1;
1046
+ }
1032
1047
 
1033
- if ( movement < 0 ) {
1048
+ /**
1049
+ * Compare two mouse actions
1050
+ * @param {Object} action1
1051
+ * @param {Object} action2
1052
+ * @returns {Boolean} True if action1 and action 2 are the same mouse action, false otherwise
1053
+ */
1054
+ compareMouseAction( action1, action2 ) {
1034
1055
 
1035
- size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
1056
+ if ( action1.operation == action2.operation ) {
1036
1057
 
1037
- } else if ( movement > 0 ) {
1058
+ if ( action1.mouse == action2.mouse && action1.key == action2.key ) {
1038
1059
 
1039
- size = Math.pow( this.scaleFactor, movement * screenNotches );
1060
+ return true;
1040
1061
 
1041
- }
1062
+ } else {
1042
1063
 
1043
- this._v3_1.setFromMatrixPosition( this._gizmoMatrixState );
1064
+ return false;
1044
1065
 
1045
- this.applyTransformMatrix( this.scale( size, this._v3_1 ) );
1066
+ }
1046
1067
 
1047
- }
1068
+ } else {
1048
1069
 
1049
- }
1070
+ return false;
1050
1071
 
1051
- break;
1072
+ }
1052
1073
 
1053
- case STATE.FOV:
1074
+ }
1054
1075
 
1055
- if ( this.enableZoom && this.camera.isPerspectiveCamera ) {
1076
+ /**
1077
+ * 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
1078
+ * @param {String} operation The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV)
1079
+ * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
1080
+ * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
1081
+ * @returns {Boolean} True if the mouse action has been successfully added, false otherwise
1082
+ */
1083
+ setMouseAction( operation, mouse, key = null ) {
1056
1084
 
1057
- if ( restart ) {
1085
+ const operationInput = [ 'PAN', 'ROTATE', 'ZOOM', 'FOV' ];
1086
+ const mouseInput = [ 0, 1, 2, 'WHEEL' ];
1087
+ const keyInput = [ 'CTRL', 'SHIFT', null ];
1088
+ let state;
1058
1089
 
1059
- //switch to fov operation
1090
+ if ( ! operationInput.includes( operation ) || ! mouseInput.includes( mouse ) || ! keyInput.includes( key ) ) {
1060
1091
 
1061
- this.dispatchEvent( _endEvent );
1062
- this.dispatchEvent( _startEvent );
1092
+ //invalid parameters
1093
+ return false;
1063
1094
 
1064
- this.updateTbState( opState, true );
1065
- this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
1066
- this._currentCursorPosition.copy( this._startCursorPosition );
1095
+ }
1067
1096
 
1068
- if ( this.enableGrid ) {
1097
+ if ( mouse == 'WHEEL' ) {
1069
1098
 
1070
- this.disposeGrid();
1099
+ if ( operation != 'ZOOM' && operation != 'FOV' ) {
1071
1100
 
1072
- }
1101
+ //cannot associate 2D operation to 1D input
1102
+ return false;
1073
1103
 
1074
- this.activateGizmos( false );
1104
+ }
1075
1105
 
1076
- } else {
1106
+ }
1077
1107
 
1078
- //continue with fov operation
1079
- const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
1080
- this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
1108
+ switch ( operation ) {
1081
1109
 
1082
- const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
1110
+ case 'PAN':
1083
1111
 
1084
- let size = 1;
1112
+ state = STATE.PAN;
1113
+ break;
1085
1114
 
1086
- if ( movement < 0 ) {
1115
+ case 'ROTATE':
1087
1116
 
1088
- size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
1117
+ state = STATE.ROTATE;
1118
+ break;
1089
1119
 
1090
- } else if ( movement > 0 ) {
1120
+ case 'ZOOM':
1091
1121
 
1092
- size = Math.pow( this.scaleFactor, movement * screenNotches );
1122
+ state = STATE.SCALE;
1123
+ break;
1093
1124
 
1094
- }
1125
+ case 'FOV':
1095
1126
 
1096
- this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
1097
- const x = this._v3_1.distanceTo( this._gizmos.position );
1098
- let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
1127
+ state = STATE.FOV;
1128
+ break;
1099
1129
 
1100
- //check min and max distance
1101
- xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
1130
+ }
1102
1131
 
1103
- const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 );
1132
+ const action = {
1104
1133
 
1105
- //calculate new fov
1106
- let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
1134
+ operation: operation,
1135
+ mouse: mouse,
1136
+ key: key,
1137
+ state: state
1107
1138
 
1108
- //check min and max fov
1109
- newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov );
1139
+ };
1110
1140
 
1111
- const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
1112
- size = x / newDistance;
1113
- this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
1141
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1114
1142
 
1115
- this.setFov( newFov );
1116
- this.applyTransformMatrix( this.scale( size, this._v3_2, false ) );
1143
+ if ( this.mouseActions[ i ].mouse == action.mouse && this.mouseActions[ i ].key == action.key ) {
1117
1144
 
1118
- //adjusting distance
1119
- _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x );
1120
- this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
1145
+ this.mouseActions.splice( i, 1, action );
1146
+ return true;
1121
1147
 
1122
- }
1148
+ }
1123
1149
 
1124
- }
1150
+ }
1125
1151
 
1126
- break;
1152
+ this.mouseActions.push( action );
1153
+ return true;
1127
1154
 
1128
- }
1155
+ }
1129
1156
 
1130
- this.dispatchEvent( _changeEvent );
1157
+ /**
1158
+ * Remove a mouse action by specifying its mouse/key combination
1159
+ * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
1160
+ * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
1161
+ * @returns {Boolean} True if the operation has been succesfully removed, false otherwise
1162
+ */
1163
+ unsetMouseAction( mouse, key = null ) {
1131
1164
 
1132
- }
1165
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1133
1166
 
1134
- };
1167
+ if ( this.mouseActions[ i ].mouse == mouse && this.mouseActions[ i ].key == key ) {
1135
1168
 
1136
- onSinglePanEnd = () => {
1169
+ this.mouseActions.splice( i, 1 );
1170
+ return true;
1137
1171
 
1138
- if ( this._state == STATE.ROTATE ) {
1172
+ }
1139
1173
 
1174
+ }
1140
1175
 
1141
- if ( ! this.enableRotate ) {
1176
+ return false;
1142
1177
 
1143
- return;
1178
+ }
1144
1179
 
1145
- }
1180
+ /**
1181
+ * Return the operation associated to a mouse/keyboard combination
1182
+ * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
1183
+ * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
1184
+ * @returns The operation if it has been found, null otherwise
1185
+ */
1186
+ getOpFromAction( mouse, key ) {
1146
1187
 
1147
- if ( this.enableAnimations ) {
1188
+ let action;
1148
1189
 
1149
- //perform rotation animation
1150
- const deltaTime = ( performance.now() - this._timeCurrent );
1151
- if ( deltaTime < 120 ) {
1190
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1152
1191
 
1153
- const w = Math.abs( ( this._wPrev + this._wCurr ) / 2 );
1192
+ action = this.mouseActions[ i ];
1193
+ if ( action.mouse == mouse && action.key == key ) {
1154
1194
 
1155
- const self = this;
1156
- this._animationId = window.requestAnimationFrame( function ( t ) {
1195
+ return action.operation;
1157
1196
 
1158
- self.updateTbState( STATE.ANIMATION_ROTATE, true );
1159
- const rotationAxis = self.calculateRotationAxis( self._cursorPosPrev, self._cursorPosCurr );
1197
+ }
1160
1198
 
1161
- self.onRotationAnim( t, rotationAxis, Math.min( w, self.wMax ) );
1199
+ }
1162
1200
 
1163
- } );
1201
+ if ( key != null ) {
1164
1202
 
1165
- } else {
1203
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1166
1204
 
1167
- //cursor has been standing still for over 120 ms since last movement
1168
- this.updateTbState( STATE.IDLE, false );
1169
- this.activateGizmos( false );
1170
- this.dispatchEvent( _changeEvent );
1205
+ action = this.mouseActions[ i ];
1206
+ if ( action.mouse == mouse && action.key == null ) {
1207
+
1208
+ return action.operation;
1171
1209
 
1172
1210
  }
1173
1211
 
1174
- } else {
1212
+ }
1175
1213
 
1176
- this.updateTbState( STATE.IDLE, false );
1177
- this.activateGizmos( false );
1178
- this.dispatchEvent( _changeEvent );
1214
+ }
1179
1215
 
1180
- }
1216
+ return null;
1181
1217
 
1182
- } else if ( this._state == STATE.PAN || this._state == STATE.IDLE ) {
1218
+ }
1183
1219
 
1184
- this.updateTbState( STATE.IDLE, false );
1220
+ /**
1221
+ * Get the operation associated to mouse and key combination and returns the corresponding FSA state
1222
+ * @param {Number} mouse Mouse button
1223
+ * @param {String} key Keyboard modifier
1224
+ * @returns The FSA state obtained from the operation associated to mouse/keyboard combination
1225
+ */
1226
+ getOpStateFromAction( mouse, key ) {
1185
1227
 
1186
- if ( this.enableGrid ) {
1228
+ let action;
1187
1229
 
1188
- this.disposeGrid();
1230
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1189
1231
 
1190
- }
1232
+ action = this.mouseActions[ i ];
1233
+ if ( action.mouse == mouse && action.key == key ) {
1191
1234
 
1192
- this.activateGizmos( false );
1193
- this.dispatchEvent( _changeEvent );
1235
+ return action.state;
1194
1236
 
1237
+ }
1195
1238
 
1196
1239
  }
1197
1240
 
1198
- this.dispatchEvent( _endEvent );
1241
+ if ( key != null ) {
1199
1242
 
1200
- };
1243
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1201
1244
 
1202
- onDoubleTap = ( event ) => {
1245
+ action = this.mouseActions[ i ];
1246
+ if ( action.mouse == mouse && action.key == null ) {
1203
1247
 
1204
- if ( this.enabled && this.enablePan && this.scene != null ) {
1248
+ return action.state;
1205
1249
 
1206
- this.dispatchEvent( _startEvent );
1250
+ }
1207
1251
 
1208
- this.setCenter( event.clientX, event.clientY );
1209
- const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.camera );
1252
+ }
1210
1253
 
1211
- if ( hitP != null && this.enableAnimations ) {
1254
+ }
1212
1255
 
1213
- const self = this;
1214
- if ( this._animationId != - 1 ) {
1256
+ return null;
1215
1257
 
1216
- window.cancelAnimationFrame( this._animationId );
1258
+ }
1217
1259
 
1218
- }
1260
+ /**
1261
+ * Calculate the angle between two pointers
1262
+ * @param {PointerEvent} p1
1263
+ * @param {PointerEvent} p2
1264
+ * @returns {Number} The angle between two pointers in degrees
1265
+ */
1266
+ getAngle( p1, p2 ) {
1219
1267
 
1220
- this._timeStart = - 1;
1221
- this._animationId = window.requestAnimationFrame( function ( t ) {
1268
+ return Math.atan2( p2.clientY - p1.clientY, p2.clientX - p1.clientX ) * 180 / Math.PI;
1222
1269
 
1223
- self.updateTbState( STATE.ANIMATION_FOCUS, true );
1224
- self.onFocusAnim( t, hitP, self._cameraMatrixState, self._gizmoMatrixState );
1270
+ }
1225
1271
 
1226
- } );
1272
+ /**
1273
+ * Update a PointerEvent inside current pointerevents array
1274
+ * @param {PointerEvent} event
1275
+ */
1276
+ updateTouchEvent( event ) {
1227
1277
 
1228
- } else if ( hitP != null && ! this.enableAnimations ) {
1278
+ for ( let i = 0; i < this._touchCurrent.length; i ++ ) {
1229
1279
 
1230
- this.updateTbState( STATE.FOCUS, true );
1231
- this.focus( hitP, this.scaleFactor );
1232
- this.updateTbState( STATE.IDLE, false );
1233
- this.dispatchEvent( _changeEvent );
1280
+ if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
1281
+
1282
+ this._touchCurrent.splice( i, 1, event );
1283
+ break;
1234
1284
 
1235
1285
  }
1236
1286
 
1237
1287
  }
1238
1288
 
1239
- this.dispatchEvent( _endEvent );
1240
-
1241
- };
1289
+ }
1242
1290
 
1243
- onDoublePanStart = () => {
1291
+ /**
1292
+ * Apply a transformation matrix, to the camera and gizmos
1293
+ * @param {Object} transformation Object containing matrices to apply to camera and gizmos
1294
+ */
1295
+ applyTransformMatrix( transformation ) {
1244
1296
 
1245
- if ( this.enabled && this.enablePan ) {
1297
+ if ( transformation.camera != null ) {
1246
1298
 
1247
- this.dispatchEvent( _startEvent );
1299
+ this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera );
1300
+ this._m4_1.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
1301
+ this.camera.updateMatrix();
1248
1302
 
1249
- this.updateTbState( STATE.PAN, true );
1303
+ //update camera up vector
1304
+ if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) {
1250
1305
 
1251
- this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
1252
- this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) );
1253
- this._currentCursorPosition.copy( this._startCursorPosition );
1306
+ this.camera.up.copy( this._upState ).applyQuaternion( this.camera.quaternion );
1254
1307
 
1255
- this.activateGizmos( false );
1308
+ }
1256
1309
 
1257
1310
  }
1258
1311
 
1259
- };
1312
+ if ( transformation.gizmos != null ) {
1260
1313
 
1261
- onDoublePanMove = () => {
1314
+ this._m4_1.copy( this._gizmoMatrixState ).premultiply( transformation.gizmos );
1315
+ this._m4_1.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1316
+ this._gizmos.updateMatrix();
1262
1317
 
1263
- if ( this.enabled && this.enablePan ) {
1318
+ }
1264
1319
 
1265
- this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
1320
+ if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) {
1266
1321
 
1267
- if ( this._state != STATE.PAN ) {
1322
+ this._tbRadius = this.calculateTbRadius( this.camera );
1268
1323
 
1269
- this.updateTbState( STATE.PAN, true );
1270
- this._startCursorPosition.copy( this._currentCursorPosition );
1324
+ if ( this.adjustNearFar ) {
1271
1325
 
1272
- }
1326
+ const cameraDistance = this.camera.position.distanceTo( this._gizmos.position );
1273
1327
 
1274
- this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) );
1275
- this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) );
1276
- this.dispatchEvent( _changeEvent );
1328
+ const bb = new Box3();
1329
+ bb.setFromObject( this._gizmos );
1330
+ const sphere = new Sphere();
1331
+ bb.getBoundingSphere( sphere );
1277
1332
 
1278
- }
1333
+ const adjustedNearPosition = Math.max( this._nearPos0, sphere.radius + sphere.center.length() );
1334
+ const regularNearPosition = cameraDistance - this._initialNear;
1335
+
1336
+ const minNearPos = Math.min( adjustedNearPosition, regularNearPosition );
1337
+ this.camera.near = cameraDistance - minNearPos;
1279
1338
 
1280
- };
1281
1339
 
1282
- onDoublePanEnd = () => {
1340
+ const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() );
1341
+ const regularFarPosition = cameraDistance - this._initialFar;
1283
1342
 
1284
- this.updateTbState( STATE.IDLE, false );
1285
- this.dispatchEvent( _endEvent );
1343
+ const minFarPos = Math.min( adjustedFarPosition, regularFarPosition );
1344
+ this.camera.far = cameraDistance - minFarPos;
1286
1345
 
1287
- };
1346
+ this.camera.updateProjectionMatrix();
1288
1347
 
1348
+ } else {
1289
1349
 
1290
- onRotateStart = () => {
1350
+ let update = false;
1291
1351
 
1292
- if ( this.enabled && this.enableRotate ) {
1352
+ if ( this.camera.near != this._initialNear ) {
1293
1353
 
1294
- this.dispatchEvent( _startEvent );
1354
+ this.camera.near = this._initialNear;
1355
+ update = true;
1295
1356
 
1296
- this.updateTbState( STATE.ZROTATE, true );
1357
+ }
1297
1358
 
1298
- //this._startFingerRotation = event.rotation;
1359
+ if ( this.camera.far != this._initialFar ) {
1299
1360
 
1300
- this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
1301
- this._currentFingerRotation = this._startFingerRotation;
1361
+ this.camera.far = this._initialFar;
1362
+ update = true;
1302
1363
 
1303
- this.camera.getWorldDirection( this._rotationAxis ); //rotation axis
1364
+ }
1304
1365
 
1305
- if ( ! this.enablePan && ! this.enableZoom ) {
1366
+ if ( update ) {
1306
1367
 
1307
- this.activateGizmos( true );
1368
+ this.camera.updateProjectionMatrix();
1369
+
1370
+ }
1308
1371
 
1309
1372
  }
1310
1373
 
1311
1374
  }
1312
1375
 
1313
- };
1314
-
1315
- onRotateMove = () => {
1376
+ }
1316
1377
 
1317
- if ( this.enabled && this.enableRotate ) {
1378
+ /**
1379
+ * Calculate the angular speed
1380
+ * @param {Number} p0 Position at t0
1381
+ * @param {Number} p1 Position at t1
1382
+ * @param {Number} t0 Initial time in milliseconds
1383
+ * @param {Number} t1 Ending time in milliseconds
1384
+ */
1385
+ calculateAngularSpeed( p0, p1, t0, t1 ) {
1318
1386
 
1319
- this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
1320
- let rotationPoint;
1387
+ const s = p1 - p0;
1388
+ const t = ( t1 - t0 ) / 1000;
1389
+ if ( t == 0 ) {
1321
1390
 
1322
- if ( this._state != STATE.ZROTATE ) {
1391
+ return 0;
1323
1392
 
1324
- this.updateTbState( STATE.ZROTATE, true );
1325
- this._startFingerRotation = this._currentFingerRotation;
1393
+ }
1326
1394
 
1327
- }
1395
+ return s / t;
1328
1396
 
1329
- //this._currentFingerRotation = event.rotation;
1330
- this._currentFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
1397
+ }
1331
1398
 
1332
- if ( ! this.enablePan ) {
1399
+ /**
1400
+ * Calculate the distance between two pointers
1401
+ * @param {PointerEvent} p0 The first pointer
1402
+ * @param {PointerEvent} p1 The second pointer
1403
+ * @returns {number} The distance between the two pointers
1404
+ */
1405
+ calculatePointersDistance( p0, p1 ) {
1333
1406
 
1334
- rotationPoint = new Vector3().setFromMatrixPosition( this._gizmoMatrixState );
1407
+ return Math.sqrt( Math.pow( p1.clientX - p0.clientX, 2 ) + Math.pow( p1.clientY - p0.clientY, 2 ) );
1335
1408
 
1336
- } else {
1409
+ }
1337
1410
 
1338
- this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
1339
- rotationPoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._v3_2 );
1411
+ /**
1412
+ * Calculate the rotation axis as the vector perpendicular between two vectors
1413
+ * @param {Vector3} vec1 The first vector
1414
+ * @param {Vector3} vec2 The second vector
1415
+ * @returns {Vector3} The normalized rotation axis
1416
+ */
1417
+ calculateRotationAxis( vec1, vec2 ) {
1340
1418
 
1341
- }
1419
+ this._rotationMatrix.extractRotation( this._cameraMatrixState );
1420
+ this._quat.setFromRotationMatrix( this._rotationMatrix );
1342
1421
 
1343
- const amount = MathUtils.DEG2RAD * ( this._startFingerRotation - this._currentFingerRotation );
1422
+ this._rotationAxis.crossVectors( vec1, vec2 ).applyQuaternion( this._quat );
1423
+ return this._rotationAxis.normalize().clone();
1344
1424
 
1345
- this.applyTransformMatrix( this.zRotate( rotationPoint, amount ) );
1346
- this.dispatchEvent( _changeEvent );
1425
+ }
1347
1426
 
1348
- }
1427
+ /**
1428
+ * Calculate the trackball radius so that gizmo's diamater will be 2/3 of the minimum side of the camera frustum
1429
+ * @param {Camera} camera
1430
+ * @returns {Number} The trackball radius
1431
+ */
1432
+ calculateTbRadius( camera ) {
1349
1433
 
1350
- };
1434
+ const distance = camera.position.distanceTo( this._gizmos.position );
1351
1435
 
1352
- onRotateEnd = () => {
1436
+ if ( camera.type == 'PerspectiveCamera' ) {
1353
1437
 
1354
- this.updateTbState( STATE.IDLE, false );
1355
- this.activateGizmos( false );
1356
- this.dispatchEvent( _endEvent );
1438
+ const halfFovV = MathUtils.DEG2RAD * camera.fov * 0.5; //vertical fov/2 in radians
1439
+ const halfFovH = Math.atan( ( camera.aspect ) * Math.tan( halfFovV ) ); //horizontal fov/2 in radians
1440
+ return Math.tan( Math.min( halfFovV, halfFovH ) ) * distance * this.radiusFactor;
1357
1441
 
1358
- };
1442
+ } else if ( camera.type == 'OrthographicCamera' ) {
1359
1443
 
1360
- onPinchStart = () => {
1444
+ return Math.min( camera.top, camera.right ) * this.radiusFactor;
1361
1445
 
1362
- if ( this.enabled && this.enableZoom ) {
1446
+ }
1363
1447
 
1364
- this.dispatchEvent( _startEvent );
1365
- this.updateTbState( STATE.SCALE, true );
1448
+ }
1366
1449
 
1367
- this._startFingerDistance = this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] );
1368
- this._currentFingerDistance = this._startFingerDistance;
1450
+ /**
1451
+ * Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in
1452
+ * @param {Vector3} point The point of interest
1453
+ * @param {Number} size Scale factor
1454
+ * @param {Number} amount Amount of operation to be completed (used for focus animations, default is complete full operation)
1455
+ */
1456
+ focus( point, size, amount = 1 ) {
1369
1457
 
1370
- this.activateGizmos( false );
1458
+ //move center of camera (along with gizmos) towards point of interest
1459
+ _offset.copy( point ).sub( this._gizmos.position ).multiplyScalar( amount );
1460
+ this._translationMatrix.makeTranslation( _offset.x, _offset.y, _offset.z );
1371
1461
 
1372
- }
1462
+ _gizmoMatrixStateTemp.copy( this._gizmoMatrixState );
1463
+ this._gizmoMatrixState.premultiply( this._translationMatrix );
1464
+ this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1373
1465
 
1374
- };
1466
+ _cameraMatrixStateTemp.copy( this._cameraMatrixState );
1467
+ this._cameraMatrixState.premultiply( this._translationMatrix );
1468
+ this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
1375
1469
 
1376
- onPinchMove = () => {
1470
+ //apply zoom
1471
+ if ( this.enableZoom ) {
1377
1472
 
1378
- if ( this.enabled && this.enableZoom ) {
1473
+ this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
1379
1474
 
1380
- this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
1381
- const minDistance = 12; //minimum distance between fingers (in css pixels)
1475
+ }
1382
1476
 
1383
- if ( this._state != STATE.SCALE ) {
1477
+ this._gizmoMatrixState.copy( _gizmoMatrixStateTemp );
1478
+ this._cameraMatrixState.copy( _cameraMatrixStateTemp );
1384
1479
 
1385
- this._startFingerDistance = this._currentFingerDistance;
1386
- this.updateTbState( STATE.SCALE, true );
1480
+ }
1387
1481
 
1388
- }
1482
+ /**
1483
+ * Draw a grid and add it to the scene
1484
+ */
1485
+ drawGrid() {
1389
1486
 
1390
- this._currentFingerDistance = Math.max( this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ), minDistance * this._devPxRatio );
1391
- const amount = this._currentFingerDistance / this._startFingerDistance;
1487
+ if ( this.scene != null ) {
1392
1488
 
1393
- let scalePoint;
1489
+ const color = 0x888888;
1490
+ const multiplier = 3;
1491
+ let size, divisions, maxLength, tick;
1394
1492
 
1395
- if ( ! this.enablePan ) {
1493
+ if ( this.camera.isOrthographicCamera ) {
1396
1494
 
1397
- scalePoint = this._gizmos.position;
1495
+ const width = this.camera.right - this.camera.left;
1496
+ const height = this.camera.bottom - this.camera.top;
1398
1497
 
1399
- } else {
1498
+ maxLength = Math.max( width, height );
1499
+ tick = maxLength / 20;
1400
1500
 
1401
- if ( this.camera.isOrthographicCamera ) {
1501
+ size = maxLength / this.camera.zoom * multiplier;
1502
+ divisions = size / tick * this.camera.zoom;
1402
1503
 
1403
- scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement )
1404
- .applyQuaternion( this.camera.quaternion )
1405
- .multiplyScalar( 1 / this.camera.zoom )
1406
- .add( this._gizmos.position );
1504
+ } else if ( this.camera.isPerspectiveCamera ) {
1407
1505
 
1408
- } else if ( this.camera.isPerspectiveCamera ) {
1506
+ const distance = this.camera.position.distanceTo( this._gizmos.position );
1507
+ const halfFovV = MathUtils.DEG2RAD * this.camera.fov * 0.5;
1508
+ const halfFovH = Math.atan( ( this.camera.aspect ) * Math.tan( halfFovV ) );
1409
1509
 
1410
- scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement )
1411
- .applyQuaternion( this.camera.quaternion )
1412
- .add( this._gizmos.position );
1510
+ maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2;
1511
+ tick = maxLength / 20;
1413
1512
 
1414
- }
1513
+ size = maxLength * multiplier;
1514
+ divisions = size / tick;
1415
1515
 
1416
1516
  }
1417
1517
 
1418
- this.applyTransformMatrix( this.scale( amount, scalePoint ) );
1419
- this.dispatchEvent( _changeEvent );
1518
+ if ( this._grid == null ) {
1420
1519
 
1421
- }
1520
+ this._grid = new GridHelper( size, divisions, color, color );
1521
+ this._grid.position.copy( this._gizmos.position );
1522
+ this._gridPosition.copy( this._grid.position );
1523
+ this._grid.quaternion.copy( this.camera.quaternion );
1524
+ this._grid.rotateX( Math.PI * 0.5 );
1422
1525
 
1423
- };
1526
+ this.scene.add( this._grid );
1424
1527
 
1425
- onPinchEnd = () => {
1528
+ }
1426
1529
 
1427
- this.updateTbState( STATE.IDLE, false );
1428
- this.dispatchEvent( _endEvent );
1530
+ }
1429
1531
 
1430
- };
1532
+ }
1431
1533
 
1432
- onTriplePanStart = () => {
1534
+ /**
1535
+ * Remove all listeners, stop animations and clean scene
1536
+ */
1537
+ dispose() {
1433
1538
 
1434
- if ( this.enabled && this.enableZoom ) {
1539
+ if ( this._animationId != - 1 ) {
1435
1540
 
1436
- this.dispatchEvent( _startEvent );
1541
+ window.cancelAnimationFrame( this._animationId );
1437
1542
 
1438
- this.updateTbState( STATE.SCALE, true );
1543
+ }
1439
1544
 
1440
- //const center = event.center;
1441
- let clientX = 0;
1442
- let clientY = 0;
1443
- const nFingers = this._touchCurrent.length;
1545
+ this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
1546
+ this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel );
1547
+ this.domElement.removeEventListener( 'wheel', this._onWheel );
1548
+ this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
1444
1549
 
1445
- for ( let i = 0; i < nFingers; i ++ ) {
1550
+ window.removeEventListener( 'pointermove', this._onPointerMove );
1551
+ window.removeEventListener( 'pointerup', this._onPointerUp );
1446
1552
 
1447
- clientX += this._touchCurrent[ i ].clientX;
1448
- clientY += this._touchCurrent[ i ].clientY;
1553
+ window.removeEventListener( 'resize', this._onWindowResize );
1449
1554
 
1450
- }
1555
+ if ( this.scene !== null ) this.scene.remove( this._gizmos );
1556
+ this.disposeGrid();
1451
1557
 
1452
- this.setCenter( clientX / nFingers, clientY / nFingers );
1558
+ }
1453
1559
 
1454
- this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
1455
- this._currentCursorPosition.copy( this._startCursorPosition );
1560
+ /**
1561
+ * remove the grid from the scene
1562
+ */
1563
+ disposeGrid() {
1456
1564
 
1457
- }
1565
+ if ( this._grid != null && this.scene != null ) {
1458
1566
 
1459
- };
1567
+ this.scene.remove( this._grid );
1568
+ this._grid = null;
1460
1569
 
1461
- onTriplePanMove = () => {
1570
+ }
1462
1571
 
1463
- if ( this.enabled && this.enableZoom ) {
1572
+ }
1464
1573
 
1465
- // fov / 2
1466
- // |\
1467
- // | \
1468
- // | \
1469
- // x | \
1470
- // | \
1471
- // | \
1472
- // | _ _ _\
1473
- // y
1574
+ /**
1575
+ * Compute the easing out cubic function for ease out effect in animation
1576
+ * @param {Number} t The absolute progress of the animation in the bound of 0 (beginning of the) and 1 (ending of animation)
1577
+ * @returns {Number} Result of easing out cubic at time t
1578
+ */
1579
+ easeOutCubic( t ) {
1474
1580
 
1475
- //const center = event.center;
1476
- let clientX = 0;
1477
- let clientY = 0;
1478
- const nFingers = this._touchCurrent.length;
1581
+ return 1 - Math.pow( 1 - t, 3 );
1479
1582
 
1480
- for ( let i = 0; i < nFingers; i ++ ) {
1583
+ }
1481
1584
 
1482
- clientX += this._touchCurrent[ i ].clientX;
1483
- clientY += this._touchCurrent[ i ].clientY;
1585
+ /**
1586
+ * Make rotation gizmos more or less visible
1587
+ * @param {Boolean} isActive If true, make gizmos more visible
1588
+ */
1589
+ activateGizmos( isActive ) {
1484
1590
 
1485
- }
1591
+ const gizmoX = this._gizmos.children[ 0 ];
1592
+ const gizmoY = this._gizmos.children[ 1 ];
1593
+ const gizmoZ = this._gizmos.children[ 2 ];
1486
1594
 
1487
- this.setCenter( clientX / nFingers, clientY / nFingers );
1595
+ if ( isActive ) {
1488
1596
 
1489
- const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
1490
- this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
1597
+ gizmoX.material.setValues( { opacity: 1 } );
1598
+ gizmoY.material.setValues( { opacity: 1 } );
1599
+ gizmoZ.material.setValues( { opacity: 1 } );
1491
1600
 
1492
- const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
1601
+ } else {
1493
1602
 
1494
- let size = 1;
1603
+ gizmoX.material.setValues( { opacity: 0.6 } );
1604
+ gizmoY.material.setValues( { opacity: 0.6 } );
1605
+ gizmoZ.material.setValues( { opacity: 0.6 } );
1495
1606
 
1496
- if ( movement < 0 ) {
1607
+ }
1497
1608
 
1498
- size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
1609
+ }
1499
1610
 
1500
- } else if ( movement > 0 ) {
1611
+ /**
1612
+ * Calculate the cursor position in NDC
1613
+ * @param {number} x Cursor horizontal coordinate within the canvas
1614
+ * @param {number} y Cursor vertical coordinate within the canvas
1615
+ * @param {HTMLElement} canvas The canvas where the renderer draws its output
1616
+ * @returns {Vector2} Cursor normalized position inside the canvas
1617
+ */
1618
+ getCursorNDC( cursorX, cursorY, canvas ) {
1501
1619
 
1502
- size = Math.pow( this.scaleFactor, movement * screenNotches );
1620
+ const canvasRect = canvas.getBoundingClientRect();
1621
+ this._v2_1.setX( ( ( cursorX - canvasRect.left ) / canvasRect.width ) * 2 - 1 );
1622
+ this._v2_1.setY( ( ( canvasRect.bottom - cursorY ) / canvasRect.height ) * 2 - 1 );
1623
+ return this._v2_1.clone();
1503
1624
 
1504
- }
1625
+ }
1505
1626
 
1506
- this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
1507
- const x = this._v3_1.distanceTo( this._gizmos.position );
1508
- let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
1627
+ /**
1628
+ * Calculate the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas
1629
+ * @param {Number} x Cursor horizontal coordinate within the canvas
1630
+ * @param {Number} y Cursor vertical coordinate within the canvas
1631
+ * @param {HTMLElement} canvas The canvas where the renderer draws its output
1632
+ * @returns {Vector2} Cursor position inside the canvas
1633
+ */
1634
+ getCursorPosition( cursorX, cursorY, canvas ) {
1509
1635
 
1510
- //check min and max distance
1511
- xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
1636
+ this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
1637
+ this._v2_1.x *= ( this.camera.right - this.camera.left ) * 0.5;
1638
+ this._v2_1.y *= ( this.camera.top - this.camera.bottom ) * 0.5;
1639
+ return this._v2_1.clone();
1512
1640
 
1513
- const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 );
1641
+ }
1514
1642
 
1515
- //calculate new fov
1516
- let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
1643
+ /**
1644
+ * Set the camera to be controlled
1645
+ * @param {Camera} camera The virtual camera to be controlled
1646
+ */
1647
+ setCamera( camera ) {
1517
1648
 
1518
- //check min and max fov
1519
- newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov );
1649
+ camera.lookAt( this.target );
1650
+ camera.updateMatrix();
1520
1651
 
1521
- const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
1522
- size = x / newDistance;
1523
- this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
1652
+ //setting state
1653
+ if ( camera.type == 'PerspectiveCamera' ) {
1524
1654
 
1525
- this.setFov( newFov );
1526
- this.applyTransformMatrix( this.scale( size, this._v3_2, false ) );
1655
+ this._fov0 = camera.fov;
1656
+ this._fovState = camera.fov;
1527
1657
 
1528
- //adjusting distance
1529
- _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x );
1530
- this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
1658
+ }
1531
1659
 
1532
- this.dispatchEvent( _changeEvent );
1660
+ this._cameraMatrixState0.copy( camera.matrix );
1661
+ this._cameraMatrixState.copy( this._cameraMatrixState0 );
1662
+ this._cameraProjectionState.copy( camera.projectionMatrix );
1663
+ this._zoom0 = camera.zoom;
1664
+ this._zoomState = this._zoom0;
1533
1665
 
1534
- }
1666
+ this._initialNear = camera.near;
1667
+ this._nearPos0 = camera.position.distanceTo( this.target ) - camera.near;
1668
+ this._nearPos = this._initialNear;
1669
+
1670
+ this._initialFar = camera.far;
1671
+ this._farPos0 = camera.position.distanceTo( this.target ) - camera.far;
1672
+ this._farPos = this._initialFar;
1535
1673
 
1536
- };
1674
+ this._up0.copy( camera.up );
1675
+ this._upState.copy( camera.up );
1537
1676
 
1538
- onTriplePanEnd = () => {
1677
+ this.camera = camera;
1678
+ this.camera.updateProjectionMatrix();
1539
1679
 
1540
- this.updateTbState( STATE.IDLE, false );
1541
- this.dispatchEvent( _endEvent );
1542
- //this.dispatchEvent( _changeEvent );
1680
+ //making gizmos
1681
+ this._tbRadius = this.calculateTbRadius( camera );
1682
+ this.makeGizmos( this.target, this._tbRadius );
1543
1683
 
1544
- };
1684
+ }
1545
1685
 
1546
1686
  /**
1547
- * Set _center's x/y coordinates
1548
- * @param {Number} clientX
1549
- * @param {Number} clientY
1687
+ * Set gizmos visibility
1688
+ * @param {Boolean} value Value of gizmos visibility
1550
1689
  */
1551
- setCenter = ( clientX, clientY ) => {
1690
+ setGizmosVisible( value ) {
1552
1691
 
1553
- _center.x = clientX;
1554
- _center.y = clientY;
1692
+ this._gizmos.visible = value;
1693
+ this.dispatchEvent( _changeEvent );
1555
1694
 
1556
- };
1695
+ }
1557
1696
 
1558
1697
  /**
1559
- * Set default mouse actions
1698
+ * Set gizmos radius factor and redraws gizmos
1699
+ * @param {Float} value Value of radius factor
1560
1700
  */
1561
- initializeMouseActions = () => {
1562
-
1563
- this.setMouseAction( 'PAN', 0, 'CTRL' );
1564
- this.setMouseAction( 'PAN', 2 );
1565
-
1566
- this.setMouseAction( 'ROTATE', 0 );
1701
+ setTbRadius( value ) {
1567
1702
 
1568
- this.setMouseAction( 'ZOOM', 'WHEEL' );
1569
- this.setMouseAction( 'ZOOM', 1 );
1703
+ this.radiusFactor = value;
1704
+ this._tbRadius = this.calculateTbRadius( this.camera );
1570
1705
 
1571
- this.setMouseAction( 'FOV', 'WHEEL', 'SHIFT' );
1572
- this.setMouseAction( 'FOV', 1, 'SHIFT' );
1706
+ const curve = new EllipseCurve( 0, 0, this._tbRadius, this._tbRadius );
1707
+ const points = curve.getPoints( this._curvePts );
1708
+ const curveGeometry = new BufferGeometry().setFromPoints( points );
1573
1709
 
1574
1710
 
1575
- };
1711
+ for ( const gizmo in this._gizmos.children ) {
1576
1712
 
1577
- /**
1578
- * Compare two mouse actions
1579
- * @param {Object} action1
1580
- * @param {Object} action2
1581
- * @returns {Boolean} True if action1 and action 2 are the same mouse action, false otherwise
1582
- */
1583
- compareMouseAction = ( action1, action2 ) => {
1713
+ this._gizmos.children[ gizmo ].geometry = curveGeometry;
1584
1714
 
1585
- if ( action1.operation == action2.operation ) {
1715
+ }
1586
1716
 
1587
- if ( action1.mouse == action2.mouse && action1.key == action2.key ) {
1717
+ this.dispatchEvent( _changeEvent );
1588
1718
 
1589
- return true;
1719
+ }
1590
1720
 
1591
- } else {
1721
+ /**
1722
+ * Creates the rotation gizmos matching trackball center and radius
1723
+ * @param {Vector3} tbCenter The trackball center
1724
+ * @param {number} tbRadius The trackball radius
1725
+ */
1726
+ makeGizmos( tbCenter, tbRadius ) {
1592
1727
 
1593
- return false;
1728
+ const curve = new EllipseCurve( 0, 0, tbRadius, tbRadius );
1729
+ const points = curve.getPoints( this._curvePts );
1594
1730
 
1595
- }
1731
+ //geometry
1732
+ const curveGeometry = new BufferGeometry().setFromPoints( points );
1596
1733
 
1597
- } else {
1734
+ //material
1735
+ const curveMaterialX = new LineBasicMaterial( { color: 0xff8080, fog: false, transparent: true, opacity: 0.6 } );
1736
+ const curveMaterialY = new LineBasicMaterial( { color: 0x80ff80, fog: false, transparent: true, opacity: 0.6 } );
1737
+ const curveMaterialZ = new LineBasicMaterial( { color: 0x8080ff, fog: false, transparent: true, opacity: 0.6 } );
1598
1738
 
1599
- return false;
1739
+ //line
1740
+ const gizmoX = new Line( curveGeometry, curveMaterialX );
1741
+ const gizmoY = new Line( curveGeometry, curveMaterialY );
1742
+ const gizmoZ = new Line( curveGeometry, curveMaterialZ );
1600
1743
 
1601
- }
1744
+ const rotation = Math.PI * 0.5;
1745
+ gizmoX.rotation.x = rotation;
1746
+ gizmoY.rotation.y = rotation;
1602
1747
 
1603
- };
1604
1748
 
1605
- /**
1606
- * 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
1607
- * @param {String} operation The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV)
1608
- * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
1609
- * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
1610
- * @returns {Boolean} True if the mouse action has been successfully added, false otherwise
1611
- */
1612
- setMouseAction = ( operation, mouse, key = null ) => {
1749
+ //setting state
1750
+ this._gizmoMatrixState0.identity().setPosition( tbCenter );
1751
+ this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
1613
1752
 
1614
- const operationInput = [ 'PAN', 'ROTATE', 'ZOOM', 'FOV' ];
1615
- const mouseInput = [ 0, 1, 2, 'WHEEL' ];
1616
- const keyInput = [ 'CTRL', 'SHIFT', null ];
1617
- let state;
1753
+ if ( this.camera.zoom !== 1 ) {
1618
1754
 
1619
- if ( ! operationInput.includes( operation ) || ! mouseInput.includes( mouse ) || ! keyInput.includes( key ) ) {
1755
+ //adapt gizmos size to camera zoom
1756
+ const size = 1 / this.camera.zoom;
1757
+ this._scaleMatrix.makeScale( size, size, size );
1758
+ this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z );
1620
1759
 
1621
- //invalid parameters
1622
- return false;
1760
+ this._gizmoMatrixState.premultiply( this._translationMatrix ).premultiply( this._scaleMatrix );
1761
+ this._translationMatrix.makeTranslation( tbCenter.x, tbCenter.y, tbCenter.z );
1762
+ this._gizmoMatrixState.premultiply( this._translationMatrix );
1623
1763
 
1624
1764
  }
1625
1765
 
1626
- if ( mouse == 'WHEEL' ) {
1766
+ this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1627
1767
 
1628
- if ( operation != 'ZOOM' && operation != 'FOV' ) {
1768
+ //
1629
1769
 
1630
- //cannot associate 2D operation to 1D input
1631
- return false;
1770
+ this._gizmos.traverse( function ( object ) {
1632
1771
 
1633
- }
1772
+ if ( object.isLine ) {
1634
1773
 
1635
- }
1774
+ object.geometry.dispose();
1775
+ object.material.dispose();
1636
1776
 
1637
- switch ( operation ) {
1777
+ }
1638
1778
 
1639
- case 'PAN':
1779
+ } );
1640
1780
 
1641
- state = STATE.PAN;
1642
- break;
1781
+ this._gizmos.clear();
1643
1782
 
1644
- case 'ROTATE':
1783
+ //
1645
1784
 
1646
- state = STATE.ROTATE;
1647
- break;
1785
+ this._gizmos.add( gizmoX );
1786
+ this._gizmos.add( gizmoY );
1787
+ this._gizmos.add( gizmoZ );
1648
1788
 
1649
- case 'ZOOM':
1789
+ }
1650
1790
 
1651
- state = STATE.SCALE;
1652
- break;
1791
+ /**
1792
+ * Perform animation for focus operation
1793
+ * @param {Number} time Instant in which this function is called as performance.now()
1794
+ * @param {Vector3} point Point of interest for focus operation
1795
+ * @param {Matrix4} cameraMatrix Camera matrix
1796
+ * @param {Matrix4} gizmoMatrix Gizmos matrix
1797
+ */
1798
+ onFocusAnim( time, point, cameraMatrix, gizmoMatrix ) {
1653
1799
 
1654
- case 'FOV':
1800
+ if ( this._timeStart == - 1 ) {
1655
1801
 
1656
- state = STATE.FOV;
1657
- break;
1802
+ //animation start
1803
+ this._timeStart = time;
1658
1804
 
1659
1805
  }
1660
1806
 
1661
- const action = {
1807
+ if ( this._state == STATE.ANIMATION_FOCUS ) {
1662
1808
 
1663
- operation: operation,
1664
- mouse: mouse,
1665
- key: key,
1666
- state: state
1809
+ const deltaTime = time - this._timeStart;
1810
+ const animTime = deltaTime / this.focusAnimationTime;
1667
1811
 
1668
- };
1812
+ this._gizmoMatrixState.copy( gizmoMatrix );
1669
1813
 
1670
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1814
+ if ( animTime >= 1 ) {
1671
1815
 
1672
- if ( this.mouseActions[ i ].mouse == action.mouse && this.mouseActions[ i ].key == action.key ) {
1816
+ //animation end
1673
1817
 
1674
- this.mouseActions.splice( i, 1, action );
1675
- return true;
1818
+ this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1676
1819
 
1677
- }
1820
+ this.focus( point, this.scaleFactor );
1678
1821
 
1679
- }
1822
+ this._timeStart = - 1;
1823
+ this.updateTbState( STATE.IDLE, false );
1824
+ this.activateGizmos( false );
1680
1825
 
1681
- this.mouseActions.push( action );
1682
- return true;
1826
+ this.dispatchEvent( _changeEvent );
1683
1827
 
1684
- };
1828
+ } else {
1685
1829
 
1686
- /**
1687
- * Remove a mouse action by specifying its mouse/key combination
1688
- * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
1689
- * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
1690
- * @returns {Boolean} True if the operation has been succesfully removed, false otherwise
1691
- */
1692
- unsetMouseAction = ( mouse, key = null ) => {
1830
+ const amount = this.easeOutCubic( animTime );
1831
+ const size = ( ( 1 - amount ) + ( this.scaleFactor * amount ) );
1693
1832
 
1694
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1833
+ this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1834
+ this.focus( point, size, amount );
1695
1835
 
1696
- if ( this.mouseActions[ i ].mouse == mouse && this.mouseActions[ i ].key == key ) {
1836
+ this.dispatchEvent( _changeEvent );
1837
+ const self = this;
1838
+ this._animationId = window.requestAnimationFrame( function ( t ) {
1697
1839
 
1698
- this.mouseActions.splice( i, 1 );
1699
- return true;
1840
+ self.onFocusAnim( t, point, cameraMatrix, gizmoMatrix.clone() );
1841
+
1842
+ } );
1700
1843
 
1701
1844
  }
1702
1845
 
1703
- }
1846
+ } else {
1704
1847
 
1705
- return false;
1848
+ //interrupt animation
1706
1849
 
1707
- };
1850
+ this._animationId = - 1;
1851
+ this._timeStart = - 1;
1708
1852
 
1709
- /**
1710
- * Return the operation associated to a mouse/keyboard combination
1711
- * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
1712
- * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
1713
- * @returns The operation if it has been found, null otherwise
1714
- */
1715
- getOpFromAction = ( mouse, key ) => {
1716
-
1717
- let action;
1853
+ }
1718
1854
 
1719
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1855
+ }
1720
1856
 
1721
- action = this.mouseActions[ i ];
1722
- if ( action.mouse == mouse && action.key == key ) {
1857
+ /**
1858
+ * Perform animation for rotation operation
1859
+ * @param {Number} time Instant in which this function is called as performance.now()
1860
+ * @param {Vector3} rotationAxis Rotation axis
1861
+ * @param {number} w0 Initial angular velocity
1862
+ */
1863
+ onRotationAnim( time, rotationAxis, w0 ) {
1723
1864
 
1724
- return action.operation;
1865
+ if ( this._timeStart == - 1 ) {
1725
1866
 
1726
- }
1867
+ //animation start
1868
+ this._anglePrev = 0;
1869
+ this._angleCurrent = 0;
1870
+ this._timeStart = time;
1727
1871
 
1728
1872
  }
1729
1873
 
1730
- if ( key != null ) {
1731
-
1732
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1733
-
1734
- action = this.mouseActions[ i ];
1735
- if ( action.mouse == mouse && action.key == null ) {
1736
-
1737
- return action.operation;
1738
-
1739
- }
1874
+ if ( this._state == STATE.ANIMATION_ROTATE ) {
1740
1875
 
1741
- }
1876
+ //w = w0 + alpha * t
1877
+ const deltaTime = ( time - this._timeStart ) / 1000;
1878
+ const w = w0 + ( ( - this.dampingFactor ) * deltaTime );
1742
1879
 
1743
- }
1880
+ if ( w > 0 ) {
1744
1881
 
1745
- return null;
1882
+ //tetha = 0.5 * alpha * t^2 + w0 * t + tetha0
1883
+ this._angleCurrent = 0.5 * ( - this.dampingFactor ) * Math.pow( deltaTime, 2 ) + w0 * deltaTime + 0;
1884
+ this.applyTransformMatrix( this.rotate( rotationAxis, this._angleCurrent ) );
1885
+ this.dispatchEvent( _changeEvent );
1886
+ const self = this;
1887
+ this._animationId = window.requestAnimationFrame( function ( t ) {
1746
1888
 
1747
- };
1889
+ self.onRotationAnim( t, rotationAxis, w0 );
1748
1890
 
1749
- /**
1750
- * Get the operation associated to mouse and key combination and returns the corresponding FSA state
1751
- * @param {Number} mouse Mouse button
1752
- * @param {String} key Keyboard modifier
1753
- * @returns The FSA state obtained from the operation associated to mouse/keyboard combination
1754
- */
1755
- getOpStateFromAction = ( mouse, key ) => {
1891
+ } );
1756
1892
 
1757
- let action;
1893
+ } else {
1758
1894
 
1759
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1895
+ this._animationId = - 1;
1896
+ this._timeStart = - 1;
1760
1897
 
1761
- action = this.mouseActions[ i ];
1762
- if ( action.mouse == mouse && action.key == key ) {
1898
+ this.updateTbState( STATE.IDLE, false );
1899
+ this.activateGizmos( false );
1763
1900
 
1764
- return action.state;
1901
+ this.dispatchEvent( _changeEvent );
1765
1902
 
1766
1903
  }
1767
1904
 
1768
- }
1769
-
1770
- if ( key != null ) {
1905
+ } else {
1771
1906
 
1772
- for ( let i = 0; i < this.mouseActions.length; i ++ ) {
1907
+ //interrupt animation
1773
1908
 
1774
- action = this.mouseActions[ i ];
1775
- if ( action.mouse == mouse && action.key == null ) {
1909
+ this._animationId = - 1;
1910
+ this._timeStart = - 1;
1776
1911
 
1777
- return action.state;
1912
+ if ( this._state != STATE.ROTATE ) {
1778
1913
 
1779
- }
1914
+ this.activateGizmos( false );
1915
+ this.dispatchEvent( _changeEvent );
1780
1916
 
1781
1917
  }
1782
1918
 
1783
1919
  }
1784
1920
 
1785
- return null;
1921
+ }
1786
1922
 
1787
- };
1788
1923
 
1789
1924
  /**
1790
- * Calculate the angle between two pointers
1791
- * @param {PointerEvent} p1
1792
- * @param {PointerEvent} p2
1793
- * @returns {Number} The angle between two pointers in degrees
1925
+ * Perform pan operation moving camera between two points
1926
+ * @param {Vector3} p0 Initial point
1927
+ * @param {Vector3} p1 Ending point
1928
+ * @param {Boolean} adjust If movement should be adjusted considering camera distance (Perspective only)
1794
1929
  */
1795
- getAngle = ( p1, p2 ) => {
1930
+ pan( p0, p1, adjust = false ) {
1796
1931
 
1797
- return Math.atan2( p2.clientY - p1.clientY, p2.clientX - p1.clientX ) * 180 / Math.PI;
1932
+ const movement = p0.clone().sub( p1 );
1798
1933
 
1799
- };
1934
+ if ( this.camera.isOrthographicCamera ) {
1800
1935
 
1801
- /**
1802
- * Update a PointerEvent inside current pointerevents array
1803
- * @param {PointerEvent} event
1804
- */
1805
- updateTouchEvent = ( event ) => {
1936
+ //adjust movement amount
1937
+ movement.multiplyScalar( 1 / this.camera.zoom );
1806
1938
 
1807
- for ( let i = 0; i < this._touchCurrent.length; i ++ ) {
1939
+ } else if ( this.camera.isPerspectiveCamera && adjust ) {
1808
1940
 
1809
- if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
1941
+ //adjust movement amount
1942
+ this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position
1943
+ this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position
1944
+ const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.camera.position.distanceTo( this._gizmos.position );
1945
+ movement.multiplyScalar( 1 / distanceFactor );
1810
1946
 
1811
- this._touchCurrent.splice( i, 1, event );
1812
- break;
1947
+ }
1813
1948
 
1814
- }
1949
+ this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.camera.quaternion );
1815
1950
 
1816
- }
1951
+ this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z );
1952
+
1953
+ this.setTransformationMatrices( this._m4_1, this._m4_1 );
1954
+ return _transformation;
1817
1955
 
1818
- };
1956
+ }
1819
1957
 
1820
1958
  /**
1821
- * Apply a transformation matrix, to the camera and gizmos
1822
- * @param {Object} transformation Object containing matrices to apply to camera and gizmos
1959
+ * Reset trackball
1823
1960
  */
1824
- applyTransformMatrix( transformation ) {
1961
+ reset() {
1825
1962
 
1826
- if ( transformation.camera != null ) {
1963
+ this.camera.zoom = this._zoom0;
1827
1964
 
1828
- this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera );
1829
- this._m4_1.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
1830
- this.camera.updateMatrix();
1965
+ if ( this.camera.isPerspectiveCamera ) {
1831
1966
 
1832
- //update camera up vector
1833
- if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) {
1967
+ this.camera.fov = this._fov0;
1834
1968
 
1835
- this.camera.up.copy( this._upState ).applyQuaternion( this.camera.quaternion );
1969
+ }
1836
1970
 
1837
- }
1971
+ this.camera.near = this._nearPos;
1972
+ this.camera.far = this._farPos;
1973
+ this._cameraMatrixState.copy( this._cameraMatrixState0 );
1974
+ this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
1975
+ this.camera.up.copy( this._up0 );
1838
1976
 
1839
- }
1977
+ this.camera.updateMatrix();
1978
+ this.camera.updateProjectionMatrix();
1840
1979
 
1841
- if ( transformation.gizmos != null ) {
1980
+ this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
1981
+ this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1982
+ this._gizmos.updateMatrix();
1842
1983
 
1843
- this._m4_1.copy( this._gizmoMatrixState ).premultiply( transformation.gizmos );
1844
- this._m4_1.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
1845
- this._gizmos.updateMatrix();
1984
+ this._tbRadius = this.calculateTbRadius( this.camera );
1985
+ this.makeGizmos( this._gizmos.position, this._tbRadius );
1846
1986
 
1847
- }
1987
+ this.camera.lookAt( this._gizmos.position );
1848
1988
 
1849
- if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) {
1989
+ this.updateTbState( STATE.IDLE, false );
1850
1990
 
1851
- this._tbRadius = this.calculateTbRadius( this.camera );
1991
+ this.dispatchEvent( _changeEvent );
1852
1992
 
1853
- if ( this.adjustNearFar ) {
1993
+ }
1854
1994
 
1855
- const cameraDistance = this.camera.position.distanceTo( this._gizmos.position );
1995
+ /**
1996
+ * Rotate the camera around an axis passing by trackball's center
1997
+ * @param {Vector3} axis Rotation axis
1998
+ * @param {number} angle Angle in radians
1999
+ * @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera
2000
+ */
2001
+ rotate( axis, angle ) {
1856
2002
 
1857
- const bb = new Box3();
1858
- bb.setFromObject( this._gizmos );
1859
- const sphere = new Sphere();
1860
- bb.getBoundingSphere( sphere );
2003
+ const point = this._gizmos.position; //rotation center
2004
+ this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
2005
+ this._rotationMatrix.makeRotationAxis( axis, - angle );
1861
2006
 
1862
- const adjustedNearPosition = Math.max( this._nearPos0, sphere.radius + sphere.center.length() );
1863
- const regularNearPosition = cameraDistance - this._initialNear;
2007
+ //rotate camera
2008
+ this._m4_1.makeTranslation( point.x, point.y, point.z );
2009
+ this._m4_1.multiply( this._rotationMatrix );
2010
+ this._m4_1.multiply( this._translationMatrix );
1864
2011
 
1865
- const minNearPos = Math.min( adjustedNearPosition, regularNearPosition );
1866
- this.camera.near = cameraDistance - minNearPos;
2012
+ this.setTransformationMatrices( this._m4_1 );
1867
2013
 
2014
+ return _transformation;
1868
2015
 
1869
- const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() );
1870
- const regularFarPosition = cameraDistance - this._initialFar;
2016
+ }
1871
2017
 
1872
- const minFarPos = Math.min( adjustedFarPosition, regularFarPosition );
1873
- this.camera.far = cameraDistance - minFarPos;
2018
+ copyState() {
1874
2019
 
1875
- this.camera.updateProjectionMatrix();
2020
+ let state;
2021
+ if ( this.camera.isOrthographicCamera ) {
1876
2022
 
1877
- } else {
2023
+ state = JSON.stringify( { arcballState: {
1878
2024
 
1879
- let update = false;
2025
+ cameraFar: this.camera.far,
2026
+ cameraMatrix: this.camera.matrix,
2027
+ cameraNear: this.camera.near,
2028
+ cameraUp: this.camera.up,
2029
+ cameraZoom: this.camera.zoom,
2030
+ gizmoMatrix: this._gizmos.matrix
1880
2031
 
1881
- if ( this.camera.near != this._initialNear ) {
2032
+ } } );
1882
2033
 
1883
- this.camera.near = this._initialNear;
1884
- update = true;
2034
+ } else if ( this.camera.isPerspectiveCamera ) {
1885
2035
 
1886
- }
2036
+ state = JSON.stringify( { arcballState: {
2037
+ cameraFar: this.camera.far,
2038
+ cameraFov: this.camera.fov,
2039
+ cameraMatrix: this.camera.matrix,
2040
+ cameraNear: this.camera.near,
2041
+ cameraUp: this.camera.up,
2042
+ cameraZoom: this.camera.zoom,
2043
+ gizmoMatrix: this._gizmos.matrix
1887
2044
 
1888
- if ( this.camera.far != this._initialFar ) {
2045
+ } } );
1889
2046
 
1890
- this.camera.far = this._initialFar;
1891
- update = true;
2047
+ }
1892
2048
 
1893
- }
2049
+ navigator.clipboard.writeText( state );
1894
2050
 
1895
- if ( update ) {
2051
+ }
1896
2052
 
1897
- this.camera.updateProjectionMatrix();
2053
+ pasteState() {
1898
2054
 
1899
- }
2055
+ const self = this;
2056
+ navigator.clipboard.readText().then( function resolved( value ) {
1900
2057
 
1901
- }
2058
+ self.setStateFromJSON( value );
1902
2059
 
1903
- }
2060
+ } );
1904
2061
 
1905
2062
  }
1906
2063
 
1907
2064
  /**
1908
- * Calculate the angular speed
1909
- * @param {Number} p0 Position at t0
1910
- * @param {Number} p1 Position at t1
1911
- * @param {Number} t0 Initial time in milliseconds
1912
- * @param {Number} t1 Ending time in milliseconds
2065
+ * Save the current state of the control. This can later be recover with .reset
1913
2066
  */
1914
- calculateAngularSpeed = ( p0, p1, t0, t1 ) => {
2067
+ saveState() {
1915
2068
 
1916
- const s = p1 - p0;
1917
- const t = ( t1 - t0 ) / 1000;
1918
- if ( t == 0 ) {
2069
+ this._cameraMatrixState0.copy( this.camera.matrix );
2070
+ this._gizmoMatrixState0.copy( this._gizmos.matrix );
2071
+ this._nearPos = this.camera.near;
2072
+ this._farPos = this.camera.far;
2073
+ this._zoom0 = this.camera.zoom;
2074
+ this._up0.copy( this.camera.up );
1919
2075
 
1920
- return 0;
2076
+ if ( this.camera.isPerspectiveCamera ) {
2077
+
2078
+ this._fov0 = this.camera.fov;
1921
2079
 
1922
2080
  }
1923
2081
 
1924
- return s / t;
1925
-
1926
- };
2082
+ }
1927
2083
 
1928
2084
  /**
1929
- * Calculate the distance between two pointers
1930
- * @param {PointerEvent} p0 The first pointer
1931
- * @param {PointerEvent} p1 The second pointer
1932
- * @returns {number} The distance between the two pointers
2085
+ * Perform uniform scale operation around a given point
2086
+ * @param {Number} size Scale factor
2087
+ * @param {Vector3} point Point around which scale
2088
+ * @param {Boolean} scaleGizmos If gizmos should be scaled (Perspective only)
2089
+ * @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos
1933
2090
  */
1934
- calculatePointersDistance = ( p0, p1 ) => {
2091
+ scale( size, point, scaleGizmos = true ) {
1935
2092
 
1936
- return Math.sqrt( Math.pow( p1.clientX - p0.clientX, 2 ) + Math.pow( p1.clientY - p0.clientY, 2 ) );
2093
+ _scalePointTemp.copy( point );
2094
+ let sizeInverse = 1 / size;
1937
2095
 
1938
- };
2096
+ if ( this.camera.isOrthographicCamera ) {
1939
2097
 
1940
- /**
1941
- * Calculate the rotation axis as the vector perpendicular between two vectors
1942
- * @param {Vector3} vec1 The first vector
1943
- * @param {Vector3} vec2 The second vector
1944
- * @returns {Vector3} The normalized rotation axis
1945
- */
1946
- calculateRotationAxis = ( vec1, vec2 ) => {
2098
+ //camera zoom
2099
+ this.camera.zoom = this._zoomState;
2100
+ this.camera.zoom *= size;
1947
2101
 
1948
- this._rotationMatrix.extractRotation( this._cameraMatrixState );
1949
- this._quat.setFromRotationMatrix( this._rotationMatrix );
2102
+ //check min and max zoom
2103
+ if ( this.camera.zoom > this.maxZoom ) {
1950
2104
 
1951
- this._rotationAxis.crossVectors( vec1, vec2 ).applyQuaternion( this._quat );
1952
- return this._rotationAxis.normalize().clone();
2105
+ this.camera.zoom = this.maxZoom;
2106
+ sizeInverse = this._zoomState / this.maxZoom;
1953
2107
 
1954
- };
2108
+ } else if ( this.camera.zoom < this.minZoom ) {
1955
2109
 
1956
- /**
1957
- * Calculate the trackball radius so that gizmo's diamater will be 2/3 of the minimum side of the camera frustum
1958
- * @param {Camera} camera
1959
- * @returns {Number} The trackball radius
1960
- */
1961
- calculateTbRadius = ( camera ) => {
2110
+ this.camera.zoom = this.minZoom;
2111
+ sizeInverse = this._zoomState / this.minZoom;
1962
2112
 
1963
- const distance = camera.position.distanceTo( this._gizmos.position );
2113
+ }
1964
2114
 
1965
- if ( camera.type == 'PerspectiveCamera' ) {
2115
+ this.camera.updateProjectionMatrix();
1966
2116
 
1967
- const halfFovV = MathUtils.DEG2RAD * camera.fov * 0.5; //vertical fov/2 in radians
1968
- const halfFovH = Math.atan( ( camera.aspect ) * Math.tan( halfFovV ) ); //horizontal fov/2 in radians
1969
- return Math.tan( Math.min( halfFovV, halfFovH ) ) * distance * this.radiusFactor;
2117
+ this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position
1970
2118
 
1971
- } else if ( camera.type == 'OrthographicCamera' ) {
2119
+ //scale gizmos so they appear in the same spot having the same dimension
2120
+ this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
2121
+ this._translationMatrix.makeTranslation( - this._v3_1.x, - this._v3_1.y, - this._v3_1.z );
1972
2122
 
1973
- return Math.min( camera.top, camera.right ) * this.radiusFactor;
2123
+ this._m4_2.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ).multiply( this._scaleMatrix );
2124
+ this._m4_2.multiply( this._translationMatrix );
1974
2125
 
1975
- }
1976
2126
 
1977
- };
2127
+ //move camera and gizmos to obtain pinch effect
2128
+ _scalePointTemp.sub( this._v3_1 );
1978
2129
 
1979
- /**
1980
- * Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in
1981
- * @param {Vector3} point The point of interest
1982
- * @param {Number} size Scale factor
1983
- * @param {Number} amount Amount of operation to be completed (used for focus animations, default is complete full operation)
1984
- */
1985
- focus = ( point, size, amount = 1 ) => {
2130
+ const amount = _scalePointTemp.clone().multiplyScalar( sizeInverse );
2131
+ _scalePointTemp.sub( amount );
1986
2132
 
1987
- //move center of camera (along with gizmos) towards point of interest
1988
- _offset.copy( point ).sub( this._gizmos.position ).multiplyScalar( amount );
1989
- this._translationMatrix.makeTranslation( _offset.x, _offset.y, _offset.z );
2133
+ this._m4_1.makeTranslation( _scalePointTemp.x, _scalePointTemp.y, _scalePointTemp.z );
2134
+ this._m4_2.premultiply( this._m4_1 );
1990
2135
 
1991
- _gizmoMatrixStateTemp.copy( this._gizmoMatrixState );
1992
- this._gizmoMatrixState.premultiply( this._translationMatrix );
1993
- this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
2136
+ this.setTransformationMatrices( this._m4_1, this._m4_2 );
2137
+ return _transformation;
1994
2138
 
1995
- _cameraMatrixStateTemp.copy( this._cameraMatrixState );
1996
- this._cameraMatrixState.premultiply( this._translationMatrix );
1997
- this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
2139
+ } else if ( this.camera.isPerspectiveCamera ) {
1998
2140
 
1999
- //apply zoom
2000
- if ( this.enableZoom ) {
2141
+ this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
2142
+ this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
2001
2143
 
2002
- this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
2144
+ //move camera
2145
+ let distance = this._v3_1.distanceTo( _scalePointTemp );
2146
+ let amount = distance - ( distance * sizeInverse );
2003
2147
 
2004
- }
2148
+ //check min and max distance
2149
+ const newDistance = distance - amount;
2150
+ if ( newDistance < this.minDistance ) {
2005
2151
 
2006
- this._gizmoMatrixState.copy( _gizmoMatrixStateTemp );
2007
- this._cameraMatrixState.copy( _cameraMatrixStateTemp );
2152
+ sizeInverse = this.minDistance / distance;
2153
+ amount = distance - ( distance * sizeInverse );
2008
2154
 
2009
- };
2155
+ } else if ( newDistance > this.maxDistance ) {
2010
2156
 
2011
- /**
2012
- * Draw a grid and add it to the scene
2013
- */
2014
- drawGrid = () => {
2157
+ sizeInverse = this.maxDistance / distance;
2158
+ amount = distance - ( distance * sizeInverse );
2015
2159
 
2016
- if ( this.scene != null ) {
2160
+ }
2017
2161
 
2018
- const color = 0x888888;
2019
- const multiplier = 3;
2020
- let size, divisions, maxLength, tick;
2162
+ _offset.copy( _scalePointTemp ).sub( this._v3_1 ).normalize().multiplyScalar( amount );
2021
2163
 
2022
- if ( this.camera.isOrthographicCamera ) {
2164
+ this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
2023
2165
 
2024
- const width = this.camera.right - this.camera.left;
2025
- const height = this.camera.bottom - this.camera.top;
2026
2166
 
2027
- maxLength = Math.max( width, height );
2028
- tick = maxLength / 20;
2167
+ if ( scaleGizmos ) {
2029
2168
 
2030
- size = maxLength / this.camera.zoom * multiplier;
2031
- divisions = size / tick * this.camera.zoom;
2169
+ //scale gizmos so they appear in the same spot having the same dimension
2170
+ const pos = this._v3_2;
2032
2171
 
2033
- } else if ( this.camera.isPerspectiveCamera ) {
2172
+ distance = pos.distanceTo( _scalePointTemp );
2173
+ amount = distance - ( distance * sizeInverse );
2174
+ _offset.copy( _scalePointTemp ).sub( this._v3_2 ).normalize().multiplyScalar( amount );
2034
2175
 
2035
- const distance = this.camera.position.distanceTo( this._gizmos.position );
2036
- const halfFovV = MathUtils.DEG2RAD * this.camera.fov * 0.5;
2037
- const halfFovH = Math.atan( ( this.camera.aspect ) * Math.tan( halfFovV ) );
2176
+ this._translationMatrix.makeTranslation( pos.x, pos.y, pos.z );
2177
+ this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
2038
2178
 
2039
- maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2;
2040
- tick = maxLength / 20;
2179
+ this._m4_2.makeTranslation( _offset.x, _offset.y, _offset.z ).multiply( this._translationMatrix );
2180
+ this._m4_2.multiply( this._scaleMatrix );
2041
2181
 
2042
- size = maxLength * multiplier;
2043
- divisions = size / tick;
2182
+ this._translationMatrix.makeTranslation( - pos.x, - pos.y, - pos.z );
2044
2183
 
2045
- }
2184
+ this._m4_2.multiply( this._translationMatrix );
2185
+ this.setTransformationMatrices( this._m4_1, this._m4_2 );
2046
2186
 
2047
- if ( this._grid == null ) {
2048
2187
 
2049
- this._grid = new GridHelper( size, divisions, color, color );
2050
- this._grid.position.copy( this._gizmos.position );
2051
- this._gridPosition.copy( this._grid.position );
2052
- this._grid.quaternion.copy( this.camera.quaternion );
2053
- this._grid.rotateX( Math.PI * 0.5 );
2188
+ } else {
2054
2189
 
2055
- this.scene.add( this._grid );
2190
+ this.setTransformationMatrices( this._m4_1 );
2056
2191
 
2057
2192
  }
2058
2193
 
2194
+ return _transformation;
2195
+
2059
2196
  }
2060
2197
 
2061
- };
2198
+ }
2062
2199
 
2063
2200
  /**
2064
- * Remove all listeners, stop animations and clean scene
2201
+ * Set camera fov
2202
+ * @param {Number} value fov to be setted
2065
2203
  */
2066
- dispose = () => {
2204
+ setFov( value ) {
2067
2205
 
2068
- if ( this._animationId != - 1 ) {
2206
+ if ( this.camera.isPerspectiveCamera ) {
2069
2207
 
2070
- window.cancelAnimationFrame( this._animationId );
2208
+ this.camera.fov = MathUtils.clamp( value, this.minFov, this.maxFov );
2209
+ this.camera.updateProjectionMatrix();
2071
2210
 
2072
2211
  }
2073
2212
 
2074
- this.domElement.removeEventListener( 'pointerdown', this.onPointerDown );
2075
- this.domElement.removeEventListener( 'pointercancel', this.onPointerCancel );
2076
- this.domElement.removeEventListener( 'wheel', this.onWheel );
2077
- this.domElement.removeEventListener( 'contextmenu', this.onContextMenu );
2213
+ }
2078
2214
 
2079
- window.removeEventListener( 'pointermove', this.onPointerMove );
2080
- window.removeEventListener( 'pointerup', this.onPointerUp );
2215
+ /**
2216
+ * Set values in transformation object
2217
+ * @param {Matrix4} camera Transformation to be applied to the camera
2218
+ * @param {Matrix4} gizmos Transformation to be applied to gizmos
2219
+ */
2220
+ setTransformationMatrices( camera = null, gizmos = null ) {
2081
2221
 
2082
- window.removeEventListener( 'resize', this.onWindowResize );
2222
+ if ( camera != null ) {
2083
2223
 
2084
- if ( this.scene !== null ) this.scene.remove( this._gizmos );
2085
- this.disposeGrid();
2224
+ if ( _transformation.camera != null ) {
2086
2225
 
2087
- };
2226
+ _transformation.camera.copy( camera );
2088
2227
 
2089
- /**
2090
- * remove the grid from the scene
2091
- */
2092
- disposeGrid = () => {
2228
+ } else {
2093
2229
 
2094
- if ( this._grid != null && this.scene != null ) {
2230
+ _transformation.camera = camera.clone();
2095
2231
 
2096
- this.scene.remove( this._grid );
2097
- this._grid = null;
2232
+ }
2098
2233
 
2099
- }
2234
+ } else {
2100
2235
 
2101
- };
2236
+ _transformation.camera = null;
2102
2237
 
2103
- /**
2104
- * Compute the easing out cubic function for ease out effect in animation
2105
- * @param {Number} t The absolute progress of the animation in the bound of 0 (beginning of the) and 1 (ending of animation)
2106
- * @returns {Number} Result of easing out cubic at time t
2107
- */
2108
- easeOutCubic = ( t ) => {
2238
+ }
2109
2239
 
2110
- return 1 - Math.pow( 1 - t, 3 );
2240
+ if ( gizmos != null ) {
2111
2241
 
2112
- };
2242
+ if ( _transformation.gizmos != null ) {
2113
2243
 
2114
- /**
2115
- * Make rotation gizmos more or less visible
2116
- * @param {Boolean} isActive If true, make gizmos more visible
2117
- */
2118
- activateGizmos = ( isActive ) => {
2244
+ _transformation.gizmos.copy( gizmos );
2119
2245
 
2120
- const gizmoX = this._gizmos.children[ 0 ];
2121
- const gizmoY = this._gizmos.children[ 1 ];
2122
- const gizmoZ = this._gizmos.children[ 2 ];
2246
+ } else {
2123
2247
 
2124
- if ( isActive ) {
2248
+ _transformation.gizmos = gizmos.clone();
2125
2249
 
2126
- gizmoX.material.setValues( { opacity: 1 } );
2127
- gizmoY.material.setValues( { opacity: 1 } );
2128
- gizmoZ.material.setValues( { opacity: 1 } );
2250
+ }
2129
2251
 
2130
2252
  } else {
2131
2253
 
2132
- gizmoX.material.setValues( { opacity: 0.6 } );
2133
- gizmoY.material.setValues( { opacity: 0.6 } );
2134
- gizmoZ.material.setValues( { opacity: 0.6 } );
2254
+ _transformation.gizmos = null;
2135
2255
 
2136
2256
  }
2137
2257
 
2138
- };
2258
+ }
2139
2259
 
2140
2260
  /**
2141
- * Calculate the cursor position in NDC
2142
- * @param {number} x Cursor horizontal coordinate within the canvas
2143
- * @param {number} y Cursor vertical coordinate within the canvas
2144
- * @param {HTMLElement} canvas The canvas where the renderer draws its output
2145
- * @returns {Vector2} Cursor normalized position inside the canvas
2261
+ * Rotate camera around its direction axis passing by a given point by a given angle
2262
+ * @param {Vector3} point The point where the rotation axis is passing trough
2263
+ * @param {Number} angle Angle in radians
2264
+ * @returns The computed transormation matix
2146
2265
  */
2147
- getCursorNDC = ( cursorX, cursorY, canvas ) => {
2266
+ zRotate( point, angle ) {
2148
2267
 
2149
- const canvasRect = canvas.getBoundingClientRect();
2150
- this._v2_1.setX( ( ( cursorX - canvasRect.left ) / canvasRect.width ) * 2 - 1 );
2151
- this._v2_1.setY( ( ( canvasRect.bottom - cursorY ) / canvasRect.height ) * 2 - 1 );
2152
- return this._v2_1.clone();
2268
+ this._rotationMatrix.makeRotationAxis( this._rotationAxis, angle );
2269
+ this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
2153
2270
 
2154
- };
2271
+ this._m4_1.makeTranslation( point.x, point.y, point.z );
2272
+ this._m4_1.multiply( this._rotationMatrix );
2273
+ this._m4_1.multiply( this._translationMatrix );
2155
2274
 
2156
- /**
2157
- * Calculate the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas
2158
- * @param {Number} x Cursor horizontal coordinate within the canvas
2159
- * @param {Number} y Cursor vertical coordinate within the canvas
2160
- * @param {HTMLElement} canvas The canvas where the renderer draws its output
2161
- * @returns {Vector2} Cursor position inside the canvas
2162
- */
2163
- getCursorPosition = ( cursorX, cursorY, canvas ) => {
2275
+ this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ).sub( point ); //vector from rotation center to gizmos position
2276
+ this._v3_2.copy( this._v3_1 ).applyAxisAngle( this._rotationAxis, angle ); //apply rotation
2277
+ this._v3_2.sub( this._v3_1 );
2164
2278
 
2165
- this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
2166
- this._v2_1.x *= ( this.camera.right - this.camera.left ) * 0.5;
2167
- this._v2_1.y *= ( this.camera.top - this.camera.bottom ) * 0.5;
2168
- return this._v2_1.clone();
2279
+ this._m4_2.makeTranslation( this._v3_2.x, this._v3_2.y, this._v3_2.z );
2169
2280
 
2170
- };
2281
+ this.setTransformationMatrices( this._m4_1, this._m4_2 );
2282
+ return _transformation;
2171
2283
 
2172
- /**
2173
- * Set the camera to be controlled
2174
- * @param {Camera} camera The virtual camera to be controlled
2175
- */
2176
- setCamera = ( camera ) => {
2284
+ }
2177
2285
 
2178
- camera.lookAt( this.target );
2179
- camera.updateMatrix();
2180
2286
 
2181
- //setting state
2182
- if ( camera.type == 'PerspectiveCamera' ) {
2287
+ getRaycaster() {
2183
2288
 
2184
- this._fov0 = camera.fov;
2185
- this._fovState = camera.fov;
2289
+ return _raycaster;
2186
2290
 
2187
- }
2291
+ }
2188
2292
 
2189
- this._cameraMatrixState0.copy( camera.matrix );
2190
- this._cameraMatrixState.copy( this._cameraMatrixState0 );
2191
- this._cameraProjectionState.copy( camera.projectionMatrix );
2192
- this._zoom0 = camera.zoom;
2193
- this._zoomState = this._zoom0;
2194
2293
 
2195
- this._initialNear = camera.near;
2196
- this._nearPos0 = camera.position.distanceTo( this.target ) - camera.near;
2197
- this._nearPos = this._initialNear;
2294
+ /**
2295
+ * Unproject the cursor on the 3D object surface
2296
+ * @param {Vector2} cursor Cursor coordinates in NDC
2297
+ * @param {Camera} camera Virtual camera
2298
+ * @returns {Vector3} The point of intersection with the model, if exist, null otherwise
2299
+ */
2300
+ unprojectOnObj( cursor, camera ) {
2198
2301
 
2199
- this._initialFar = camera.far;
2200
- this._farPos0 = camera.position.distanceTo( this.target ) - camera.far;
2201
- this._farPos = this._initialFar;
2302
+ const raycaster = this.getRaycaster();
2303
+ raycaster.near = camera.near;
2304
+ raycaster.far = camera.far;
2305
+ raycaster.setFromCamera( cursor, camera );
2202
2306
 
2203
- this._up0.copy( camera.up );
2204
- this._upState.copy( camera.up );
2307
+ const intersect = raycaster.intersectObjects( this.scene.children, true );
2205
2308
 
2206
- this.camera = camera;
2207
- this.camera.updateProjectionMatrix();
2309
+ for ( let i = 0; i < intersect.length; i ++ ) {
2208
2310
 
2209
- //making gizmos
2210
- this._tbRadius = this.calculateTbRadius( camera );
2211
- this.makeGizmos( this.target, this._tbRadius );
2311
+ if ( intersect[ i ].object.uuid != this._gizmos.uuid && intersect[ i ].face != null ) {
2212
2312
 
2213
- };
2313
+ return intersect[ i ].point.clone();
2214
2314
 
2215
- /**
2216
- * Set gizmos visibility
2217
- * @param {Boolean} value Value of gizmos visibility
2218
- */
2219
- setGizmosVisible( value ) {
2315
+ }
2220
2316
 
2221
- this._gizmos.visible = value;
2222
- this.dispatchEvent( _changeEvent );
2317
+ }
2318
+
2319
+ return null;
2223
2320
 
2224
2321
  }
2225
2322
 
2226
2323
  /**
2227
- * Set gizmos radius factor and redraws gizmos
2228
- * @param {Float} value Value of radius factor
2324
+ * Unproject the cursor on the trackball surface
2325
+ * @param {Camera} camera The virtual camera
2326
+ * @param {Number} cursorX Cursor horizontal coordinate on screen
2327
+ * @param {Number} cursorY Cursor vertical coordinate on screen
2328
+ * @param {HTMLElement} canvas The canvas where the renderer draws its output
2329
+ * @param {number} tbRadius The trackball radius
2330
+ * @returns {Vector3} The unprojected point on the trackball surface
2229
2331
  */
2230
- setTbRadius( value ) {
2231
-
2232
- this.radiusFactor = value;
2233
- this._tbRadius = this.calculateTbRadius( this.camera );
2234
-
2235
- const curve = new EllipseCurve( 0, 0, this._tbRadius, this._tbRadius );
2236
- const points = curve.getPoints( this._curvePts );
2237
- const curveGeometry = new BufferGeometry().setFromPoints( points );
2332
+ unprojectOnTbSurface( camera, cursorX, cursorY, canvas, tbRadius ) {
2238
2333
 
2334
+ if ( camera.type == 'OrthographicCamera' ) {
2239
2335
 
2240
- for ( const gizmo in this._gizmos.children ) {
2336
+ this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
2337
+ this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
2241
2338
 
2242
- this._gizmos.children[ gizmo ].geometry = curveGeometry;
2339
+ const x2 = Math.pow( this._v2_1.x, 2 );
2340
+ const y2 = Math.pow( this._v2_1.y, 2 );
2341
+ const r2 = Math.pow( this._tbRadius, 2 );
2243
2342
 
2244
- }
2343
+ if ( x2 + y2 <= r2 * 0.5 ) {
2245
2344
 
2246
- this.dispatchEvent( _changeEvent );
2345
+ //intersection with sphere
2346
+ this._v3_1.setZ( Math.sqrt( r2 - ( x2 + y2 ) ) );
2247
2347
 
2248
- }
2348
+ } else {
2249
2349
 
2250
- /**
2251
- * Creates the rotation gizmos matching trackball center and radius
2252
- * @param {Vector3} tbCenter The trackball center
2253
- * @param {number} tbRadius The trackball radius
2254
- */
2255
- makeGizmos = ( tbCenter, tbRadius ) => {
2350
+ //intersection with hyperboloid
2351
+ this._v3_1.setZ( ( r2 * 0.5 ) / ( Math.sqrt( x2 + y2 ) ) );
2256
2352
 
2257
- const curve = new EllipseCurve( 0, 0, tbRadius, tbRadius );
2258
- const points = curve.getPoints( this._curvePts );
2353
+ }
2259
2354
 
2260
- //geometry
2261
- const curveGeometry = new BufferGeometry().setFromPoints( points );
2355
+ return this._v3_1;
2262
2356
 
2263
- //material
2264
- const curveMaterialX = new LineBasicMaterial( { color: 0xff8080, fog: false, transparent: true, opacity: 0.6 } );
2265
- const curveMaterialY = new LineBasicMaterial( { color: 0x80ff80, fog: false, transparent: true, opacity: 0.6 } );
2266
- const curveMaterialZ = new LineBasicMaterial( { color: 0x8080ff, fog: false, transparent: true, opacity: 0.6 } );
2357
+ } else if ( camera.type == 'PerspectiveCamera' ) {
2267
2358
 
2268
- //line
2269
- const gizmoX = new Line( curveGeometry, curveMaterialX );
2270
- const gizmoY = new Line( curveGeometry, curveMaterialY );
2271
- const gizmoZ = new Line( curveGeometry, curveMaterialZ );
2359
+ //unproject cursor on the near plane
2360
+ this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
2272
2361
 
2273
- const rotation = Math.PI * 0.5;
2274
- gizmoX.rotation.x = rotation;
2275
- gizmoY.rotation.y = rotation;
2362
+ this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
2363
+ this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
2276
2364
 
2365
+ const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
2366
+ const cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
2367
+ const radius2 = Math.pow( tbRadius, 2 );
2277
2368
 
2278
- //setting state
2279
- this._gizmoMatrixState0.identity().setPosition( tbCenter );
2280
- this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
2369
+ // camera
2370
+ // |\
2371
+ // | \
2372
+ // | \
2373
+ // h | \
2374
+ // | \
2375
+ // | \
2376
+ // _ _ | _ _ _\ _ _ near plane
2377
+ // l
2281
2378
 
2282
- if ( this.camera.zoom !== 1 ) {
2379
+ const h = this._v3_1.z;
2380
+ const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
2283
2381
 
2284
- //adapt gizmos size to camera zoom
2285
- const size = 1 / this.camera.zoom;
2286
- this._scaleMatrix.makeScale( size, size, size );
2287
- this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z );
2382
+ if ( l == 0 ) {
2288
2383
 
2289
- this._gizmoMatrixState.premultiply( this._translationMatrix ).premultiply( this._scaleMatrix );
2290
- this._translationMatrix.makeTranslation( tbCenter.x, tbCenter.y, tbCenter.z );
2291
- this._gizmoMatrixState.premultiply( this._translationMatrix );
2384
+ //ray aligned with camera
2385
+ rayDir.set( this._v3_1.x, this._v3_1.y, tbRadius );
2386
+ return rayDir;
2292
2387
 
2293
- }
2388
+ }
2294
2389
 
2295
- this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
2390
+ const m = h / l;
2391
+ const q = cameraGizmoDistance;
2296
2392
 
2297
- //
2393
+ /*
2394
+ * calculate intersection point between unprojected ray and trackball surface
2395
+ *|y = m * x + q
2396
+ *|x^2 + y^2 = r^2
2397
+ *
2398
+ * (m^2 + 1) * x^2 + (2 * m * q) * x + q^2 - r^2 = 0
2399
+ */
2400
+ let a = Math.pow( m, 2 ) + 1;
2401
+ let b = 2 * m * q;
2402
+ let c = Math.pow( q, 2 ) - radius2;
2403
+ let delta = Math.pow( b, 2 ) - ( 4 * a * c );
2298
2404
 
2299
- this._gizmos.traverse( function ( object ) {
2405
+ if ( delta >= 0 ) {
2300
2406
 
2301
- if ( object.isLine ) {
2407
+ //intersection with sphere
2408
+ this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
2409
+ this._v2_1.setY( m * this._v2_1.x + q );
2302
2410
 
2303
- object.geometry.dispose();
2304
- object.material.dispose();
2411
+ const angle = MathUtils.RAD2DEG * this._v2_1.angle();
2305
2412
 
2306
- }
2413
+ if ( angle >= 45 ) {
2307
2414
 
2308
- } );
2415
+ //if angle between intersection point and X' axis is >= 45°, return that point
2416
+ //otherwise, calculate intersection point with hyperboloid
2309
2417
 
2310
- this._gizmos.clear();
2418
+ const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) );
2419
+ rayDir.multiplyScalar( rayLength );
2420
+ rayDir.z += cameraGizmoDistance;
2421
+ return rayDir;
2311
2422
 
2312
- //
2423
+ }
2313
2424
 
2314
- this._gizmos.add( gizmoX );
2315
- this._gizmos.add( gizmoY );
2316
- this._gizmos.add( gizmoZ );
2425
+ }
2317
2426
 
2318
- };
2427
+ //intersection with hyperboloid
2428
+ /*
2429
+ *|y = m * x + q
2430
+ *|y = (1 / x) * (r^2 / 2)
2431
+ *
2432
+ * m * x^2 + q * x - r^2 / 2 = 0
2433
+ */
2319
2434
 
2320
- /**
2321
- * Perform animation for focus operation
2322
- * @param {Number} time Instant in which this function is called as performance.now()
2323
- * @param {Vector3} point Point of interest for focus operation
2324
- * @param {Matrix4} cameraMatrix Camera matrix
2325
- * @param {Matrix4} gizmoMatrix Gizmos matrix
2326
- */
2327
- onFocusAnim = ( time, point, cameraMatrix, gizmoMatrix ) => {
2435
+ a = m;
2436
+ b = q;
2437
+ c = - radius2 * 0.5;
2438
+ delta = Math.pow( b, 2 ) - ( 4 * a * c );
2439
+ this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
2440
+ this._v2_1.setY( m * this._v2_1.x + q );
2328
2441
 
2329
- if ( this._timeStart == - 1 ) {
2442
+ const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) );
2330
2443
 
2331
- //animation start
2332
- this._timeStart = time;
2444
+ rayDir.multiplyScalar( rayLength );
2445
+ rayDir.z += cameraGizmoDistance;
2446
+ return rayDir;
2333
2447
 
2334
2448
  }
2335
2449
 
2336
- if ( this._state == STATE.ANIMATION_FOCUS ) {
2450
+ }
2337
2451
 
2338
- const deltaTime = time - this._timeStart;
2339
- const animTime = deltaTime / this.focusAnimationTime;
2340
2452
 
2341
- this._gizmoMatrixState.copy( gizmoMatrix );
2453
+ /**
2454
+ * Unproject the cursor on the plane passing through the center of the trackball orthogonal to the camera
2455
+ * @param {Camera} camera The virtual camera
2456
+ * @param {Number} cursorX Cursor horizontal coordinate on screen
2457
+ * @param {Number} cursorY Cursor vertical coordinate on screen
2458
+ * @param {HTMLElement} canvas The canvas where the renderer draws its output
2459
+ * @param {Boolean} initialDistance If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only)
2460
+ * @returns {Vector3} The unprojected point on the trackball plane
2461
+ */
2462
+ unprojectOnTbPlane( camera, cursorX, cursorY, canvas, initialDistance = false ) {
2342
2463
 
2343
- if ( animTime >= 1 ) {
2464
+ if ( camera.type == 'OrthographicCamera' ) {
2344
2465
 
2345
- //animation end
2466
+ this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
2467
+ this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
2346
2468
 
2347
- this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
2469
+ return this._v3_1.clone();
2348
2470
 
2349
- this.focus( point, this.scaleFactor );
2471
+ } else if ( camera.type == 'PerspectiveCamera' ) {
2350
2472
 
2351
- this._timeStart = - 1;
2352
- this.updateTbState( STATE.IDLE, false );
2353
- this.activateGizmos( false );
2473
+ this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
2354
2474
 
2355
- this.dispatchEvent( _changeEvent );
2475
+ //unproject cursor on the near plane
2476
+ this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
2477
+ this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
2356
2478
 
2357
- } else {
2479
+ const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
2358
2480
 
2359
- const amount = this.easeOutCubic( animTime );
2360
- const size = ( ( 1 - amount ) + ( this.scaleFactor * amount ) );
2481
+ // camera
2482
+ // |\
2483
+ // | \
2484
+ // | \
2485
+ // h | \
2486
+ // | \
2487
+ // | \
2488
+ // _ _ | _ _ _\ _ _ near plane
2489
+ // l
2361
2490
 
2362
- this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
2363
- this.focus( point, size, amount );
2491
+ const h = this._v3_1.z;
2492
+ const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
2493
+ let cameraGizmoDistance;
2364
2494
 
2365
- this.dispatchEvent( _changeEvent );
2366
- const self = this;
2367
- this._animationId = window.requestAnimationFrame( function ( t ) {
2495
+ if ( initialDistance ) {
2368
2496
 
2369
- self.onFocusAnim( t, point, cameraMatrix, gizmoMatrix.clone() );
2497
+ cameraGizmoDistance = this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ).distanceTo( this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ) );
2370
2498
 
2371
- } );
2499
+ } else {
2500
+
2501
+ cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
2372
2502
 
2373
2503
  }
2374
2504
 
2375
- } else {
2505
+ /*
2506
+ * calculate intersection point between unprojected ray and the plane
2507
+ *|y = mx + q
2508
+ *|y = 0
2509
+ *
2510
+ * x = -q/m
2511
+ */
2512
+ if ( l == 0 ) {
2376
2513
 
2377
- //interrupt animation
2514
+ //ray aligned with camera
2515
+ rayDir.set( 0, 0, 0 );
2516
+ return rayDir;
2378
2517
 
2379
- this._animationId = - 1;
2380
- this._timeStart = - 1;
2518
+ }
2519
+
2520
+ const m = h / l;
2521
+ const q = cameraGizmoDistance;
2522
+ const x = - q / m;
2523
+
2524
+ const rayLength = Math.sqrt( Math.pow( q, 2 ) + Math.pow( x, 2 ) );
2525
+ rayDir.multiplyScalar( rayLength );
2526
+ rayDir.z = 0;
2527
+ return rayDir;
2381
2528
 
2382
2529
  }
2383
2530
 
2384
- };
2531
+ }
2385
2532
 
2386
2533
  /**
2387
- * Perform animation for rotation operation
2388
- * @param {Number} time Instant in which this function is called as performance.now()
2389
- * @param {Vector3} rotationAxis Rotation axis
2390
- * @param {number} w0 Initial angular velocity
2534
+ * Update camera and gizmos state
2391
2535
  */
2392
- onRotationAnim = ( time, rotationAxis, w0 ) => {
2536
+ updateMatrixState() {
2393
2537
 
2394
- if ( this._timeStart == - 1 ) {
2538
+ //update camera and gizmos state
2539
+ this._cameraMatrixState.copy( this.camera.matrix );
2540
+ this._gizmoMatrixState.copy( this._gizmos.matrix );
2395
2541
 
2396
- //animation start
2397
- this._anglePrev = 0;
2398
- this._angleCurrent = 0;
2399
- this._timeStart = time;
2542
+ if ( this.camera.isOrthographicCamera ) {
2400
2543
 
2401
- }
2544
+ this._cameraProjectionState.copy( this.camera.projectionMatrix );
2545
+ this.camera.updateProjectionMatrix();
2546
+ this._zoomState = this.camera.zoom;
2402
2547
 
2403
- if ( this._state == STATE.ANIMATION_ROTATE ) {
2548
+ } else if ( this.camera.isPerspectiveCamera ) {
2404
2549
 
2405
- //w = w0 + alpha * t
2406
- const deltaTime = ( time - this._timeStart ) / 1000;
2407
- const w = w0 + ( ( - this.dampingFactor ) * deltaTime );
2550
+ this._fovState = this.camera.fov;
2408
2551
 
2409
- if ( w > 0 ) {
2552
+ }
2410
2553
 
2411
- //tetha = 0.5 * alpha * t^2 + w0 * t + tetha0
2412
- this._angleCurrent = 0.5 * ( - this.dampingFactor ) * Math.pow( deltaTime, 2 ) + w0 * deltaTime + 0;
2413
- this.applyTransformMatrix( this.rotate( rotationAxis, this._angleCurrent ) );
2414
- this.dispatchEvent( _changeEvent );
2415
- const self = this;
2416
- this._animationId = window.requestAnimationFrame( function ( t ) {
2554
+ }
2417
2555
 
2418
- self.onRotationAnim( t, rotationAxis, w0 );
2556
+ /**
2557
+ * Update the trackball FSA
2558
+ * @param {STATE} newState New state of the FSA
2559
+ * @param {Boolean} updateMatrices If matriices state should be updated
2560
+ */
2561
+ updateTbState( newState, updateMatrices ) {
2419
2562
 
2420
- } );
2563
+ this._state = newState;
2564
+ if ( updateMatrices ) {
2421
2565
 
2422
- } else {
2566
+ this.updateMatrixState();
2423
2567
 
2424
- this._animationId = - 1;
2425
- this._timeStart = - 1;
2568
+ }
2426
2569
 
2427
- this.updateTbState( STATE.IDLE, false );
2428
- this.activateGizmos( false );
2570
+ }
2429
2571
 
2430
- this.dispatchEvent( _changeEvent );
2572
+ update() {
2431
2573
 
2432
- }
2574
+ const EPS = 0.000001;
2433
2575
 
2434
- } else {
2576
+ if ( this.target.equals( this._currentTarget ) === false ) {
2435
2577
 
2436
- //interrupt animation
2578
+ this._gizmos.position.copy( this.target ); //for correct radius calculation
2579
+ this._tbRadius = this.calculateTbRadius( this.camera );
2580
+ this.makeGizmos( this.target, this._tbRadius );
2581
+ this._currentTarget.copy( this.target );
2437
2582
 
2438
- this._animationId = - 1;
2439
- this._timeStart = - 1;
2583
+ }
2440
2584
 
2441
- if ( this._state != STATE.ROTATE ) {
2585
+ //check min/max parameters
2586
+ if ( this.camera.isOrthographicCamera ) {
2442
2587
 
2443
- this.activateGizmos( false );
2444
- this.dispatchEvent( _changeEvent );
2588
+ //check zoom
2589
+ if ( this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom ) {
2590
+
2591
+ const newZoom = MathUtils.clamp( this.camera.zoom, this.minZoom, this.maxZoom );
2592
+ this.applyTransformMatrix( this.scale( newZoom / this.camera.zoom, this._gizmos.position, true ) );
2445
2593
 
2446
2594
  }
2447
2595
 
2448
- }
2596
+ } else if ( this.camera.isPerspectiveCamera ) {
2449
2597
 
2450
- };
2598
+ //check distance
2599
+ const distance = this.camera.position.distanceTo( this._gizmos.position );
2451
2600
 
2601
+ if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) {
2452
2602
 
2453
- /**
2454
- * Perform pan operation moving camera between two points
2455
- * @param {Vector3} p0 Initial point
2456
- * @param {Vector3} p1 Ending point
2457
- * @param {Boolean} adjust If movement should be adjusted considering camera distance (Perspective only)
2458
- */
2459
- pan = ( p0, p1, adjust = false ) => {
2603
+ const newDistance = MathUtils.clamp( distance, this.minDistance, this.maxDistance );
2604
+ this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) );
2605
+ this.updateMatrixState();
2460
2606
 
2461
- const movement = p0.clone().sub( p1 );
2607
+ }
2462
2608
 
2463
- if ( this.camera.isOrthographicCamera ) {
2609
+ //check fov
2610
+ if ( this.camera.fov < this.minFov || this.camera.fov > this.maxFov ) {
2464
2611
 
2465
- //adjust movement amount
2466
- movement.multiplyScalar( 1 / this.camera.zoom );
2612
+ this.camera.fov = MathUtils.clamp( this.camera.fov, this.minFov, this.maxFov );
2613
+ this.camera.updateProjectionMatrix();
2467
2614
 
2468
- } else if ( this.camera.isPerspectiveCamera && adjust ) {
2615
+ }
2469
2616
 
2470
- //adjust movement amount
2471
- this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position
2472
- this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position
2473
- const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.camera.position.distanceTo( this._gizmos.position );
2474
- movement.multiplyScalar( 1 / distanceFactor );
2617
+ const oldRadius = this._tbRadius;
2618
+ this._tbRadius = this.calculateTbRadius( this.camera );
2475
2619
 
2476
- }
2620
+ if ( oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS ) {
2477
2621
 
2478
- this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.camera.quaternion );
2622
+ const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
2623
+ const newRadius = this._tbRadius / scale;
2624
+ const curve = new EllipseCurve( 0, 0, newRadius, newRadius );
2625
+ const points = curve.getPoints( this._curvePts );
2626
+ const curveGeometry = new BufferGeometry().setFromPoints( points );
2479
2627
 
2480
- this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z );
2628
+ for ( const gizmo in this._gizmos.children ) {
2481
2629
 
2482
- this.setTransformationMatrices( this._m4_1, this._m4_1 );
2483
- return _transformation;
2630
+ this._gizmos.children[ gizmo ].geometry = curveGeometry;
2484
2631
 
2485
- };
2632
+ }
2486
2633
 
2487
- /**
2488
- * Reset trackball
2489
- */
2490
- reset = () => {
2634
+ }
2491
2635
 
2492
- this.camera.zoom = this._zoom0;
2636
+ }
2493
2637
 
2494
- if ( this.camera.isPerspectiveCamera ) {
2638
+ this.camera.lookAt( this._gizmos.position );
2495
2639
 
2496
- this.camera.fov = this._fov0;
2640
+ }
2497
2641
 
2498
- }
2642
+ setStateFromJSON( json ) {
2499
2643
 
2500
- this.camera.near = this._nearPos;
2501
- this.camera.far = this._farPos;
2502
- this._cameraMatrixState.copy( this._cameraMatrixState0 );
2503
- this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
2504
- this.camera.up.copy( this._up0 );
2644
+ const state = JSON.parse( json );
2505
2645
 
2506
- this.camera.updateMatrix();
2507
- this.camera.updateProjectionMatrix();
2646
+ if ( state.arcballState != undefined ) {
2508
2647
 
2509
- this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
2510
- this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
2511
- this._gizmos.updateMatrix();
2648
+ this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements );
2649
+ this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
2512
2650
 
2513
- this._tbRadius = this.calculateTbRadius( this.camera );
2514
- this.makeGizmos( this._gizmos.position, this._tbRadius );
2651
+ this.camera.up.copy( state.arcballState.cameraUp );
2652
+ this.camera.near = state.arcballState.cameraNear;
2653
+ this.camera.far = state.arcballState.cameraFar;
2515
2654
 
2516
- this.camera.lookAt( this._gizmos.position );
2655
+ this.camera.zoom = state.arcballState.cameraZoom;
2517
2656
 
2518
- this.updateTbState( STATE.IDLE, false );
2657
+ if ( this.camera.isPerspectiveCamera ) {
2519
2658
 
2520
- this.dispatchEvent( _changeEvent );
2659
+ this.camera.fov = state.arcballState.cameraFov;
2521
2660
 
2522
- };
2661
+ }
2523
2662
 
2524
- /**
2525
- * Rotate the camera around an axis passing by trackball's center
2526
- * @param {Vector3} axis Rotation axis
2527
- * @param {number} angle Angle in radians
2528
- * @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera
2529
- */
2530
- rotate = ( axis, angle ) => {
2663
+ this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements );
2664
+ this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
2531
2665
 
2532
- const point = this._gizmos.position; //rotation center
2533
- this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
2534
- this._rotationMatrix.makeRotationAxis( axis, - angle );
2666
+ this.camera.updateMatrix();
2667
+ this.camera.updateProjectionMatrix();
2535
2668
 
2536
- //rotate camera
2537
- this._m4_1.makeTranslation( point.x, point.y, point.z );
2538
- this._m4_1.multiply( this._rotationMatrix );
2539
- this._m4_1.multiply( this._translationMatrix );
2669
+ this._gizmos.updateMatrix();
2540
2670
 
2541
- this.setTransformationMatrices( this._m4_1 );
2671
+ this._tbRadius = this.calculateTbRadius( this.camera );
2672
+ const gizmoTmp = new Matrix4().copy( this._gizmoMatrixState0 );
2673
+ this.makeGizmos( this._gizmos.position, this._tbRadius );
2674
+ this._gizmoMatrixState0.copy( gizmoTmp );
2542
2675
 
2543
- return _transformation;
2676
+ this.camera.lookAt( this._gizmos.position );
2677
+ this.updateTbState( STATE.IDLE, false );
2544
2678
 
2545
- };
2679
+ this.dispatchEvent( _changeEvent );
2546
2680
 
2547
- copyState = () => {
2681
+ }
2548
2682
 
2549
- let state;
2550
- if ( this.camera.isOrthographicCamera ) {
2683
+ }
2551
2684
 
2552
- state = JSON.stringify( { arcballState: {
2685
+ }
2553
2686
 
2554
- cameraFar: this.camera.far,
2555
- cameraMatrix: this.camera.matrix,
2556
- cameraNear: this.camera.near,
2557
- cameraUp: this.camera.up,
2558
- cameraZoom: this.camera.zoom,
2559
- gizmoMatrix: this._gizmos.matrix
2687
+ //listeners
2560
2688
 
2561
- } } );
2689
+ function onWindowResize() {
2562
2690
 
2563
- } else if ( this.camera.isPerspectiveCamera ) {
2691
+ const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
2692
+ this._tbRadius = this.calculateTbRadius( this.camera );
2564
2693
 
2565
- state = JSON.stringify( { arcballState: {
2566
- cameraFar: this.camera.far,
2567
- cameraFov: this.camera.fov,
2568
- cameraMatrix: this.camera.matrix,
2569
- cameraNear: this.camera.near,
2570
- cameraUp: this.camera.up,
2571
- cameraZoom: this.camera.zoom,
2572
- gizmoMatrix: this._gizmos.matrix
2694
+ const newRadius = this._tbRadius / scale;
2695
+ const curve = new EllipseCurve( 0, 0, newRadius, newRadius );
2696
+ const points = curve.getPoints( this._curvePts );
2697
+ const curveGeometry = new BufferGeometry().setFromPoints( points );
2573
2698
 
2574
- } } );
2575
2699
 
2576
- }
2700
+ for ( const gizmo in this._gizmos.children ) {
2577
2701
 
2578
- navigator.clipboard.writeText( state );
2702
+ this._gizmos.children[ gizmo ].geometry = curveGeometry;
2579
2703
 
2580
- };
2704
+ }
2581
2705
 
2582
- pasteState = () => {
2706
+ this.dispatchEvent( _changeEvent );
2583
2707
 
2584
- const self = this;
2585
- navigator.clipboard.readText().then( function resolved( value ) {
2708
+ }
2586
2709
 
2587
- self.setStateFromJSON( value );
2710
+ function onContextMenu( event ) {
2588
2711
 
2589
- } );
2712
+ if ( ! this.enabled ) {
2590
2713
 
2591
- };
2714
+ return;
2592
2715
 
2593
- /**
2594
- * Save the current state of the control. This can later be recover with .reset
2595
- */
2596
- saveState = () => {
2716
+ }
2597
2717
 
2598
- this._cameraMatrixState0.copy( this.camera.matrix );
2599
- this._gizmoMatrixState0.copy( this._gizmos.matrix );
2600
- this._nearPos = this.camera.near;
2601
- this._farPos = this.camera.far;
2602
- this._zoom0 = this.camera.zoom;
2603
- this._up0.copy( this.camera.up );
2718
+ for ( let i = 0; i < this.mouseActions.length; i ++ ) {
2604
2719
 
2605
- if ( this.camera.isPerspectiveCamera ) {
2720
+ if ( this.mouseActions[ i ].mouse == 2 ) {
2606
2721
 
2607
- this._fov0 = this.camera.fov;
2722
+ //prevent only if button 2 is actually used
2723
+ event.preventDefault();
2724
+ break;
2608
2725
 
2609
2726
  }
2610
2727
 
2611
- };
2728
+ }
2612
2729
 
2613
- /**
2614
- * Perform uniform scale operation around a given point
2615
- * @param {Number} size Scale factor
2616
- * @param {Vector3} point Point around which scale
2617
- * @param {Boolean} scaleGizmos If gizmos should be scaled (Perspective only)
2618
- * @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos
2619
- */
2620
- scale = ( size, point, scaleGizmos = true ) => {
2730
+ }
2621
2731
 
2622
- _scalePointTemp.copy( point );
2623
- let sizeInverse = 1 / size;
2732
+ function onPointerCancel() {
2624
2733
 
2625
- if ( this.camera.isOrthographicCamera ) {
2734
+ this._touchStart.splice( 0, this._touchStart.length );
2735
+ this._touchCurrent.splice( 0, this._touchCurrent.length );
2736
+ this._input = INPUT.NONE;
2626
2737
 
2627
- //camera zoom
2628
- this.camera.zoom = this._zoomState;
2629
- this.camera.zoom *= size;
2738
+ }
2630
2739
 
2631
- //check min and max zoom
2632
- if ( this.camera.zoom > this.maxZoom ) {
2740
+ function onPointerDown( event ) {
2633
2741
 
2634
- this.camera.zoom = this.maxZoom;
2635
- sizeInverse = this._zoomState / this.maxZoom;
2742
+ if ( event.button == 0 && event.isPrimary ) {
2636
2743
 
2637
- } else if ( this.camera.zoom < this.minZoom ) {
2744
+ this._downValid = true;
2745
+ this._downEvents.push( event );
2746
+ this._downStart = performance.now();
2638
2747
 
2639
- this.camera.zoom = this.minZoom;
2640
- sizeInverse = this._zoomState / this.minZoom;
2748
+ } else {
2641
2749
 
2642
- }
2750
+ this._downValid = false;
2643
2751
 
2644
- this.camera.updateProjectionMatrix();
2752
+ }
2645
2753
 
2646
- this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position
2754
+ if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
2647
2755
 
2648
- //scale gizmos so they appear in the same spot having the same dimension
2649
- this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
2650
- this._translationMatrix.makeTranslation( - this._v3_1.x, - this._v3_1.y, - this._v3_1.z );
2756
+ this._touchStart.push( event );
2757
+ this._touchCurrent.push( event );
2651
2758
 
2652
- this._m4_2.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ).multiply( this._scaleMatrix );
2653
- this._m4_2.multiply( this._translationMatrix );
2759
+ switch ( this._input ) {
2654
2760
 
2761
+ case INPUT.NONE:
2655
2762
 
2656
- //move camera and gizmos to obtain pinch effect
2657
- _scalePointTemp.sub( this._v3_1 );
2763
+ //singleStart
2764
+ this._input = INPUT.ONE_FINGER;
2765
+ this.onSinglePanStart( event, 'ROTATE' );
2658
2766
 
2659
- const amount = _scalePointTemp.clone().multiplyScalar( sizeInverse );
2660
- _scalePointTemp.sub( amount );
2767
+ window.addEventListener( 'pointermove', this._onPointerMove );
2768
+ window.addEventListener( 'pointerup', this._onPointerUp );
2661
2769
 
2662
- this._m4_1.makeTranslation( _scalePointTemp.x, _scalePointTemp.y, _scalePointTemp.z );
2663
- this._m4_2.premultiply( this._m4_1 );
2770
+ break;
2664
2771
 
2665
- this.setTransformationMatrices( this._m4_1, this._m4_2 );
2666
- return _transformation;
2772
+ case INPUT.ONE_FINGER:
2773
+ case INPUT.ONE_FINGER_SWITCHED:
2667
2774
 
2668
- } else if ( this.camera.isPerspectiveCamera ) {
2775
+ //doubleStart
2776
+ this._input = INPUT.TWO_FINGER;
2669
2777
 
2670
- this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
2671
- this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
2778
+ this.onRotateStart();
2779
+ this.onPinchStart();
2780
+ this.onDoublePanStart();
2672
2781
 
2673
- //move camera
2674
- let distance = this._v3_1.distanceTo( _scalePointTemp );
2675
- let amount = distance - ( distance * sizeInverse );
2782
+ break;
2676
2783
 
2677
- //check min and max distance
2678
- const newDistance = distance - amount;
2679
- if ( newDistance < this.minDistance ) {
2784
+ case INPUT.TWO_FINGER:
2680
2785
 
2681
- sizeInverse = this.minDistance / distance;
2682
- amount = distance - ( distance * sizeInverse );
2786
+ //multipleStart
2787
+ this._input = INPUT.MULT_FINGER;
2788
+ this.onTriplePanStart( event );
2789
+ break;
2683
2790
 
2684
- } else if ( newDistance > this.maxDistance ) {
2791
+ }
2685
2792
 
2686
- sizeInverse = this.maxDistance / distance;
2687
- amount = distance - ( distance * sizeInverse );
2793
+ } else if ( event.pointerType != 'touch' && this._input == INPUT.NONE ) {
2688
2794
 
2689
- }
2795
+ let modifier = null;
2690
2796
 
2691
- _offset.copy( _scalePointTemp ).sub( this._v3_1 ).normalize().multiplyScalar( amount );
2797
+ if ( event.ctrlKey || event.metaKey ) {
2692
2798
 
2693
- this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
2799
+ modifier = 'CTRL';
2694
2800
 
2801
+ } else if ( event.shiftKey ) {
2695
2802
 
2696
- if ( scaleGizmos ) {
2803
+ modifier = 'SHIFT';
2697
2804
 
2698
- //scale gizmos so they appear in the same spot having the same dimension
2699
- const pos = this._v3_2;
2805
+ }
2700
2806
 
2701
- distance = pos.distanceTo( _scalePointTemp );
2702
- amount = distance - ( distance * sizeInverse );
2703
- _offset.copy( _scalePointTemp ).sub( this._v3_2 ).normalize().multiplyScalar( amount );
2807
+ this._mouseOp = this.getOpFromAction( event.button, modifier );
2808
+ if ( this._mouseOp != null ) {
2704
2809
 
2705
- this._translationMatrix.makeTranslation( pos.x, pos.y, pos.z );
2706
- this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
2810
+ window.addEventListener( 'pointermove', this._onPointerMove );
2811
+ window.addEventListener( 'pointerup', this._onPointerUp );
2707
2812
 
2708
- this._m4_2.makeTranslation( _offset.x, _offset.y, _offset.z ).multiply( this._translationMatrix );
2709
- this._m4_2.multiply( this._scaleMatrix );
2813
+ //singleStart
2814
+ this._input = INPUT.CURSOR;
2815
+ this._button = event.button;
2816
+ this.onSinglePanStart( event, this._mouseOp );
2710
2817
 
2711
- this._translationMatrix.makeTranslation( - pos.x, - pos.y, - pos.z );
2818
+ }
2712
2819
 
2713
- this._m4_2.multiply( this._translationMatrix );
2714
- this.setTransformationMatrices( this._m4_1, this._m4_2 );
2820
+ }
2715
2821
 
2822
+ }
2716
2823
 
2717
- } else {
2824
+ function onPointerMove( event ) {
2718
2825
 
2719
- this.setTransformationMatrices( this._m4_1 );
2826
+ if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
2720
2827
 
2721
- }
2828
+ switch ( this._input ) {
2722
2829
 
2723
- return _transformation;
2830
+ case INPUT.ONE_FINGER:
2724
2831
 
2725
- }
2832
+ //singleMove
2833
+ this.updateTouchEvent( event );
2726
2834
 
2727
- };
2835
+ this.onSinglePanMove( event, STATE.ROTATE );
2836
+ break;
2728
2837
 
2729
- /**
2730
- * Set camera fov
2731
- * @param {Number} value fov to be setted
2732
- */
2733
- setFov = ( value ) => {
2838
+ case INPUT.ONE_FINGER_SWITCHED:
2734
2839
 
2735
- if ( this.camera.isPerspectiveCamera ) {
2840
+ const movement = this.calculatePointersDistance( this._touchCurrent[ 0 ], event ) * this._devPxRatio;
2736
2841
 
2737
- this.camera.fov = MathUtils.clamp( value, this.minFov, this.maxFov );
2738
- this.camera.updateProjectionMatrix();
2842
+ if ( movement >= this._switchSensibility ) {
2739
2843
 
2740
- }
2844
+ //singleMove
2845
+ this._input = INPUT.ONE_FINGER;
2846
+ this.updateTouchEvent( event );
2741
2847
 
2742
- };
2848
+ this.onSinglePanStart( event, 'ROTATE' );
2849
+ break;
2743
2850
 
2744
- /**
2745
- * Set values in transformation object
2746
- * @param {Matrix4} camera Transformation to be applied to the camera
2747
- * @param {Matrix4} gizmos Transformation to be applied to gizmos
2748
- */
2749
- setTransformationMatrices( camera = null, gizmos = null ) {
2851
+ }
2750
2852
 
2751
- if ( camera != null ) {
2853
+ break;
2752
2854
 
2753
- if ( _transformation.camera != null ) {
2855
+ case INPUT.TWO_FINGER:
2754
2856
 
2755
- _transformation.camera.copy( camera );
2857
+ //rotate/pan/pinchMove
2858
+ this.updateTouchEvent( event );
2756
2859
 
2757
- } else {
2860
+ this.onRotateMove();
2861
+ this.onPinchMove();
2862
+ this.onDoublePanMove();
2758
2863
 
2759
- _transformation.camera = camera.clone();
2864
+ break;
2760
2865
 
2761
- }
2866
+ case INPUT.MULT_FINGER:
2762
2867
 
2763
- } else {
2868
+ //multMove
2869
+ this.updateTouchEvent( event );
2764
2870
 
2765
- _transformation.camera = null;
2871
+ this.onTriplePanMove( event );
2872
+ break;
2766
2873
 
2767
2874
  }
2768
2875
 
2769
- if ( gizmos != null ) {
2770
-
2771
- if ( _transformation.gizmos != null ) {
2772
-
2773
- _transformation.gizmos.copy( gizmos );
2876
+ } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
2774
2877
 
2775
- } else {
2878
+ let modifier = null;
2776
2879
 
2777
- _transformation.gizmos = gizmos.clone();
2880
+ if ( event.ctrlKey || event.metaKey ) {
2778
2881
 
2779
- }
2882
+ modifier = 'CTRL';
2780
2883
 
2781
- } else {
2884
+ } else if ( event.shiftKey ) {
2782
2885
 
2783
- _transformation.gizmos = null;
2886
+ modifier = 'SHIFT';
2784
2887
 
2785
2888
  }
2786
2889
 
2787
- }
2788
-
2789
- /**
2790
- * Rotate camera around its direction axis passing by a given point by a given angle
2791
- * @param {Vector3} point The point where the rotation axis is passing trough
2792
- * @param {Number} angle Angle in radians
2793
- * @returns The computed transormation matix
2794
- */
2795
- zRotate = ( point, angle ) => {
2796
-
2797
- this._rotationMatrix.makeRotationAxis( this._rotationAxis, angle );
2798
- this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
2890
+ const mouseOpState = this.getOpStateFromAction( this._button, modifier );
2799
2891
 
2800
- this._m4_1.makeTranslation( point.x, point.y, point.z );
2801
- this._m4_1.multiply( this._rotationMatrix );
2802
- this._m4_1.multiply( this._translationMatrix );
2892
+ if ( mouseOpState != null ) {
2803
2893
 
2804
- this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ).sub( point ); //vector from rotation center to gizmos position
2805
- this._v3_2.copy( this._v3_1 ).applyAxisAngle( this._rotationAxis, angle ); //apply rotation
2806
- this._v3_2.sub( this._v3_1 );
2894
+ this.onSinglePanMove( event, mouseOpState );
2807
2895
 
2808
- this._m4_2.makeTranslation( this._v3_2.x, this._v3_2.y, this._v3_2.z );
2896
+ }
2809
2897
 
2810
- this.setTransformationMatrices( this._m4_1, this._m4_2 );
2811
- return _transformation;
2898
+ }
2812
2899
 
2813
- };
2900
+ //checkDistance
2901
+ if ( this._downValid ) {
2814
2902
 
2903
+ const movement = this.calculatePointersDistance( this._downEvents[ this._downEvents.length - 1 ], event ) * this._devPxRatio;
2904
+ if ( movement > this._movementThreshold ) {
2815
2905
 
2816
- getRaycaster() {
2906
+ this._downValid = false;
2817
2907
 
2818
- return _raycaster;
2908
+ }
2819
2909
 
2820
2910
  }
2821
2911
 
2912
+ }
2822
2913
 
2823
- /**
2824
- * Unproject the cursor on the 3D object surface
2825
- * @param {Vector2} cursor Cursor coordinates in NDC
2826
- * @param {Camera} camera Virtual camera
2827
- * @returns {Vector3} The point of intersection with the model, if exist, null otherwise
2828
- */
2829
- unprojectOnObj = ( cursor, camera ) => {
2914
+ function onPointerUp( event ) {
2830
2915
 
2831
- const raycaster = this.getRaycaster();
2832
- raycaster.near = camera.near;
2833
- raycaster.far = camera.far;
2834
- raycaster.setFromCamera( cursor, camera );
2916
+ if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
2835
2917
 
2836
- const intersect = raycaster.intersectObjects( this.scene.children, true );
2918
+ const nTouch = this._touchCurrent.length;
2837
2919
 
2838
- for ( let i = 0; i < intersect.length; i ++ ) {
2920
+ for ( let i = 0; i < nTouch; i ++ ) {
2839
2921
 
2840
- if ( intersect[ i ].object.uuid != this._gizmos.uuid && intersect[ i ].face != null ) {
2922
+ if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
2841
2923
 
2842
- return intersect[ i ].point.clone();
2924
+ this._touchCurrent.splice( i, 1 );
2925
+ this._touchStart.splice( i, 1 );
2926
+ break;
2843
2927
 
2844
2928
  }
2845
2929
 
2846
2930
  }
2847
2931
 
2848
- return null;
2932
+ switch ( this._input ) {
2849
2933
 
2850
- };
2934
+ case INPUT.ONE_FINGER:
2935
+ case INPUT.ONE_FINGER_SWITCHED:
2851
2936
 
2852
- /**
2853
- * Unproject the cursor on the trackball surface
2854
- * @param {Camera} camera The virtual camera
2855
- * @param {Number} cursorX Cursor horizontal coordinate on screen
2856
- * @param {Number} cursorY Cursor vertical coordinate on screen
2857
- * @param {HTMLElement} canvas The canvas where the renderer draws its output
2858
- * @param {number} tbRadius The trackball radius
2859
- * @returns {Vector3} The unprojected point on the trackball surface
2860
- */
2861
- unprojectOnTbSurface = ( camera, cursorX, cursorY, canvas, tbRadius ) => {
2937
+ //singleEnd
2938
+ window.removeEventListener( 'pointermove', this._onPointerMove );
2939
+ window.removeEventListener( 'pointerup', this._onPointerUp );
2862
2940
 
2863
- if ( camera.type == 'OrthographicCamera' ) {
2941
+ this._input = INPUT.NONE;
2942
+ this.onSinglePanEnd();
2864
2943
 
2865
- this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
2866
- this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
2944
+ break;
2867
2945
 
2868
- const x2 = Math.pow( this._v2_1.x, 2 );
2869
- const y2 = Math.pow( this._v2_1.y, 2 );
2870
- const r2 = Math.pow( this._tbRadius, 2 );
2946
+ case INPUT.TWO_FINGER:
2871
2947
 
2872
- if ( x2 + y2 <= r2 * 0.5 ) {
2948
+ //doubleEnd
2949
+ this.onDoublePanEnd( event );
2950
+ this.onPinchEnd( event );
2951
+ this.onRotateEnd( event );
2873
2952
 
2874
- //intersection with sphere
2875
- this._v3_1.setZ( Math.sqrt( r2 - ( x2 + y2 ) ) );
2953
+ //switching to singleStart
2954
+ this._input = INPUT.ONE_FINGER_SWITCHED;
2876
2955
 
2877
- } else {
2956
+ break;
2878
2957
 
2879
- //intersection with hyperboloid
2880
- this._v3_1.setZ( ( r2 * 0.5 ) / ( Math.sqrt( x2 + y2 ) ) );
2958
+ case INPUT.MULT_FINGER:
2959
+
2960
+ if ( this._touchCurrent.length == 0 ) {
2961
+
2962
+ window.removeEventListener( 'pointermove', this._onPointerMove );
2963
+ window.removeEventListener( 'pointerup', this._onPointerUp );
2964
+
2965
+ //multCancel
2966
+ this._input = INPUT.NONE;
2967
+ this.onTriplePanEnd();
2968
+
2969
+ }
2881
2970
 
2882
- }
2971
+ break;
2883
2972
 
2884
- return this._v3_1;
2973
+ }
2885
2974
 
2886
- } else if ( camera.type == 'PerspectiveCamera' ) {
2975
+ } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
2887
2976
 
2888
- //unproject cursor on the near plane
2889
- this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
2977
+ window.removeEventListener( 'pointermove', this._onPointerMove );
2978
+ window.removeEventListener( 'pointerup', this._onPointerUp );
2890
2979
 
2891
- this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
2892
- this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
2980
+ this._input = INPUT.NONE;
2981
+ this.onSinglePanEnd();
2982
+ this._button = - 1;
2893
2983
 
2894
- const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
2895
- const cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
2896
- const radius2 = Math.pow( tbRadius, 2 );
2984
+ }
2897
2985
 
2898
- // camera
2899
- // |\
2900
- // | \
2901
- // | \
2902
- // h | \
2903
- // | \
2904
- // | \
2905
- // _ _ | _ _ _\ _ _ near plane
2906
- // l
2986
+ if ( event.isPrimary ) {
2907
2987
 
2908
- const h = this._v3_1.z;
2909
- const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
2988
+ if ( this._downValid ) {
2910
2989
 
2911
- if ( l == 0 ) {
2990
+ const downTime = event.timeStamp - this._downEvents[ this._downEvents.length - 1 ].timeStamp;
2912
2991
 
2913
- //ray aligned with camera
2914
- rayDir.set( this._v3_1.x, this._v3_1.y, tbRadius );
2915
- return rayDir;
2992
+ if ( downTime <= this._maxDownTime ) {
2916
2993
 
2917
- }
2994
+ if ( this._nclicks == 0 ) {
2918
2995
 
2919
- const m = h / l;
2920
- const q = cameraGizmoDistance;
2996
+ //first valid click detected
2997
+ this._nclicks = 1;
2998
+ this._clickStart = performance.now();
2921
2999
 
2922
- /*
2923
- * calculate intersection point between unprojected ray and trackball surface
2924
- *|y = m * x + q
2925
- *|x^2 + y^2 = r^2
2926
- *
2927
- * (m^2 + 1) * x^2 + (2 * m * q) * x + q^2 - r^2 = 0
2928
- */
2929
- let a = Math.pow( m, 2 ) + 1;
2930
- let b = 2 * m * q;
2931
- let c = Math.pow( q, 2 ) - radius2;
2932
- let delta = Math.pow( b, 2 ) - ( 4 * a * c );
3000
+ } else {
2933
3001
 
2934
- if ( delta >= 0 ) {
3002
+ const clickInterval = event.timeStamp - this._clickStart;
3003
+ const movement = this.calculatePointersDistance( this._downEvents[ 1 ], this._downEvents[ 0 ] ) * this._devPxRatio;
2935
3004
 
2936
- //intersection with sphere
2937
- this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
2938
- this._v2_1.setY( m * this._v2_1.x + q );
3005
+ if ( clickInterval <= this._maxInterval && movement <= this._posThreshold ) {
2939
3006
 
2940
- const angle = MathUtils.RAD2DEG * this._v2_1.angle();
3007
+ //second valid click detected
3008
+ //fire double tap and reset values
3009
+ this._nclicks = 0;
3010
+ this._downEvents.splice( 0, this._downEvents.length );
3011
+ this.onDoubleTap( event );
2941
3012
 
2942
- if ( angle >= 45 ) {
3013
+ } else {
2943
3014
 
2944
- //if angle between intersection point and X' axis is >= 45°, return that point
2945
- //otherwise, calculate intersection point with hyperboloid
3015
+ //new 'first click'
3016
+ this._nclicks = 1;
3017
+ this._downEvents.shift();
3018
+ this._clickStart = performance.now();
2946
3019
 
2947
- const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) );
2948
- rayDir.multiplyScalar( rayLength );
2949
- rayDir.z += cameraGizmoDistance;
2950
- return rayDir;
3020
+ }
2951
3021
 
2952
3022
  }
2953
3023
 
2954
- }
3024
+ } else {
2955
3025
 
2956
- //intersection with hyperboloid
2957
- /*
2958
- *|y = m * x + q
2959
- *|y = (1 / x) * (r^2 / 2)
2960
- *
2961
- * m * x^2 + q * x - r^2 / 2 = 0
2962
- */
3026
+ this._downValid = false;
3027
+ this._nclicks = 0;
3028
+ this._downEvents.splice( 0, this._downEvents.length );
2963
3029
 
2964
- a = m;
2965
- b = q;
2966
- c = - radius2 * 0.5;
2967
- delta = Math.pow( b, 2 ) - ( 4 * a * c );
2968
- this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
2969
- this._v2_1.setY( m * this._v2_1.x + q );
3030
+ }
2970
3031
 
2971
- const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) );
3032
+ } else {
2972
3033
 
2973
- rayDir.multiplyScalar( rayLength );
2974
- rayDir.z += cameraGizmoDistance;
2975
- return rayDir;
3034
+ this._nclicks = 0;
3035
+ this._downEvents.splice( 0, this._downEvents.length );
2976
3036
 
2977
3037
  }
2978
3038
 
2979
- };
3039
+ }
2980
3040
 
3041
+ }
2981
3042
 
2982
- /**
2983
- * Unproject the cursor on the plane passing through the center of the trackball orthogonal to the camera
2984
- * @param {Camera} camera The virtual camera
2985
- * @param {Number} cursorX Cursor horizontal coordinate on screen
2986
- * @param {Number} cursorY Cursor vertical coordinate on screen
2987
- * @param {HTMLElement} canvas The canvas where the renderer draws its output
2988
- * @param {Boolean} initialDistance If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only)
2989
- * @returns {Vector3} The unprojected point on the trackball plane
2990
- */
2991
- unprojectOnTbPlane = ( camera, cursorX, cursorY, canvas, initialDistance = false ) => {
3043
+ function onWheel( event ) {
2992
3044
 
2993
- if ( camera.type == 'OrthographicCamera' ) {
3045
+ if ( this.enabled && this.enableZoom ) {
2994
3046
 
2995
- this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
2996
- this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
3047
+ let modifier = null;
2997
3048
 
2998
- return this._v3_1.clone();
3049
+ if ( event.ctrlKey || event.metaKey ) {
2999
3050
 
3000
- } else if ( camera.type == 'PerspectiveCamera' ) {
3051
+ modifier = 'CTRL';
3001
3052
 
3002
- this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
3053
+ } else if ( event.shiftKey ) {
3003
3054
 
3004
- //unproject cursor on the near plane
3005
- this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
3006
- this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
3055
+ modifier = 'SHIFT';
3007
3056
 
3008
- const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
3057
+ }
3009
3058
 
3010
- // camera
3011
- // |\
3012
- // | \
3013
- // | \
3014
- // h | \
3015
- // | \
3016
- // | \
3017
- // _ _ | _ _ _\ _ _ near plane
3018
- // l
3059
+ const mouseOp = this.getOpFromAction( 'WHEEL', modifier );
3019
3060
 
3020
- const h = this._v3_1.z;
3021
- const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
3022
- let cameraGizmoDistance;
3061
+ if ( mouseOp != null ) {
3023
3062
 
3024
- if ( initialDistance ) {
3063
+ event.preventDefault();
3064
+ this.dispatchEvent( _startEvent );
3025
3065
 
3026
- cameraGizmoDistance = this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ).distanceTo( this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ) );
3066
+ const notchDeltaY = 125; //distance of one notch of mouse wheel
3067
+ let sgn = event.deltaY / notchDeltaY;
3027
3068
 
3028
- } else {
3069
+ let size = 1;
3029
3070
 
3030
- cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
3071
+ if ( sgn > 0 ) {
3031
3072
 
3032
- }
3073
+ size = 1 / this.scaleFactor;
3033
3074
 
3034
- /*
3035
- * calculate intersection point between unprojected ray and the plane
3036
- *|y = mx + q
3037
- *|y = 0
3038
- *
3039
- * x = -q/m
3040
- */
3041
- if ( l == 0 ) {
3075
+ } else if ( sgn < 0 ) {
3042
3076
 
3043
- //ray aligned with camera
3044
- rayDir.set( 0, 0, 0 );
3045
- return rayDir;
3077
+ size = this.scaleFactor;
3046
3078
 
3047
3079
  }
3048
3080
 
3049
- const m = h / l;
3050
- const q = cameraGizmoDistance;
3051
- const x = - q / m;
3081
+ switch ( mouseOp ) {
3052
3082
 
3053
- const rayLength = Math.sqrt( Math.pow( q, 2 ) + Math.pow( x, 2 ) );
3054
- rayDir.multiplyScalar( rayLength );
3055
- rayDir.z = 0;
3056
- return rayDir;
3083
+ case 'ZOOM':
3057
3084
 
3058
- }
3085
+ this.updateTbState( STATE.SCALE, true );
3059
3086
 
3060
- };
3087
+ if ( sgn > 0 ) {
3061
3088
 
3062
- /**
3063
- * Update camera and gizmos state
3064
- */
3065
- updateMatrixState = () => {
3089
+ size = 1 / ( Math.pow( this.scaleFactor, sgn ) );
3066
3090
 
3067
- //update camera and gizmos state
3068
- this._cameraMatrixState.copy( this.camera.matrix );
3069
- this._gizmoMatrixState.copy( this._gizmos.matrix );
3091
+ } else if ( sgn < 0 ) {
3070
3092
 
3071
- if ( this.camera.isOrthographicCamera ) {
3093
+ size = Math.pow( this.scaleFactor, - sgn );
3072
3094
 
3073
- this._cameraProjectionState.copy( this.camera.projectionMatrix );
3074
- this.camera.updateProjectionMatrix();
3075
- this._zoomState = this.camera.zoom;
3095
+ }
3076
3096
 
3077
- } else if ( this.camera.isPerspectiveCamera ) {
3097
+ if ( this.cursorZoom && this.enablePan ) {
3078
3098
 
3079
- this._fovState = this.camera.fov;
3099
+ let scalePoint;
3080
3100
 
3081
- }
3101
+ if ( this.camera.isOrthographicCamera ) {
3082
3102
 
3083
- };
3103
+ scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position );
3084
3104
 
3085
- /**
3086
- * Update the trackball FSA
3087
- * @param {STATE} newState New state of the FSA
3088
- * @param {Boolean} updateMatrices If matriices state should be updated
3089
- */
3090
- updateTbState = ( newState, updateMatrices ) => {
3105
+ } else if ( this.camera.isPerspectiveCamera ) {
3091
3106
 
3092
- this._state = newState;
3093
- if ( updateMatrices ) {
3107
+ scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position );
3094
3108
 
3095
- this.updateMatrixState();
3109
+ }
3096
3110
 
3097
- }
3111
+ this.applyTransformMatrix( this.scale( size, scalePoint ) );
3098
3112
 
3099
- };
3113
+ } else {
3100
3114
 
3101
- update = () => {
3115
+ this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
3102
3116
 
3103
- const EPS = 0.000001;
3117
+ }
3104
3118
 
3105
- if ( this.target.equals( this._currentTarget ) === false ) {
3119
+ if ( this._grid != null ) {
3106
3120
 
3107
- this._gizmos.position.copy( this.target ); //for correct radius calculation
3108
- this._tbRadius = this.calculateTbRadius( this.camera );
3109
- this.makeGizmos( this.target, this._tbRadius );
3110
- this._currentTarget.copy( this.target );
3121
+ this.disposeGrid();
3122
+ this.drawGrid();
3111
3123
 
3112
- }
3124
+ }
3113
3125
 
3114
- //check min/max parameters
3115
- if ( this.camera.isOrthographicCamera ) {
3126
+ this.updateTbState( STATE.IDLE, false );
3116
3127
 
3117
- //check zoom
3118
- if ( this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom ) {
3128
+ this.dispatchEvent( _changeEvent );
3129
+ this.dispatchEvent( _endEvent );
3119
3130
 
3120
- const newZoom = MathUtils.clamp( this.camera.zoom, this.minZoom, this.maxZoom );
3121
- this.applyTransformMatrix( this.scale( newZoom / this.camera.zoom, this._gizmos.position, true ) );
3131
+ break;
3122
3132
 
3123
- }
3133
+ case 'FOV':
3124
3134
 
3125
- } else if ( this.camera.isPerspectiveCamera ) {
3135
+ if ( this.camera.isPerspectiveCamera ) {
3126
3136
 
3127
- //check distance
3128
- const distance = this.camera.position.distanceTo( this._gizmos.position );
3137
+ this.updateTbState( STATE.FOV, true );
3129
3138
 
3130
- if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) {
3131
3139
 
3132
- const newDistance = MathUtils.clamp( distance, this.minDistance, this.maxDistance );
3133
- this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) );
3134
- this.updateMatrixState();
3140
+ //Vertigo effect
3135
3141
 
3136
- }
3142
+ // fov / 2
3143
+ // |\
3144
+ // | \
3145
+ // | \
3146
+ // x | \
3147
+ // | \
3148
+ // | \
3149
+ // | _ _ _\
3150
+ // y
3137
3151
 
3138
- //check fov
3139
- if ( this.camera.fov < this.minFov || this.camera.fov > this.maxFov ) {
3152
+ //check for iOs shift shortcut
3153
+ if ( event.deltaX != 0 ) {
3140
3154
 
3141
- this.camera.fov = MathUtils.clamp( this.camera.fov, this.minFov, this.maxFov );
3142
- this.camera.updateProjectionMatrix();
3155
+ sgn = event.deltaX / notchDeltaY;
3143
3156
 
3144
- }
3157
+ size = 1;
3145
3158
 
3146
- const oldRadius = this._tbRadius;
3147
- this._tbRadius = this.calculateTbRadius( this.camera );
3159
+ if ( sgn > 0 ) {
3148
3160
 
3149
- if ( oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS ) {
3161
+ size = 1 / ( Math.pow( this.scaleFactor, sgn ) );
3150
3162
 
3151
- const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
3152
- const newRadius = this._tbRadius / scale;
3153
- const curve = new EllipseCurve( 0, 0, newRadius, newRadius );
3154
- const points = curve.getPoints( this._curvePts );
3155
- const curveGeometry = new BufferGeometry().setFromPoints( points );
3163
+ } else if ( sgn < 0 ) {
3156
3164
 
3157
- for ( const gizmo in this._gizmos.children ) {
3165
+ size = Math.pow( this.scaleFactor, - sgn );
3158
3166
 
3159
- this._gizmos.children[ gizmo ].geometry = curveGeometry;
3167
+ }
3160
3168
 
3161
- }
3169
+ }
3162
3170
 
3163
- }
3171
+ this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
3172
+ const x = this._v3_1.distanceTo( this._gizmos.position );
3173
+ let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
3164
3174
 
3165
- }
3175
+ //check min and max distance
3176
+ xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
3166
3177
 
3167
- this.camera.lookAt( this._gizmos.position );
3178
+ const y = x * Math.tan( MathUtils.DEG2RAD * this.camera.fov * 0.5 );
3168
3179
 
3169
- };
3180
+ //calculate new fov
3181
+ let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
3170
3182
 
3171
- setStateFromJSON = ( json ) => {
3183
+ //check min and max fov
3184
+ if ( newFov > this.maxFov ) {
3172
3185
 
3173
- const state = JSON.parse( json );
3186
+ newFov = this.maxFov;
3174
3187
 
3175
- if ( state.arcballState != undefined ) {
3188
+ } else if ( newFov < this.minFov ) {
3176
3189
 
3177
- this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements );
3178
- this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale );
3190
+ newFov = this.minFov;
3179
3191
 
3180
- this.camera.up.copy( state.arcballState.cameraUp );
3181
- this.camera.near = state.arcballState.cameraNear;
3182
- this.camera.far = state.arcballState.cameraFar;
3192
+ }
3183
3193
 
3184
- this.camera.zoom = state.arcballState.cameraZoom;
3194
+ const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
3195
+ size = x / newDistance;
3185
3196
 
3186
- if ( this.camera.isPerspectiveCamera ) {
3197
+ this.setFov( newFov );
3198
+ this.applyTransformMatrix( this.scale( size, this._gizmos.position, false ) );
3187
3199
 
3188
- this.camera.fov = state.arcballState.cameraFov;
3200
+ }
3189
3201
 
3190
- }
3202
+ if ( this._grid != null ) {
3191
3203
 
3192
- this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements );
3193
- this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
3204
+ this.disposeGrid();
3205
+ this.drawGrid();
3194
3206
 
3195
- this.camera.updateMatrix();
3196
- this.camera.updateProjectionMatrix();
3207
+ }
3197
3208
 
3198
- this._gizmos.updateMatrix();
3209
+ this.updateTbState( STATE.IDLE, false );
3199
3210
 
3200
- this._tbRadius = this.calculateTbRadius( this.camera );
3201
- const gizmoTmp = new Matrix4().copy( this._gizmoMatrixState0 );
3202
- this.makeGizmos( this._gizmos.position, this._tbRadius );
3203
- this._gizmoMatrixState0.copy( gizmoTmp );
3211
+ this.dispatchEvent( _changeEvent );
3212
+ this.dispatchEvent( _endEvent );
3204
3213
 
3205
- this.camera.lookAt( this._gizmos.position );
3206
- this.updateTbState( STATE.IDLE, false );
3214
+ break;
3207
3215
 
3208
- this.dispatchEvent( _changeEvent );
3216
+ }
3209
3217
 
3210
3218
  }
3211
3219
 
3212
- };
3220
+ }
3213
3221
 
3214
3222
  }
3215
3223