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