@zephyr3d/scene 0.7.0 → 0.8.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 (368) hide show
  1. package/dist/animation/animation.js +4 -33
  2. package/dist/animation/animation.js.map +1 -1
  3. package/dist/animation/animationset.js +24 -11
  4. package/dist/animation/animationset.js.map +1 -1
  5. package/dist/animation/animationtrack.js +1 -0
  6. package/dist/animation/animationtrack.js.map +1 -1
  7. package/dist/animation/eulerrotationtrack.js +0 -1
  8. package/dist/animation/eulerrotationtrack.js.map +1 -1
  9. package/dist/animation/ik/ccd_solver.js +372 -0
  10. package/dist/animation/ik/ccd_solver.js.map +1 -0
  11. package/dist/animation/ik/fabrik_solver.js +399 -0
  12. package/dist/animation/ik/fabrik_solver.js.map +1 -0
  13. package/dist/animation/ik/ik_angle_constraint.js +181 -0
  14. package/dist/animation/ik/ik_angle_constraint.js.map +1 -0
  15. package/dist/animation/ik/ik_chain.js +163 -0
  16. package/dist/animation/ik/ik_chain.js.map +1 -0
  17. package/dist/animation/ik/ik_chain_builder.js +68 -0
  18. package/dist/animation/ik/ik_chain_builder.js.map +1 -0
  19. package/dist/animation/ik/ik_constraint.js +25 -0
  20. package/dist/animation/ik/ik_constraint.js.map +1 -0
  21. package/dist/animation/ik/ik_pole_constraint.js +123 -0
  22. package/dist/animation/ik/ik_pole_constraint.js.map +1 -0
  23. package/dist/animation/ik/ik_solver.js +48 -0
  24. package/dist/animation/ik/ik_solver.js.map +1 -0
  25. package/dist/animation/ik/ik_track.js +96 -0
  26. package/dist/animation/ik/ik_track.js.map +1 -0
  27. package/dist/animation/ik/ik_utils.js +270 -0
  28. package/dist/animation/ik/ik_utils.js.map +1 -0
  29. package/dist/animation/ik/two_bone_ik_solver.js +332 -0
  30. package/dist/animation/ik/two_bone_ik_solver.js.map +1 -0
  31. package/dist/animation/ik_modifier.js +61 -0
  32. package/dist/animation/ik_modifier.js.map +1 -0
  33. package/dist/animation/ik_postprocessor.js +71 -0
  34. package/dist/animation/ik_postprocessor.js.map +1 -0
  35. package/dist/animation/manual_transform_processor.js +156 -0
  36. package/dist/animation/manual_transform_processor.js.map +1 -0
  37. package/dist/animation/morphtarget.js +7 -1
  38. package/dist/animation/morphtarget.js.map +1 -1
  39. package/dist/animation/morphtrack.js +3 -3
  40. package/dist/animation/morphtrack.js.map +1 -1
  41. package/dist/animation/proptrack.js +37 -41
  42. package/dist/animation/proptrack.js.map +1 -1
  43. package/dist/animation/rotationtrack.js +0 -1
  44. package/dist/animation/rotationtrack.js.map +1 -1
  45. package/dist/animation/scaletrack.js +0 -1
  46. package/dist/animation/scaletrack.js.map +1 -1
  47. package/dist/animation/skeleton.js +57 -60
  48. package/dist/animation/skeleton.js.map +1 -1
  49. package/dist/animation/skeleton_modifier.js +38 -0
  50. package/dist/animation/skeleton_modifier.js.map +1 -0
  51. package/dist/animation/skeleton_postprocessor.js +50 -0
  52. package/dist/animation/skeleton_postprocessor.js.map +1 -0
  53. package/dist/animation/spring/multi_chain_spring_system.js +503 -0
  54. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -0
  55. package/dist/animation/spring/spring_chain.js +103 -0
  56. package/dist/animation/spring/spring_chain.js.map +1 -0
  57. package/dist/animation/spring/spring_collider.js +247 -0
  58. package/dist/animation/spring/spring_collider.js.map +1 -0
  59. package/dist/animation/spring/spring_constraint.js +21 -0
  60. package/dist/animation/spring/spring_constraint.js.map +1 -0
  61. package/dist/animation/spring/spring_particle.js +20 -0
  62. package/dist/animation/spring/spring_particle.js.map +1 -0
  63. package/dist/animation/spring/spring_system.js +526 -0
  64. package/dist/animation/spring/spring_system.js.map +1 -0
  65. package/dist/animation/spring_modifier.js +45 -0
  66. package/dist/animation/spring_modifier.js.map +1 -0
  67. package/dist/animation/spring_postprocessor.js +54 -0
  68. package/dist/animation/spring_postprocessor.js.map +1 -0
  69. package/dist/animation/translationtrack.js +0 -1
  70. package/dist/animation/translationtrack.js.map +1 -1
  71. package/dist/app/api.js +13 -4
  72. package/dist/app/api.js.map +1 -1
  73. package/dist/app/app.js +13 -1
  74. package/dist/app/app.js.map +1 -1
  75. package/dist/app/engine.js +33 -45
  76. package/dist/app/engine.js.map +1 -1
  77. package/dist/app/inputmgr.js.map +1 -1
  78. package/dist/app/runtimescript.js.map +1 -1
  79. package/dist/app/screen.js +318 -0
  80. package/dist/app/screen.js.map +1 -0
  81. package/dist/app/scriptingsystem.js +10 -6
  82. package/dist/app/scriptingsystem.js.map +1 -1
  83. package/dist/app/scriptregistry.js +11 -22
  84. package/dist/app/scriptregistry.js.map +1 -1
  85. package/dist/asset/assetmanager.js +79 -58
  86. package/dist/asset/assetmanager.js.map +1 -1
  87. package/dist/asset/builtin.js +5 -5
  88. package/dist/asset/builtin.js.map +1 -1
  89. package/dist/asset/loaders/dds/dds.js +6 -2
  90. package/dist/asset/loaders/dds/dds.js.map +1 -1
  91. package/dist/asset/loaders/dds/dds_loader.js +1 -1
  92. package/dist/asset/loaders/dds/dds_loader.js.map +1 -1
  93. package/dist/asset/loaders/gltf/gltf_loader.js +14 -6
  94. package/dist/asset/loaders/gltf/gltf_loader.js.map +1 -1
  95. package/dist/asset/loaders/gltf/helpers.js +6 -2
  96. package/dist/asset/loaders/gltf/helpers.js.map +1 -1
  97. package/dist/asset/loaders/hdr/hdr.js +22 -15
  98. package/dist/asset/loaders/hdr/hdr.js.map +1 -1
  99. package/dist/asset/loaders/image/tga_Loader.js +2 -1
  100. package/dist/asset/loaders/image/tga_Loader.js.map +1 -1
  101. package/dist/asset/loaders/image/webimage_loader.js.map +1 -1
  102. package/dist/asset/loaders/loader.js +2 -2
  103. package/dist/asset/loaders/loader.js.map +1 -1
  104. package/dist/asset/model.js +9 -5
  105. package/dist/asset/model.js.map +1 -1
  106. package/dist/blitter/bilateralblur.js.map +1 -1
  107. package/dist/blitter/blitter.js +9 -11
  108. package/dist/blitter/blitter.js.map +1 -1
  109. package/dist/blitter/box.js +0 -1
  110. package/dist/blitter/box.js.map +1 -1
  111. package/dist/blitter/copy.js.map +1 -1
  112. package/dist/blitter/gaussianblur.js.map +1 -1
  113. package/dist/camera/base.js +1 -3
  114. package/dist/camera/base.js.map +1 -1
  115. package/dist/camera/camera.js +121 -37
  116. package/dist/camera/camera.js.map +1 -1
  117. package/dist/camera/fps.js +19 -17
  118. package/dist/camera/fps.js.map +1 -1
  119. package/dist/camera/orbit.js +45 -43
  120. package/dist/camera/orbit.js.map +1 -1
  121. package/dist/camera/orthocamera.js +27 -21
  122. package/dist/camera/orthocamera.js.map +1 -1
  123. package/dist/camera/perspectivecamera.js +28 -19
  124. package/dist/camera/perspectivecamera.js.map +1 -1
  125. package/dist/index.d.ts +15802 -15577
  126. package/dist/index.js +33 -11
  127. package/dist/index.js.map +1 -1
  128. package/dist/material/blinn.js.map +1 -1
  129. package/dist/material/grassmaterial.js.map +1 -1
  130. package/dist/material/lambert.js.map +1 -1
  131. package/dist/material/material.js +11 -5
  132. package/dist/material/material.js.map +1 -1
  133. package/dist/material/meshmaterial.js +10 -9
  134. package/dist/material/meshmaterial.js.map +1 -1
  135. package/dist/material/mixins/albedocolor.js.map +1 -1
  136. package/dist/material/mixins/foliage.js.map +1 -1
  137. package/dist/material/mixins/lightmodel/blinnphong.js +66 -4
  138. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  139. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  140. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +7 -0
  141. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  142. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  143. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  144. package/dist/material/mixins/lit.js +3 -3
  145. package/dist/material/mixins/lit.js.map +1 -1
  146. package/dist/material/mixins/pbr/brdf.js.map +1 -1
  147. package/dist/material/mixins/pbr/common.js.map +1 -1
  148. package/dist/material/mixins/texture.js +3 -0
  149. package/dist/material/mixins/texture.js.map +1 -1
  150. package/dist/material/mixins/vertexcolor.js.map +1 -1
  151. package/dist/material/particle.js.map +1 -1
  152. package/dist/material/pbrblueprint.js +180 -22
  153. package/dist/material/pbrblueprint.js.map +1 -1
  154. package/dist/material/pbrmr.js.map +1 -1
  155. package/dist/material/pbrsg.js.map +1 -1
  156. package/dist/material/shader/helper.js +41 -56
  157. package/dist/material/shader/helper.js.map +1 -1
  158. package/dist/material/sprite.js +301 -0
  159. package/dist/material/sprite.js.map +1 -0
  160. package/dist/material/sprite3d.js +301 -0
  161. package/dist/material/sprite3d.js.map +1 -0
  162. package/dist/material/sprite3d_std.js +116 -0
  163. package/dist/material/sprite3d_std.js.map +1 -0
  164. package/dist/material/sprite3dblueprint.js +235 -0
  165. package/dist/material/sprite3dblueprint.js.map +1 -0
  166. package/dist/material/sprite_std.js +116 -0
  167. package/dist/material/sprite_std.js.map +1 -0
  168. package/dist/material/spriteblueprint.js +235 -0
  169. package/dist/material/spriteblueprint.js.map +1 -0
  170. package/dist/material/terrain-cm.js +0 -1
  171. package/dist/material/terrain-cm.js.map +1 -1
  172. package/dist/material/unlit.js.map +1 -1
  173. package/dist/material/water.js.map +1 -1
  174. package/dist/posteffect/bloom.js.map +1 -1
  175. package/dist/posteffect/compositor.js.map +1 -1
  176. package/dist/posteffect/fxaa.js.map +1 -1
  177. package/dist/posteffect/grayscale.js.map +1 -1
  178. package/dist/posteffect/motionblur.js.map +1 -1
  179. package/dist/posteffect/posteffect.js.map +1 -1
  180. package/dist/posteffect/sao.js.map +1 -1
  181. package/dist/posteffect/ssr.js +7 -5
  182. package/dist/posteffect/ssr.js.map +1 -1
  183. package/dist/posteffect/taa.js.map +1 -1
  184. package/dist/posteffect/tonemap.js.map +1 -1
  185. package/dist/render/abuffer_oit.js +8 -6
  186. package/dist/render/abuffer_oit.js.map +1 -1
  187. package/dist/render/clipmap.js +0 -10
  188. package/dist/render/clipmap.js.map +1 -1
  189. package/dist/render/cluster_light.js +4 -4
  190. package/dist/render/cluster_light.js.map +1 -1
  191. package/dist/render/cull_visitor.js +15 -17
  192. package/dist/render/cull_visitor.js.map +1 -1
  193. package/dist/render/depthpass.js +3 -4
  194. package/dist/render/depthpass.js.map +1 -1
  195. package/dist/render/drawable.js +15 -0
  196. package/dist/render/drawable.js.map +1 -0
  197. package/dist/render/drawable_mixin.js +8 -4
  198. package/dist/render/drawable_mixin.js.map +1 -1
  199. package/dist/render/envlight.js.map +1 -1
  200. package/dist/render/fbm_wavegenerator.js +3 -1
  201. package/dist/render/fbm_wavegenerator.js.map +1 -1
  202. package/dist/render/fft_wavegenerator.js +67 -64
  203. package/dist/render/fft_wavegenerator.js.map +1 -1
  204. package/dist/render/fullscreenquad.js.map +1 -1
  205. package/dist/render/gerstner_wavegenerator.js +3 -1
  206. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  207. package/dist/render/globalbindgroup_allocator.js +3 -1
  208. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  209. package/dist/render/hzb.js +1 -2
  210. package/dist/render/hzb.js.map +1 -1
  211. package/dist/render/lightpass.js +13 -13
  212. package/dist/render/lightpass.js.map +1 -1
  213. package/dist/render/objectcolorpass.js +4 -7
  214. package/dist/render/objectcolorpass.js.map +1 -1
  215. package/dist/render/primitive.js +4 -5
  216. package/dist/render/primitive.js.map +1 -1
  217. package/dist/render/render_queue.js +10 -5
  218. package/dist/render/render_queue.js.map +1 -1
  219. package/dist/render/renderer.js +16 -22
  220. package/dist/render/renderer.js.map +1 -1
  221. package/dist/render/renderpass.js +27 -52
  222. package/dist/render/renderpass.js.map +1 -1
  223. package/dist/render/rendertarget.js +5 -0
  224. package/dist/render/rendertarget.js.map +1 -0
  225. package/dist/render/screenrendertarget.js +56 -0
  226. package/dist/render/screenrendertarget.js.map +1 -0
  227. package/dist/render/shadowmap_pass.js +4 -5
  228. package/dist/render/shadowmap_pass.js.map +1 -1
  229. package/dist/render/sky.js +16 -14
  230. package/dist/render/sky.js.map +1 -1
  231. package/dist/render/weightedblended_oit.js.map +1 -1
  232. package/dist/scene/basesprite.js +296 -0
  233. package/dist/scene/basesprite.js.map +1 -0
  234. package/dist/scene/batchgroup.js +4 -2
  235. package/dist/scene/batchgroup.js.map +1 -1
  236. package/dist/scene/environment.js +9 -10
  237. package/dist/scene/environment.js.map +1 -1
  238. package/dist/scene/graph_node.js.map +1 -1
  239. package/dist/scene/light.js +22 -4
  240. package/dist/scene/light.js.map +1 -1
  241. package/dist/scene/mesh.js +96 -26
  242. package/dist/scene/mesh.js.map +1 -1
  243. package/dist/scene/octree.js.map +1 -1
  244. package/dist/scene/particlesys.js +25 -22
  245. package/dist/scene/particlesys.js.map +1 -1
  246. package/dist/scene/raycast_visitor.js +0 -16
  247. package/dist/scene/raycast_visitor.js.map +1 -1
  248. package/dist/scene/scene.js +7 -4
  249. package/dist/scene/scene.js.map +1 -1
  250. package/dist/scene/scene_node.js +20 -35
  251. package/dist/scene/scene_node.js.map +1 -1
  252. package/dist/scene/sprite.js +18 -0
  253. package/dist/scene/sprite.js.map +1 -0
  254. package/dist/scene/sprite3d.js +18 -0
  255. package/dist/scene/sprite3d.js.map +1 -0
  256. package/dist/scene/terrain-cm/grass.js +5 -4
  257. package/dist/scene/terrain-cm/grass.js.map +1 -1
  258. package/dist/scene/terrain-cm/grassmaterial.js +3 -1
  259. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  260. package/dist/scene/terrain-cm/terrain-cm.js +65 -48
  261. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  262. package/dist/scene/water.js +35 -30
  263. package/dist/scene/water.js.map +1 -1
  264. package/dist/shaders/atmosphere.js +17 -29
  265. package/dist/shaders/atmosphere.js.map +1 -1
  266. package/dist/shaders/fog.js.map +1 -1
  267. package/dist/shaders/misc.js.map +1 -1
  268. package/dist/shaders/noise.js.map +1 -1
  269. package/dist/shaders/pbr.js.map +1 -1
  270. package/dist/shaders/shadow.js.map +1 -1
  271. package/dist/shaders/ssr.js.map +1 -1
  272. package/dist/shaders/temporal.js.map +1 -1
  273. package/dist/shaders/water.js.map +1 -1
  274. package/dist/shadow/esm.js +1 -1
  275. package/dist/shadow/esm.js.map +1 -1
  276. package/dist/shadow/pcf_opt.js.map +1 -1
  277. package/dist/shadow/pcf_pd.js.map +1 -1
  278. package/dist/shadow/shader.js +3 -1
  279. package/dist/shadow/shader.js.map +1 -1
  280. package/dist/shadow/shadow_impl.js.map +1 -1
  281. package/dist/shadow/shadowmapper.js +6 -18
  282. package/dist/shadow/shadowmapper.js.map +1 -1
  283. package/dist/shadow/ssm.js.map +1 -1
  284. package/dist/shadow/vsm.js.map +1 -1
  285. package/dist/shapes/box.js +9 -3
  286. package/dist/shapes/box.js.map +1 -1
  287. package/dist/shapes/cylinder.js +2 -2
  288. package/dist/shapes/cylinder.js.map +1 -1
  289. package/dist/shapes/plane.js +2 -2
  290. package/dist/shapes/plane.js.map +1 -1
  291. package/dist/shapes/shape.js +2 -1
  292. package/dist/shapes/shape.js.map +1 -1
  293. package/dist/shapes/sphere.js +2 -2
  294. package/dist/shapes/sphere.js.map +1 -1
  295. package/dist/shapes/tetrahedron.js +5 -6
  296. package/dist/shapes/tetrahedron.js.map +1 -1
  297. package/dist/shapes/torus.js +2 -2
  298. package/dist/shapes/torus.js.map +1 -1
  299. package/dist/utility/aabbtree.js +1 -1
  300. package/dist/utility/aabbtree.js.map +1 -1
  301. package/dist/utility/blueprint/common/constants.js +578 -104
  302. package/dist/utility/blueprint/common/constants.js.map +1 -1
  303. package/dist/utility/blueprint/common/math.js +471 -9
  304. package/dist/utility/blueprint/common/math.js.map +1 -1
  305. package/dist/utility/blueprint/common.js +1 -1
  306. package/dist/utility/blueprint/common.js.map +1 -1
  307. package/dist/utility/blueprint/material/func.js +9 -5
  308. package/dist/utility/blueprint/material/func.js.map +1 -1
  309. package/dist/utility/blueprint/material/inputs.js.map +1 -1
  310. package/dist/utility/blueprint/material/ir.js +288 -80
  311. package/dist/utility/blueprint/material/ir.js.map +1 -1
  312. package/dist/utility/blueprint/material/pbr.js +92 -4
  313. package/dist/utility/blueprint/material/pbr.js.map +1 -1
  314. package/dist/utility/blueprint/material/texture.js +62 -45
  315. package/dist/utility/blueprint/material/texture.js.map +1 -1
  316. package/dist/utility/blueprint/node.js +28 -1
  317. package/dist/utility/blueprint/node.js.map +1 -1
  318. package/dist/utility/bounding_volume.js.map +1 -1
  319. package/dist/utility/debug.js.map +1 -1
  320. package/dist/utility/draco/decoder.js.map +1 -1
  321. package/dist/utility/misc.js +1 -1
  322. package/dist/utility/misc.js.map +1 -1
  323. package/dist/utility/panorama.js.map +1 -1
  324. package/dist/utility/pmrem.js.map +1 -1
  325. package/dist/utility/rendermipmap.js.map +1 -1
  326. package/dist/utility/serialization/json.js +19 -15
  327. package/dist/utility/serialization/json.js.map +1 -1
  328. package/dist/utility/serialization/manager.js +89 -22
  329. package/dist/utility/serialization/manager.js.map +1 -1
  330. package/dist/utility/serialization/scene/animation.js +44 -41
  331. package/dist/utility/serialization/scene/animation.js.map +1 -1
  332. package/dist/utility/serialization/scene/batch.js +4 -3
  333. package/dist/utility/serialization/scene/batch.js.map +1 -1
  334. package/dist/utility/serialization/scene/camera.js +97 -9
  335. package/dist/utility/serialization/scene/camera.js.map +1 -1
  336. package/dist/utility/serialization/scene/common.js +102 -28
  337. package/dist/utility/serialization/scene/common.js.map +1 -1
  338. package/dist/utility/serialization/scene/light.js +14 -12
  339. package/dist/utility/serialization/scene/light.js.map +1 -1
  340. package/dist/utility/serialization/scene/material.js +216 -26
  341. package/dist/utility/serialization/scene/material.js.map +1 -1
  342. package/dist/utility/serialization/scene/mesh.js +71 -100
  343. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  344. package/dist/utility/serialization/scene/misc.js +3 -2
  345. package/dist/utility/serialization/scene/misc.js.map +1 -1
  346. package/dist/utility/serialization/scene/node.js +42 -9
  347. package/dist/utility/serialization/scene/node.js.map +1 -1
  348. package/dist/utility/serialization/scene/particle.js +7 -5
  349. package/dist/utility/serialization/scene/particle.js.map +1 -1
  350. package/dist/utility/serialization/scene/primitive.js +18 -15
  351. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  352. package/dist/utility/serialization/scene/scene.js +3 -2
  353. package/dist/utility/serialization/scene/scene.js.map +1 -1
  354. package/dist/utility/serialization/scene/sprite.js +164 -0
  355. package/dist/utility/serialization/scene/sprite.js.map +1 -0
  356. package/dist/utility/serialization/scene/terrain.js +8 -6
  357. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  358. package/dist/utility/serialization/scene/water.js +11 -9
  359. package/dist/utility/serialization/scene/water.js.map +1 -1
  360. package/dist/utility/serialization/types.js +6 -0
  361. package/dist/utility/serialization/types.js.map +1 -0
  362. package/dist/utility/shprojector.js.map +1 -1
  363. package/dist/utility/textures/ggxlut.js.map +1 -1
  364. package/dist/utility/textures/gradientnoise.js.map +1 -1
  365. package/dist/utility/textures/randomnoise.js.map +1 -1
  366. package/dist/values.js +14 -14
  367. package/dist/values.js.map +1 -1
  368. package/package.json +80 -80
@@ -0,0 +1,399 @@
1
+ import { Vector3, Quaternion } from '@zephyr3d/base';
2
+ import { IKSolver } from './ik_solver.js';
3
+ import { IKUtils } from './ik_utils.js';
4
+
5
+ /**
6
+ * FABRIK (Forward And Backward Reaching Inverse Kinematics) solver.
7
+ *
8
+ * @remarks
9
+ * FABRIK is an iterative IK algorithm that alternates between:
10
+ * 1. Forward pass: Move from end effector to root, pulling joints toward target
11
+ * 2. Backward pass: Move from root to end effector, restoring root position
12
+ *
13
+ * Supports multiple pole vectors to control the bending direction of different joints.
14
+ *
15
+ * @public
16
+ */ class FABRIKSolver extends IKSolver {
17
+ /** Map of joint index to pole vector configuration */ _poleVectors;
18
+ /** Twist constraints for each joint (indexed by joint index) */ _twistConstraints;
19
+ /**
20
+ * Create a FABRIK solver.
21
+ *
22
+ * @param chain - The IK chain to solve
23
+ * @param maxIterations - Maximum number of iterations (default: 15)
24
+ * @param tolerance - Convergence tolerance in world units (default: 0.001)
25
+ */ constructor(chain, maxIterations = 15, tolerance = 0.001){
26
+ super(chain, maxIterations, tolerance);
27
+ this._poleVectors = new Map();
28
+ this._twistConstraints = new Map();
29
+ // Set default twist constraints for all joints
30
+ for(let i = 0; i < chain.joints.length - 1; i++){
31
+ // Middle joints have more restrictive twist limits
32
+ const isMiddleJoint = i > 0 && i < chain.joints.length - 2;
33
+ this._twistConstraints.set(i, {
34
+ minTwist: isMiddleJoint ? -Math.PI * 0.2 : -Math.PI * 0.3,
35
+ maxTwist: isMiddleJoint ? Math.PI * 0.2 : Math.PI * 0.3,
36
+ smoothFactor: 0.3
37
+ });
38
+ }
39
+ }
40
+ /**
41
+ * Set twist constraint for a specific joint.
42
+ *
43
+ * @param jointIndex - Index of the joint
44
+ * @param minTwist - Minimum twist angle in radians
45
+ * @param maxTwist - Maximum twist angle in radians
46
+ * @param smoothFactor - Smoothing factor [0-1] (default: 0.3)
47
+ */ setTwistConstraint(jointIndex, minTwist, maxTwist, smoothFactor = 0.3) {
48
+ if (jointIndex < 0 || jointIndex >= this._chain.joints.length - 1) {
49
+ throw new Error(`Invalid joint index: ${jointIndex}`);
50
+ }
51
+ this._twistConstraints.set(jointIndex, {
52
+ minTwist,
53
+ maxTwist,
54
+ smoothFactor: Math.max(0, Math.min(1, smoothFactor))
55
+ });
56
+ }
57
+ /**
58
+ * Get twist constraint for a specific joint.
59
+ *
60
+ * @param jointIndex - Index of the joint
61
+ * @returns Twist constraint or undefined if not set
62
+ */ getTwistConstraint(jointIndex) {
63
+ return this._twistConstraints.get(jointIndex);
64
+ }
65
+ /**
66
+ * Remove twist constraint for a specific joint.
67
+ *
68
+ * @param jointIndex - Index of the joint
69
+ * @returns True if a constraint was removed
70
+ */ removeTwistConstraint(jointIndex) {
71
+ return this._twistConstraints.delete(jointIndex);
72
+ }
73
+ /**
74
+ * Clear all twist constraints.
75
+ */ clearTwistConstraints() {
76
+ this._twistConstraints.clear();
77
+ }
78
+ /**
79
+ * Set pole vector for a specific joint.
80
+ *
81
+ * @param jointIndex - Index of the joint to apply pole vector to
82
+ * @param poleVector - Pole vector position in world space
83
+ * @param weight - Weight of the pole vector constraint (0-1, default: 1)
84
+ */ setPoleVector(jointIndex, poleVector, weight = 1) {
85
+ if (jointIndex < 0 || jointIndex >= this._chain.joints.length) {
86
+ throw new Error(`Invalid joint index: ${jointIndex}`);
87
+ }
88
+ this._poleVectors.set(jointIndex, {
89
+ position: poleVector.clone(),
90
+ weight: Math.max(0, Math.min(1, weight))
91
+ });
92
+ }
93
+ /**
94
+ * Remove pole vector for a specific joint.
95
+ *
96
+ * @param jointIndex - Index of the joint to remove pole vector from
97
+ * @returns True if a pole vector was removed, false if none existed
98
+ */ removePoleVector(jointIndex) {
99
+ return this._poleVectors.delete(jointIndex);
100
+ }
101
+ /**
102
+ * Clear all pole vectors.
103
+ */ clearPoleVectors() {
104
+ this._poleVectors.clear();
105
+ }
106
+ /**
107
+ * Get pole vector configuration for a specific joint.
108
+ *
109
+ * @param jointIndex - Index of the joint
110
+ * @returns Pole vector configuration or undefined if not set
111
+ */ getPoleVector(jointIndex) {
112
+ const config = this._poleVectors.get(jointIndex);
113
+ if (config) {
114
+ return {
115
+ position: config.position.clone(),
116
+ weight: config.weight
117
+ };
118
+ }
119
+ return undefined;
120
+ }
121
+ /**
122
+ * Check if a joint has a pole vector.
123
+ *
124
+ * @param jointIndex - Index of the joint
125
+ * @returns True if the joint has a pole vector
126
+ */ hasPoleVector(jointIndex) {
127
+ return this._poleVectors.has(jointIndex);
128
+ }
129
+ /**
130
+ * Solve the IK chain to reach the target position using FABRIK algorithm.
131
+ *
132
+ * @param target - Target position for the end effector
133
+ * @returns True if converged within tolerance, false otherwise
134
+ */ solve(target) {
135
+ const joints = this._chain.joints;
136
+ // Update joint positions from scene nodes before solving
137
+ this._chain.updateFromNodes();
138
+ // Store original positions
139
+ this._chain.storeOriginalPositions();
140
+ const rootPos = joints[0].originalPosition.clone();
141
+ // Check if target is reachable
142
+ const distToTarget = Vector3.distance(rootPos, target);
143
+ if (distToTarget > this._chain.totalLength) {
144
+ // Target unreachable - stretch chain toward target
145
+ this._stretchToward(target);
146
+ return false;
147
+ }
148
+ let converged = false;
149
+ for(let iteration = 0; iteration < this._maxIterations; iteration++){
150
+ // Check convergence
151
+ const distToEnd = Vector3.distance(joints[joints.length - 1].position, target);
152
+ if (distToEnd < this._tolerance) {
153
+ converged = true;
154
+ break;
155
+ }
156
+ // Forward pass: end effector to root
157
+ this._forwardPass(target);
158
+ // Backward pass: root to end effector (constraints are applied within the pass)
159
+ this._backwardPass(rootPos);
160
+ }
161
+ return converged;
162
+ }
163
+ /**
164
+ * Apply the solved joint positions to scene nodes as rotations.
165
+ *
166
+ * @param weight - Blend weight (0 = original, 1 = full IK, default: 1)
167
+ */ applyToNodes(weight = 1) {
168
+ const joints = this._chain.joints;
169
+ for(let i = 0; i < joints.length - 1; i++){
170
+ const joint = joints[i];
171
+ const nextJoint = joints[i + 1];
172
+ // Calculate direction from current joint to next joint in world space
173
+ const originalDir = Vector3.sub(nextJoint.originalPosition, joint.originalPosition, new Vector3());
174
+ const newDir = Vector3.sub(nextJoint.position, joint.position, new Vector3());
175
+ const originalDirNorm = Vector3.normalize(originalDir, new Vector3());
176
+ const newDirNorm = Vector3.normalize(newDir, new Vector3());
177
+ // Calculate basic rotation
178
+ const deltaRotation = new Quaternion();
179
+ IKUtils.fromToRotation(originalDirNorm, newDirNorm, deltaRotation);
180
+ // Calculate new world rotation
181
+ let worldRotation = Quaternion.multiply(deltaRotation, joint.originalRotation, new Quaternion());
182
+ // Apply swing-twist constraint to prevent over-rotation
183
+ const swing = new Quaternion();
184
+ const twist = new Quaternion();
185
+ IKUtils.decomposeSwingTwist(worldRotation, newDirNorm, swing, twist);
186
+ // Get twist angle
187
+ let twistAngle = IKUtils.getTwistAngle(twist, newDirNorm);
188
+ // Get twist constraint for this joint
189
+ const constraint = this._twistConstraints.get(i);
190
+ if (constraint) {
191
+ // Clamp and smooth twist
192
+ twistAngle = IKUtils.clampAndSmoothTwist(twistAngle, joint.previousTwist, constraint.minTwist, constraint.maxTwist, constraint.smoothFactor);
193
+ }
194
+ // Store for next frame
195
+ joint.previousTwist = twistAngle;
196
+ // Reconstruct rotation from clamped twist: Q = Twist * Swing
197
+ const clampedTwist = new Quaternion();
198
+ IKUtils.createTwist(twistAngle, newDirNorm, clampedTwist);
199
+ worldRotation = Quaternion.multiply(clampedTwist, swing, worldRotation);
200
+ // Blend with original rotation based on weight
201
+ if (weight < 1) {
202
+ Quaternion.slerp(joint.originalRotation, worldRotation, weight, worldRotation);
203
+ }
204
+ // Store this frame's world rotation
205
+ if (!joint.previousIKRotation) {
206
+ joint.previousIKRotation = worldRotation.clone();
207
+ } else {
208
+ joint.previousIKRotation.set(worldRotation);
209
+ }
210
+ // Convert world rotation to local rotation (relative to parent)
211
+ if (joint.node.parent) {
212
+ const parentWorldRotation = new Quaternion();
213
+ joint.node.parent.worldMatrix.decompose(null, parentWorldRotation, null);
214
+ // localRotation = conjugate(parentWorldRotation) * worldRotation
215
+ const parentInvRotation = Quaternion.conjugate(parentWorldRotation, new Quaternion());
216
+ const localRotation = Quaternion.multiply(parentInvRotation, worldRotation, new Quaternion());
217
+ joint.node.rotation = localRotation;
218
+ } else {
219
+ // Root node has no parent, world rotation is local rotation
220
+ joint.node.rotation = worldRotation;
221
+ }
222
+ }
223
+ }
224
+ /**
225
+ * Forward pass: move from end effector toward root.
226
+ * Each joint is pulled toward its child, maintaining bone length.
227
+ * Constraints are applied immediately after each joint adjustment.
228
+ *
229
+ * @param target - Target position for end effector
230
+ */ _forwardPass(target) {
231
+ const joints = this._chain.joints;
232
+ // Set end effector to target
233
+ joints[joints.length - 1].position.set(target);
234
+ // Move backward through chain
235
+ for(let i = joints.length - 2; i >= 0; i--){
236
+ const joint = joints[i];
237
+ const childJoint = joints[i + 1];
238
+ // Calculate direction from child to current joint
239
+ const direction = Vector3.sub(joint.position, childJoint.position, new Vector3());
240
+ const distance = direction.magnitude;
241
+ if (distance > 0.000001) {
242
+ direction.scaleBy(1 / distance);
243
+ // Place joint at correct distance from child
244
+ Vector3.scale(direction, joint.boneLength, direction);
245
+ Vector3.add(childJoint.position, direction, joint.position);
246
+ }
247
+ // Apply constraints immediately after adjusting this joint
248
+ // This ensures constraints are integrated into the FABRIK algorithm
249
+ if (i > 0) {
250
+ // Apply constraints for joint i (not for root joint)
251
+ this._applyConstraintsForJoint(i);
252
+ }
253
+ }
254
+ }
255
+ /**
256
+ * Backward pass: move from root toward end effector.
257
+ * Each joint is pulled toward its parent, maintaining bone length.
258
+ * Constraints are applied immediately after each joint adjustment.
259
+ *
260
+ * @param rootPos - Original root position to restore
261
+ */ _backwardPass(rootPos) {
262
+ const joints = this._chain.joints;
263
+ // Restore root position
264
+ joints[0].position.set(rootPos);
265
+ // Move forward through chain
266
+ for(let i = 0; i < joints.length - 1; i++){
267
+ const joint = joints[i];
268
+ const childJoint = joints[i + 1];
269
+ // Calculate direction from current joint to child
270
+ const direction = Vector3.sub(childJoint.position, joint.position, new Vector3());
271
+ const distance = direction.magnitude;
272
+ if (distance > 0.000001) {
273
+ direction.scaleBy(1 / distance);
274
+ // Place child at correct distance from current joint
275
+ Vector3.scale(direction, joint.boneLength, direction);
276
+ Vector3.add(joint.position, direction, childJoint.position);
277
+ }
278
+ // Apply constraints immediately after adjusting this joint
279
+ // This ensures constraints are integrated into the FABRIK algorithm
280
+ this._applyConstraintsForJoint(i + 1);
281
+ }
282
+ }
283
+ /**
284
+ * Apply constraints for a specific joint.
285
+ * This is called during the FABRIK passes to integrate constraints into the algorithm.
286
+ *
287
+ * @param jointIndex - Index of the joint to apply constraints to
288
+ */ _applyConstraintsForJoint(jointIndex) {
289
+ const constraints = this._chain.constraints;
290
+ // Apply non-pole-vector constraints
291
+ // IKPoleVectorConstraint is deprecated and ignored in favor of built-in pole vectors
292
+ for (const constraint of constraints){
293
+ if (constraint.jointIndex === jointIndex) {
294
+ // Skip IKPoleVectorConstraint - use built-in pole vectors instead
295
+ if (constraint.constructor.name === 'IKPoleVectorConstraint') {
296
+ continue;
297
+ }
298
+ constraint.apply(this._chain.joints);
299
+ }
300
+ }
301
+ // Apply built-in pole vector if configured for this joint
302
+ const poleConfig = this._poleVectors.get(jointIndex);
303
+ if (poleConfig && poleConfig.weight > 0.001) {
304
+ this._applyPoleVectorTwist(jointIndex, poleConfig.position, poleConfig.weight);
305
+ }
306
+ }
307
+ /**
308
+ * Apply pole vector twist to a specific joint.
309
+ *
310
+ * @remarks
311
+ * This method adjusts the joint position to align with the pole vector.
312
+ * It uses the same algorithm as IKPoleVectorConstraint but is integrated
313
+ * into the solver for better performance and control.
314
+ *
315
+ * @param jointIndex - Index of the pole joint
316
+ * @param poleVector - Pole vector position in world space
317
+ * @param weight - Weight of the pole vector constraint (0-1)
318
+ */ _applyPoleVectorTwist(jointIndex, poleVector, weight) {
319
+ const joints = this._chain.joints;
320
+ // We need at least 3 joints to apply pole vector (parent, pole joint, child)
321
+ if (jointIndex < 1 || jointIndex >= joints.length - 1) {
322
+ return;
323
+ }
324
+ const parentJoint = joints[jointIndex - 1];
325
+ const currentJoint = joints[jointIndex];
326
+ const childJoint = joints[jointIndex + 1];
327
+ const parentPos = parentJoint.position;
328
+ const currentPos = currentJoint.position;
329
+ const childPos = childJoint.position;
330
+ // Calculate the plane defined by parent, child, and pole vector
331
+ // Vector from parent to child (the "line" we're bending around)
332
+ const parentToChild = Vector3.sub(childPos, parentPos, new Vector3());
333
+ const lineLength = parentToChild.magnitude;
334
+ if (lineLength < 0.000001) {
335
+ return; // Parent and child are at same position
336
+ }
337
+ const lineDir = Vector3.scale(parentToChild, 1 / lineLength, new Vector3());
338
+ // Project current joint onto the parent-child line
339
+ const parentToCurrent = Vector3.sub(currentPos, parentPos, new Vector3());
340
+ const projectionLength = Vector3.dot(parentToCurrent, lineDir);
341
+ const projectionPoint = Vector3.scale(lineDir, projectionLength, new Vector3());
342
+ Vector3.add(parentPos, projectionPoint, projectionPoint);
343
+ // Vector from projection point to current joint (perpendicular to line)
344
+ const perpendicular = Vector3.sub(currentPos, projectionPoint, new Vector3());
345
+ const perpLength = perpendicular.magnitude;
346
+ if (perpLength < 0.000001) {
347
+ return; // Current joint is on the line
348
+ }
349
+ // Calculate desired direction toward pole vector
350
+ const parentToPole = Vector3.sub(poleVector, parentPos, new Vector3());
351
+ const poleProjectionLength = Vector3.dot(parentToPole, lineDir);
352
+ const poleProjectionPoint = Vector3.scale(lineDir, poleProjectionLength, new Vector3());
353
+ Vector3.add(parentPos, poleProjectionPoint, poleProjectionPoint);
354
+ // Direction from projection to pole (perpendicular to line)
355
+ const poleDirection = Vector3.sub(poleVector, poleProjectionPoint, new Vector3());
356
+ const poleDirLength = poleDirection.magnitude;
357
+ if (poleDirLength < 0.000001) {
358
+ return; // Pole is on the line
359
+ }
360
+ poleDirection.scaleBy(1 / poleDirLength);
361
+ // Calculate new position for current joint
362
+ // Keep the same distance from the line, but rotate toward pole direction
363
+ const desiredPerpendicular = Vector3.scale(poleDirection, perpLength, new Vector3());
364
+ const newPosition = Vector3.add(projectionPoint, desiredPerpendicular, new Vector3());
365
+ // Apply weight (blend between original and constrained position)
366
+ if (weight < 0.999) {
367
+ const blended = new Vector3();
368
+ blended.x = currentPos.x + (newPosition.x - currentPos.x) * weight;
369
+ blended.y = currentPos.y + (newPosition.y - currentPos.y) * weight;
370
+ blended.z = currentPos.z + (newPosition.z - currentPos.z) * weight;
371
+ currentJoint.position.set(blended);
372
+ } else {
373
+ currentJoint.position.set(newPosition);
374
+ }
375
+ }
376
+ /**
377
+ * Stretch the chain toward an unreachable target.
378
+ * All joints are aligned in a straight line toward the target.
379
+ *
380
+ * @param target - Unreachable target position
381
+ */ _stretchToward(target) {
382
+ const joints = this._chain.joints;
383
+ const rootPos = joints[0].originalPosition;
384
+ // Calculate direction from root to target
385
+ const direction = Vector3.sub(target, rootPos, new Vector3()).inplaceNormalize();
386
+ // Place each joint along the direction at its bone length
387
+ let currentPos = rootPos.clone();
388
+ for(let i = 0; i < joints.length; i++){
389
+ joints[i].position.set(currentPos);
390
+ if (i < joints.length - 1) {
391
+ const offset = Vector3.scale(direction, joints[i].boneLength, new Vector3());
392
+ currentPos.addBy(offset);
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ export { FABRIKSolver };
399
+ //# sourceMappingURL=fabrik_solver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fabrik_solver.js","sources":["../../../src/animation/ik/fabrik_solver.ts"],"sourcesContent":["import { Vector3, Quaternion } from '@zephyr3d/base';\r\nimport { IKSolver } from './ik_solver';\r\nimport type { IKChain } from './ik_chain';\r\nimport { IKUtils } from './ik_utils';\r\nimport type { TwistConstraint } from './ik_constraint';\r\n\r\n/**\r\n * Pole vector configuration for a specific joint.\r\n */\r\ninterface PoleVectorConfig {\r\n /** Pole vector position in world space */\r\n position: Vector3;\r\n /** Weight of the pole vector constraint (0-1) */\r\n weight: number;\r\n}\r\n\r\n/**\r\n * FABRIK (Forward And Backward Reaching Inverse Kinematics) solver.\r\n *\r\n * @remarks\r\n * FABRIK is an iterative IK algorithm that alternates between:\r\n * 1. Forward pass: Move from end effector to root, pulling joints toward target\r\n * 2. Backward pass: Move from root to end effector, restoring root position\r\n *\r\n * Supports multiple pole vectors to control the bending direction of different joints.\r\n *\r\n * @public\r\n */\r\nexport class FABRIKSolver extends IKSolver {\r\n /** Map of joint index to pole vector configuration */\r\n private _poleVectors: Map<number, PoleVectorConfig>;\r\n /** Twist constraints for each joint (indexed by joint index) */\r\n private _twistConstraints: Map<number, TwistConstraint>;\r\n\r\n /**\r\n * Create a FABRIK solver.\r\n *\r\n * @param chain - The IK chain to solve\r\n * @param maxIterations - Maximum number of iterations (default: 15)\r\n * @param tolerance - Convergence tolerance in world units (default: 0.001)\r\n */\r\n constructor(chain: IKChain, maxIterations = 15, tolerance = 0.001) {\r\n super(chain, maxIterations, tolerance);\r\n this._poleVectors = new Map();\r\n this._twistConstraints = new Map();\r\n\r\n // Set default twist constraints for all joints\r\n for (let i = 0; i < chain.joints.length - 1; i++) {\r\n // Middle joints have more restrictive twist limits\r\n const isMiddleJoint = i > 0 && i < chain.joints.length - 2;\r\n this._twistConstraints.set(i, {\r\n minTwist: isMiddleJoint ? -Math.PI * 0.2 : -Math.PI * 0.3,\r\n maxTwist: isMiddleJoint ? Math.PI * 0.2 : Math.PI * 0.3,\r\n smoothFactor: 0.3\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Set twist constraint for a specific joint.\r\n *\r\n * @param jointIndex - Index of the joint\r\n * @param minTwist - Minimum twist angle in radians\r\n * @param maxTwist - Maximum twist angle in radians\r\n * @param smoothFactor - Smoothing factor [0-1] (default: 0.3)\r\n */\r\n setTwistConstraint(\r\n jointIndex: number,\r\n minTwist: number,\r\n maxTwist: number,\r\n smoothFactor: number = 0.3\r\n ): void {\r\n if (jointIndex < 0 || jointIndex >= this._chain.joints.length - 1) {\r\n throw new Error(`Invalid joint index: ${jointIndex}`);\r\n }\r\n\r\n this._twistConstraints.set(jointIndex, {\r\n minTwist,\r\n maxTwist,\r\n smoothFactor: Math.max(0, Math.min(1, smoothFactor))\r\n });\r\n }\r\n\r\n /**\r\n * Get twist constraint for a specific joint.\r\n *\r\n * @param jointIndex - Index of the joint\r\n * @returns Twist constraint or undefined if not set\r\n */\r\n getTwistConstraint(jointIndex: number): TwistConstraint | undefined {\r\n return this._twistConstraints.get(jointIndex);\r\n }\r\n\r\n /**\r\n * Remove twist constraint for a specific joint.\r\n *\r\n * @param jointIndex - Index of the joint\r\n * @returns True if a constraint was removed\r\n */\r\n removeTwistConstraint(jointIndex: number): boolean {\r\n return this._twistConstraints.delete(jointIndex);\r\n }\r\n\r\n /**\r\n * Clear all twist constraints.\r\n */\r\n clearTwistConstraints(): void {\r\n this._twistConstraints.clear();\r\n }\r\n\r\n /**\r\n * Set pole vector for a specific joint.\r\n *\r\n * @param jointIndex - Index of the joint to apply pole vector to\r\n * @param poleVector - Pole vector position in world space\r\n * @param weight - Weight of the pole vector constraint (0-1, default: 1)\r\n */\r\n setPoleVector(jointIndex: number, poleVector: Vector3, weight = 1): void {\r\n if (jointIndex < 0 || jointIndex >= this._chain.joints.length) {\r\n throw new Error(`Invalid joint index: ${jointIndex}`);\r\n }\r\n\r\n this._poleVectors.set(jointIndex, {\r\n position: poleVector.clone(),\r\n weight: Math.max(0, Math.min(1, weight))\r\n });\r\n }\r\n\r\n /**\r\n * Remove pole vector for a specific joint.\r\n *\r\n * @param jointIndex - Index of the joint to remove pole vector from\r\n * @returns True if a pole vector was removed, false if none existed\r\n */\r\n removePoleVector(jointIndex: number): boolean {\r\n return this._poleVectors.delete(jointIndex);\r\n }\r\n\r\n /**\r\n * Clear all pole vectors.\r\n */\r\n clearPoleVectors(): void {\r\n this._poleVectors.clear();\r\n }\r\n\r\n /**\r\n * Get pole vector configuration for a specific joint.\r\n *\r\n * @param jointIndex - Index of the joint\r\n * @returns Pole vector configuration or undefined if not set\r\n */\r\n getPoleVector(jointIndex: number): { position: Vector3; weight: number } | undefined {\r\n const config = this._poleVectors.get(jointIndex);\r\n if (config) {\r\n return {\r\n position: config.position.clone(),\r\n weight: config.weight\r\n };\r\n }\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Check if a joint has a pole vector.\r\n *\r\n * @param jointIndex - Index of the joint\r\n * @returns True if the joint has a pole vector\r\n */\r\n hasPoleVector(jointIndex: number): boolean {\r\n return this._poleVectors.has(jointIndex);\r\n }\r\n\r\n /**\r\n * Solve the IK chain to reach the target position using FABRIK algorithm.\r\n *\r\n * @param target - Target position for the end effector\r\n * @returns True if converged within tolerance, false otherwise\r\n */\r\n solve(target: Vector3): boolean {\r\n const joints = this._chain.joints;\r\n\r\n // Update joint positions from scene nodes before solving\r\n this._chain.updateFromNodes();\r\n\r\n // Store original positions\r\n this._chain.storeOriginalPositions();\r\n\r\n const rootPos = joints[0].originalPosition.clone();\r\n\r\n // Check if target is reachable\r\n const distToTarget = Vector3.distance(rootPos, target);\r\n if (distToTarget > this._chain.totalLength) {\r\n // Target unreachable - stretch chain toward target\r\n this._stretchToward(target);\r\n return false;\r\n }\r\n\r\n let converged = false;\r\n for (let iteration = 0; iteration < this._maxIterations; iteration++) {\r\n // Check convergence\r\n const distToEnd = Vector3.distance(joints[joints.length - 1].position, target);\r\n if (distToEnd < this._tolerance) {\r\n converged = true;\r\n break;\r\n }\r\n\r\n // Forward pass: end effector to root\r\n this._forwardPass(target);\r\n\r\n // Backward pass: root to end effector (constraints are applied within the pass)\r\n this._backwardPass(rootPos);\r\n }\r\n\r\n return converged;\r\n }\r\n\r\n /**\r\n * Apply the solved joint positions to scene nodes as rotations.\r\n *\r\n * @param weight - Blend weight (0 = original, 1 = full IK, default: 1)\r\n */\r\n applyToNodes(weight = 1): void {\r\n const joints = this._chain.joints;\r\n\r\n for (let i = 0; i < joints.length - 1; i++) {\r\n const joint = joints[i];\r\n const nextJoint = joints[i + 1];\r\n\r\n // Calculate direction from current joint to next joint in world space\r\n const originalDir = Vector3.sub(nextJoint.originalPosition, joint.originalPosition, new Vector3());\r\n const newDir = Vector3.sub(nextJoint.position, joint.position, new Vector3());\r\n\r\n const originalDirNorm = Vector3.normalize(originalDir, new Vector3());\r\n const newDirNorm = Vector3.normalize(newDir, new Vector3());\r\n\r\n // Calculate basic rotation\r\n const deltaRotation = new Quaternion();\r\n IKUtils.fromToRotation(originalDirNorm, newDirNorm, deltaRotation);\r\n\r\n // Calculate new world rotation\r\n let worldRotation = Quaternion.multiply(deltaRotation, joint.originalRotation, new Quaternion());\r\n\r\n // Apply swing-twist constraint to prevent over-rotation\r\n const swing = new Quaternion();\r\n const twist = new Quaternion();\r\n IKUtils.decomposeSwingTwist(worldRotation, newDirNorm, swing, twist);\r\n\r\n // Get twist angle\r\n let twistAngle = IKUtils.getTwistAngle(twist, newDirNorm);\r\n\r\n // Get twist constraint for this joint\r\n const constraint = this._twistConstraints.get(i);\r\n if (constraint) {\r\n // Clamp and smooth twist\r\n twistAngle = IKUtils.clampAndSmoothTwist(\r\n twistAngle,\r\n joint.previousTwist,\r\n constraint.minTwist,\r\n constraint.maxTwist,\r\n constraint.smoothFactor\r\n );\r\n }\r\n\r\n // Store for next frame\r\n joint.previousTwist = twistAngle;\r\n\r\n // Reconstruct rotation from clamped twist: Q = Twist * Swing\r\n const clampedTwist = new Quaternion();\r\n IKUtils.createTwist(twistAngle, newDirNorm, clampedTwist);\r\n worldRotation = Quaternion.multiply(clampedTwist, swing, worldRotation);\r\n\r\n // Blend with original rotation based on weight\r\n if (weight < 1) {\r\n Quaternion.slerp(joint.originalRotation, worldRotation, weight, worldRotation);\r\n }\r\n\r\n // Store this frame's world rotation\r\n if (!joint.previousIKRotation) {\r\n joint.previousIKRotation = worldRotation.clone();\r\n } else {\r\n joint.previousIKRotation.set(worldRotation);\r\n }\r\n\r\n // Convert world rotation to local rotation (relative to parent)\r\n if (joint.node.parent) {\r\n const parentWorldRotation = new Quaternion();\r\n joint.node.parent.worldMatrix.decompose(null, parentWorldRotation, null);\r\n\r\n // localRotation = conjugate(parentWorldRotation) * worldRotation\r\n const parentInvRotation = Quaternion.conjugate(parentWorldRotation, new Quaternion());\r\n const localRotation = Quaternion.multiply(parentInvRotation, worldRotation, new Quaternion());\r\n\r\n joint.node.rotation = localRotation;\r\n } else {\r\n // Root node has no parent, world rotation is local rotation\r\n joint.node.rotation = worldRotation;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Forward pass: move from end effector toward root.\r\n * Each joint is pulled toward its child, maintaining bone length.\r\n * Constraints are applied immediately after each joint adjustment.\r\n *\r\n * @param target - Target position for end effector\r\n */\r\n private _forwardPass(target: Vector3): void {\r\n const joints = this._chain.joints;\r\n\r\n // Set end effector to target\r\n joints[joints.length - 1].position.set(target);\r\n\r\n // Move backward through chain\r\n for (let i = joints.length - 2; i >= 0; i--) {\r\n const joint = joints[i];\r\n const childJoint = joints[i + 1];\r\n\r\n // Calculate direction from child to current joint\r\n const direction = Vector3.sub(joint.position, childJoint.position, new Vector3());\r\n const distance = direction.magnitude;\r\n\r\n if (distance > 0.000001) {\r\n direction.scaleBy(1 / distance);\r\n // Place joint at correct distance from child\r\n Vector3.scale(direction, joint.boneLength, direction);\r\n Vector3.add(childJoint.position, direction, joint.position);\r\n }\r\n\r\n // Apply constraints immediately after adjusting this joint\r\n // This ensures constraints are integrated into the FABRIK algorithm\r\n if (i > 0) {\r\n // Apply constraints for joint i (not for root joint)\r\n this._applyConstraintsForJoint(i);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Backward pass: move from root toward end effector.\r\n * Each joint is pulled toward its parent, maintaining bone length.\r\n * Constraints are applied immediately after each joint adjustment.\r\n *\r\n * @param rootPos - Original root position to restore\r\n */\r\n private _backwardPass(rootPos: Vector3): void {\r\n const joints = this._chain.joints;\r\n\r\n // Restore root position\r\n joints[0].position.set(rootPos);\r\n\r\n // Move forward through chain\r\n for (let i = 0; i < joints.length - 1; i++) {\r\n const joint = joints[i];\r\n const childJoint = joints[i + 1];\r\n\r\n // Calculate direction from current joint to child\r\n const direction = Vector3.sub(childJoint.position, joint.position, new Vector3());\r\n const distance = direction.magnitude;\r\n\r\n if (distance > 0.000001) {\r\n direction.scaleBy(1 / distance);\r\n // Place child at correct distance from current joint\r\n Vector3.scale(direction, joint.boneLength, direction);\r\n Vector3.add(joint.position, direction, childJoint.position);\r\n }\r\n\r\n // Apply constraints immediately after adjusting this joint\r\n // This ensures constraints are integrated into the FABRIK algorithm\r\n this._applyConstraintsForJoint(i + 1);\r\n }\r\n }\r\n\r\n /**\r\n * Apply constraints for a specific joint.\r\n * This is called during the FABRIK passes to integrate constraints into the algorithm.\r\n *\r\n * @param jointIndex - Index of the joint to apply constraints to\r\n */\r\n private _applyConstraintsForJoint(jointIndex: number): void {\r\n const constraints = this._chain.constraints;\r\n\r\n // Apply non-pole-vector constraints\r\n // IKPoleVectorConstraint is deprecated and ignored in favor of built-in pole vectors\r\n for (const constraint of constraints) {\r\n if (constraint.jointIndex === jointIndex) {\r\n // Skip IKPoleVectorConstraint - use built-in pole vectors instead\r\n if (constraint.constructor.name === 'IKPoleVectorConstraint') {\r\n continue;\r\n }\r\n constraint.apply(this._chain.joints);\r\n }\r\n }\r\n\r\n // Apply built-in pole vector if configured for this joint\r\n const poleConfig = this._poleVectors.get(jointIndex);\r\n if (poleConfig && poleConfig.weight > 0.001) {\r\n this._applyPoleVectorTwist(jointIndex, poleConfig.position, poleConfig.weight);\r\n }\r\n }\r\n\r\n /**\r\n * Apply pole vector twist to a specific joint.\r\n *\r\n * @remarks\r\n * This method adjusts the joint position to align with the pole vector.\r\n * It uses the same algorithm as IKPoleVectorConstraint but is integrated\r\n * into the solver for better performance and control.\r\n *\r\n * @param jointIndex - Index of the pole joint\r\n * @param poleVector - Pole vector position in world space\r\n * @param weight - Weight of the pole vector constraint (0-1)\r\n */\r\n private _applyPoleVectorTwist(jointIndex: number, poleVector: Vector3, weight: number): void {\r\n const joints = this._chain.joints;\r\n\r\n // We need at least 3 joints to apply pole vector (parent, pole joint, child)\r\n if (jointIndex < 1 || jointIndex >= joints.length - 1) {\r\n return;\r\n }\r\n\r\n const parentJoint = joints[jointIndex - 1];\r\n const currentJoint = joints[jointIndex];\r\n const childJoint = joints[jointIndex + 1];\r\n\r\n const parentPos = parentJoint.position;\r\n const currentPos = currentJoint.position;\r\n const childPos = childJoint.position;\r\n\r\n // Calculate the plane defined by parent, child, and pole vector\r\n // Vector from parent to child (the \"line\" we're bending around)\r\n const parentToChild = Vector3.sub(childPos, parentPos, new Vector3());\r\n const lineLength = parentToChild.magnitude;\r\n\r\n if (lineLength < 0.000001) {\r\n return; // Parent and child are at same position\r\n }\r\n\r\n const lineDir = Vector3.scale(parentToChild, 1 / lineLength, new Vector3());\r\n\r\n // Project current joint onto the parent-child line\r\n const parentToCurrent = Vector3.sub(currentPos, parentPos, new Vector3());\r\n const projectionLength = Vector3.dot(parentToCurrent, lineDir);\r\n const projectionPoint = Vector3.scale(lineDir, projectionLength, new Vector3());\r\n Vector3.add(parentPos, projectionPoint, projectionPoint);\r\n\r\n // Vector from projection point to current joint (perpendicular to line)\r\n const perpendicular = Vector3.sub(currentPos, projectionPoint, new Vector3());\r\n const perpLength = perpendicular.magnitude;\r\n\r\n if (perpLength < 0.000001) {\r\n return; // Current joint is on the line\r\n }\r\n\r\n // Calculate desired direction toward pole vector\r\n const parentToPole = Vector3.sub(poleVector, parentPos, new Vector3());\r\n const poleProjectionLength = Vector3.dot(parentToPole, lineDir);\r\n const poleProjectionPoint = Vector3.scale(lineDir, poleProjectionLength, new Vector3());\r\n Vector3.add(parentPos, poleProjectionPoint, poleProjectionPoint);\r\n\r\n // Direction from projection to pole (perpendicular to line)\r\n const poleDirection = Vector3.sub(poleVector, poleProjectionPoint, new Vector3());\r\n const poleDirLength = poleDirection.magnitude;\r\n\r\n if (poleDirLength < 0.000001) {\r\n return; // Pole is on the line\r\n }\r\n\r\n poleDirection.scaleBy(1 / poleDirLength);\r\n\r\n // Calculate new position for current joint\r\n // Keep the same distance from the line, but rotate toward pole direction\r\n const desiredPerpendicular = Vector3.scale(poleDirection, perpLength, new Vector3());\r\n const newPosition = Vector3.add(projectionPoint, desiredPerpendicular, new Vector3());\r\n\r\n // Apply weight (blend between original and constrained position)\r\n if (weight < 0.999) {\r\n const blended = new Vector3();\r\n blended.x = currentPos.x + (newPosition.x - currentPos.x) * weight;\r\n blended.y = currentPos.y + (newPosition.y - currentPos.y) * weight;\r\n blended.z = currentPos.z + (newPosition.z - currentPos.z) * weight;\r\n currentJoint.position.set(blended);\r\n } else {\r\n currentJoint.position.set(newPosition);\r\n }\r\n }\r\n\r\n /**\r\n * Stretch the chain toward an unreachable target.\r\n * All joints are aligned in a straight line toward the target.\r\n *\r\n * @param target - Unreachable target position\r\n */\r\n private _stretchToward(target: Vector3): void {\r\n const joints = this._chain.joints;\r\n const rootPos = joints[0].originalPosition;\r\n\r\n // Calculate direction from root to target\r\n const direction = Vector3.sub(target, rootPos, new Vector3()).inplaceNormalize();\r\n\r\n // Place each joint along the direction at its bone length\r\n let currentPos = rootPos.clone();\r\n for (let i = 0; i < joints.length; i++) {\r\n joints[i].position.set(currentPos);\r\n if (i < joints.length - 1) {\r\n const offset = Vector3.scale(direction, joints[i].boneLength, new Vector3());\r\n currentPos.addBy(offset);\r\n }\r\n }\r\n }\r\n}\r\n"],"names":["FABRIKSolver","IKSolver","chain","maxIterations","tolerance","_poleVectors","Map","_twistConstraints","i","joints","length","isMiddleJoint","set","minTwist","Math","PI","maxTwist","smoothFactor","setTwistConstraint","jointIndex","_chain","Error","max","min","getTwistConstraint","get","removeTwistConstraint","delete","clearTwistConstraints","clear","setPoleVector","poleVector","weight","position","clone","removePoleVector","clearPoleVectors","getPoleVector","config","undefined","hasPoleVector","has","solve","target","updateFromNodes","storeOriginalPositions","rootPos","originalPosition","distToTarget","Vector3","distance","totalLength","_stretchToward","converged","iteration","_maxIterations","distToEnd","_tolerance","_forwardPass","_backwardPass","applyToNodes","joint","nextJoint","originalDir","sub","newDir","originalDirNorm","normalize","newDirNorm","deltaRotation","Quaternion","IKUtils","fromToRotation","worldRotation","multiply","originalRotation","swing","twist","decomposeSwingTwist","twistAngle","getTwistAngle","constraint","clampAndSmoothTwist","previousTwist","clampedTwist","createTwist","slerp","previousIKRotation","node","parent","parentWorldRotation","worldMatrix","decompose","parentInvRotation","conjugate","localRotation","rotation","childJoint","direction","magnitude","scaleBy","scale","boneLength","add","_applyConstraintsForJoint","constraints","name","apply","poleConfig","_applyPoleVectorTwist","parentJoint","currentJoint","parentPos","currentPos","childPos","parentToChild","lineLength","lineDir","parentToCurrent","projectionLength","dot","projectionPoint","perpendicular","perpLength","parentToPole","poleProjectionLength","poleProjectionPoint","poleDirection","poleDirLength","desiredPerpendicular","newPosition","blended","x","y","z","inplaceNormalize","offset","addBy"],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ import { Vector3 } from '@zephyr3d/base';
2
+ import { IKConstraint } from './ik_constraint.js';
3
+
4
+ /**
5
+ * Angle constraint for IK joints.
6
+ *
7
+ * Limits the angle between the bone from parent to this joint
8
+ * and the bone from this joint to child.
9
+ *
10
+ * @remarks
11
+ * This is useful for joints like elbows and knees that have
12
+ * limited range of motion.
13
+ *
14
+ * @public
15
+ */ class IKAngleConstraint extends IKConstraint {
16
+ /** Minimum angle in degrees */ _minAngle;
17
+ /** Maximum angle in degrees */ _maxAngle;
18
+ /**
19
+ * Create an angle constraint.
20
+ *
21
+ * @param jointIndex - Index of the joint in the chain
22
+ * @param minAngle - Minimum angle in degrees (0 = straight)
23
+ * @param maxAngle - Maximum angle in degrees (180 = fully bent)
24
+ */ constructor(jointIndex, minAngle, maxAngle){
25
+ super(jointIndex);
26
+ this._minAngle = Math.max(0, minAngle);
27
+ this._maxAngle = Math.min(180, maxAngle);
28
+ if (this._minAngle > this._maxAngle) {
29
+ throw new Error('IKAngleConstraint: minAngle must be <= maxAngle');
30
+ }
31
+ }
32
+ /**
33
+ * Get the minimum angle in degrees.
34
+ */ get minAngle() {
35
+ return this._minAngle;
36
+ }
37
+ /**
38
+ * Set the minimum angle in degrees.
39
+ */ set minAngle(value) {
40
+ this._minAngle = Math.max(0, Math.min(value, this._maxAngle));
41
+ }
42
+ /**
43
+ * Get the maximum angle in degrees.
44
+ */ get maxAngle() {
45
+ return this._maxAngle;
46
+ }
47
+ /**
48
+ * Set the maximum angle in degrees.
49
+ */ set maxAngle(value) {
50
+ this._maxAngle = Math.min(180, Math.max(value, this._minAngle));
51
+ }
52
+ /**
53
+ * Apply the angle constraint to limit joint bending.
54
+ */ apply(joints) {
55
+ const index = this._jointIndex;
56
+ // For end effector (last joint), we constrain the angle between the last two bones
57
+ // For other joints, we constrain the angle at the joint itself
58
+ if (index === joints.length - 1) {
59
+ // End effector: constrain angle between (parent-1 -> parent) and (parent -> this)
60
+ if (index < 2) {
61
+ return; // Need at least 3 joints total
62
+ }
63
+ this._applyEndEffectorConstraint(joints, index);
64
+ } else {
65
+ // Regular joint: constrain angle between (parent -> this) and (this -> child)
66
+ if (index < 1) {
67
+ return; // Root joint cannot be constrained this way
68
+ }
69
+ this._applyRegularConstraint(joints, index);
70
+ }
71
+ }
72
+ /**
73
+ * Apply constraint to end effector joint.
74
+ */ _applyEndEffectorConstraint(joints, index) {
75
+ const grandParentJoint = joints[index - 2];
76
+ const parentJoint = joints[index - 1];
77
+ const currentJoint = joints[index];
78
+ // Calculate bone vectors
79
+ const incomingBone = Vector3.sub(parentJoint.position, grandParentJoint.position, new Vector3());
80
+ const outgoingBone = Vector3.sub(currentJoint.position, parentJoint.position, new Vector3());
81
+ const incomingLen = incomingBone.magnitude;
82
+ const outgoingLen = outgoingBone.magnitude;
83
+ if (incomingLen < 0.000001 || outgoingLen < 0.000001) {
84
+ return;
85
+ }
86
+ // Normalize
87
+ const incomingNorm = Vector3.scale(incomingBone, 1 / incomingLen, new Vector3());
88
+ const outgoingNorm = Vector3.scale(outgoingBone, 1 / outgoingLen, new Vector3());
89
+ // Calculate current angle
90
+ const dot = Vector3.dot(incomingNorm, outgoingNorm);
91
+ const currentAngleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * (180 / Math.PI);
92
+ // Check if constraint is violated
93
+ let targetAngleDeg = currentAngleDeg;
94
+ if (currentAngleDeg < this._minAngle) {
95
+ targetAngleDeg = this._minAngle;
96
+ } else if (currentAngleDeg > this._maxAngle) {
97
+ targetAngleDeg = this._maxAngle;
98
+ } else {
99
+ return;
100
+ }
101
+ // Apply the same constraint logic as regular joints
102
+ this._adjustJointAngle(incomingNorm, outgoingNorm, currentAngleDeg, targetAngleDeg, parentJoint, currentJoint);
103
+ }
104
+ /**
105
+ * Apply constraint to regular joint.
106
+ */ _applyRegularConstraint(joints, index) {
107
+ const parentJoint = joints[index - 1];
108
+ const currentJoint = joints[index];
109
+ const childJoint = joints[index + 1];
110
+ // Calculate bone vectors: incoming bone (parent->current) and outgoing bone (current->child)
111
+ const incomingBone = Vector3.sub(currentJoint.position, parentJoint.position, new Vector3());
112
+ const outgoingBone = Vector3.sub(childJoint.position, currentJoint.position, new Vector3());
113
+ const incomingLen = incomingBone.magnitude;
114
+ const outgoingLen = outgoingBone.magnitude;
115
+ if (incomingLen < 0.000001 || outgoingLen < 0.000001) {
116
+ return;
117
+ }
118
+ // Normalize
119
+ const incomingNorm = Vector3.scale(incomingBone, 1 / incomingLen, new Vector3());
120
+ const outgoingNorm = Vector3.scale(outgoingBone, 1 / outgoingLen, new Vector3());
121
+ // Calculate current angle between incoming and outgoing bones (interior angle)
122
+ const dot = Vector3.dot(incomingNorm, outgoingNorm);
123
+ const currentAngleDeg = Math.acos(Math.max(-1, Math.min(1, dot))) * (180 / Math.PI);
124
+ // Check if constraint is violated
125
+ let targetAngleDeg = currentAngleDeg;
126
+ if (currentAngleDeg < this._minAngle) {
127
+ targetAngleDeg = this._minAngle;
128
+ } else if (currentAngleDeg > this._maxAngle) {
129
+ targetAngleDeg = this._maxAngle;
130
+ } else {
131
+ return;
132
+ }
133
+ // Apply the constraint
134
+ this._adjustJointAngle(incomingNorm, outgoingNorm, currentAngleDeg, targetAngleDeg, currentJoint, childJoint);
135
+ }
136
+ /**
137
+ * Adjust joint angle by rotating the outgoing bone.
138
+ */ _adjustJointAngle(incomingNorm, outgoingNorm, currentAngleDeg, targetAngleDeg, pivotJoint, targetJoint) {
139
+ // Add a dead zone to prevent oscillation
140
+ const angleDifference = Math.abs(targetAngleDeg - currentAngleDeg);
141
+ const isHardConstraint = Math.abs(this._maxAngle - this._minAngle) < 0.01;
142
+ const deadZone = isHardConstraint ? 0.2 : 0.1;
143
+ if (angleDifference < deadZone) {
144
+ return; // Too close to target, avoid over-correction
145
+ }
146
+ // Calculate rotation axis (perpendicular to both bone vectors)
147
+ const axis = Vector3.cross(incomingNorm, outgoingNorm, new Vector3());
148
+ const axisLen = axis.magnitude;
149
+ // Use a smaller threshold to allow constraint to work at small angles
150
+ if (axisLen < 0.0001) {
151
+ return; // Vectors are too close to parallel or anti-parallel
152
+ }
153
+ axis.scaleBy(1 / axisLen);
154
+ // Calculate the angle we need to rotate
155
+ const targetAngleRad = targetAngleDeg * (Math.PI / 180);
156
+ const currentAngleRad = currentAngleDeg * (Math.PI / 180);
157
+ let deltaAngle = targetAngleRad - currentAngleRad;
158
+ // Apply full correction without damping to ensure constraints are enforced
159
+ // This is necessary because FABRIK will try to undo the constraint in the next iteration
160
+ // So we need strong constraint enforcement
161
+ // Rotate the outgoing bone vector around the axis by deltaAngle
162
+ const cos = Math.cos(deltaAngle);
163
+ const sin = Math.sin(deltaAngle);
164
+ const oneMinusCos = 1 - cos;
165
+ // Rodrigues' rotation formula
166
+ const rotated = new Vector3();
167
+ rotated.x = outgoingNorm.x * (cos + axis.x * axis.x * oneMinusCos) + outgoingNorm.y * (axis.x * axis.y * oneMinusCos - axis.z * sin) + outgoingNorm.z * (axis.x * axis.z * oneMinusCos + axis.y * sin);
168
+ rotated.y = outgoingNorm.x * (axis.y * axis.x * oneMinusCos + axis.z * sin) + outgoingNorm.y * (cos + axis.y * axis.y * oneMinusCos) + outgoingNorm.z * (axis.y * axis.z * oneMinusCos - axis.x * sin);
169
+ rotated.z = outgoingNorm.x * (axis.z * axis.x * oneMinusCos - axis.y * sin) + outgoingNorm.y * (axis.z * axis.y * oneMinusCos + axis.x * sin) + outgoingNorm.z * (cos + axis.z * axis.z * oneMinusCos);
170
+ // Update target joint position using the rotated bone vector
171
+ const boneLength = pivotJoint.boneLength;
172
+ rotated.scaleBy(boneLength);
173
+ Vector3.add(pivotJoint.position, rotated, targetJoint.position);
174
+ // Note: We do NOT recursively update subsequent joints here.
175
+ // Instead, we let FABRIK algorithm handle the propagation in subsequent iterations.
176
+ // This ensures that each joint's constraint is properly applied.
177
+ }
178
+ }
179
+
180
+ export { IKAngleConstraint };
181
+ //# sourceMappingURL=ik_angle_constraint.js.map