ccdiksolver-browser.html 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Three.js CCDIKSolver Browser</title>
  6. <link rel="shortcut icon" href="../../files/favicon.ico" />
  7. <link rel="stylesheet" type="text/css" href="../../files/main.css">
  8. <style>
  9. canvas {
  10. display: block;
  11. width: 100%;
  12. height: 100%;
  13. }
  14. #newWindow {
  15. display: block;
  16. position: absolute;
  17. bottom: 0.3em;
  18. left: 0.5em;
  19. color: #fff;
  20. }
  21. </style>
  22. </head>
  23. <body>
  24. <!-- Import maps polyfill -->
  25. <!-- Remove this when import maps will be widely supported -->
  26. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  27. <script type="importmap">
  28. {
  29. "imports": {
  30. "three": "../../build/three.module.js",
  31. "three/addons/": "../../examples/jsm/"
  32. }
  33. }
  34. </script>
  35. <a id='newWindow' href='./ccdiksolver-browser.html' target='_blank'>Open in New Window</a>
  36. <script type="module">
  37. //
  38. // Forked from /docs/api/en/objects/SkinnedMesh example
  39. //
  40. import {
  41. Bone,
  42. Color,
  43. ColorManagement,
  44. CylinderGeometry,
  45. DoubleSide,
  46. Float32BufferAttribute,
  47. MeshPhongMaterial,
  48. PerspectiveCamera,
  49. Scene,
  50. SkinnedMesh,
  51. Skeleton,
  52. SkeletonHelper,
  53. Vector3,
  54. Uint16BufferAttribute,
  55. WebGLRenderer
  56. } from 'three';
  57. import { CCDIKSolver, CCDIKHelper } from 'three/addons/animation/CCDIKSolver.js';
  58. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  59. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  60. ColorManagement.enabled = true;
  61. let gui, scene, camera, renderer, orbit, mesh, bones, skeletonHelper, ikSolver;
  62. const state = {
  63. ikSolverAutoUpdate: true
  64. };
  65. function initScene() {
  66. gui = new GUI();
  67. scene = new Scene();
  68. scene.background = new Color( 0x444444 );
  69. camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 200 );
  70. camera.position.z = 30;
  71. camera.position.y = 30;
  72. renderer = new WebGLRenderer( { antialias: true } );
  73. renderer.setPixelRatio( window.devicePixelRatio );
  74. renderer.setSize( window.innerWidth, window.innerHeight );
  75. document.body.appendChild( renderer.domElement );
  76. orbit = new OrbitControls( camera, renderer.domElement );
  77. orbit.enableZoom = false;
  78. window.addEventListener( 'resize', function () {
  79. camera.aspect = window.innerWidth / window.innerHeight;
  80. camera.updateProjectionMatrix();
  81. renderer.setSize( window.innerWidth, window.innerHeight );
  82. }, false );
  83. initBones();
  84. setupDatGui();
  85. }
  86. function createGeometry( sizing ) {
  87. const geometry = new CylinderGeometry(
  88. 5, // radiusTop
  89. 5, // radiusBottom
  90. sizing.height, // height
  91. 8, // radiusSegments
  92. sizing.segmentCount * 1, // heightSegments
  93. true // openEnded
  94. );
  95. const position = geometry.attributes.position;
  96. const vertex = new Vector3();
  97. const skinIndices = [];
  98. const skinWeights = [];
  99. for ( let i = 0; i < position.count; i ++ ) {
  100. vertex.fromBufferAttribute( position, i );
  101. const y = ( vertex.y + sizing.halfHeight );
  102. const skinIndex = Math.floor( y / sizing.segmentHeight );
  103. const skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight;
  104. skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
  105. skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );
  106. }
  107. geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) );
  108. geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) );
  109. return geometry;
  110. }
  111. function createBones( sizing ) {
  112. bones = [];
  113. // "root bone"
  114. const rootBone = new Bone();
  115. rootBone.name = 'root';
  116. rootBone.position.y = - sizing.halfHeight;
  117. bones.push( rootBone );
  118. //
  119. // "bone0", "bone1", "bone2", "bone3"
  120. //
  121. // "bone0"
  122. let prevBone = new Bone();
  123. prevBone.position.y = 0;
  124. rootBone.add( prevBone );
  125. bones.push( prevBone );
  126. // "bone1", "bone2", "bone3"
  127. for ( let i = 1; i <= sizing.segmentCount; i ++ ) {
  128. const bone = new Bone();
  129. bone.position.y = sizing.segmentHeight;
  130. bones.push( bone );
  131. bone.name = `bone${i}`;
  132. prevBone.add( bone );
  133. prevBone = bone;
  134. }
  135. // "target"
  136. const targetBone = new Bone();
  137. targetBone.name = 'target';
  138. targetBone.position.y = sizing.height + sizing.segmentHeight; // relative to parent: rootBone
  139. rootBone.add( targetBone );
  140. bones.push( targetBone );
  141. return bones;
  142. }
  143. function createMesh( geometry, bones ) {
  144. const material = new MeshPhongMaterial( {
  145. color: 0x156289,
  146. emissive: 0x072534,
  147. side: DoubleSide,
  148. flatShading: true,
  149. wireframe: true
  150. } );
  151. const mesh = new SkinnedMesh( geometry, material );
  152. const skeleton = new Skeleton( bones );
  153. mesh.add( bones[ 0 ] );
  154. mesh.bind( skeleton );
  155. skeletonHelper = new SkeletonHelper( mesh );
  156. skeletonHelper.material.linewidth = 2;
  157. scene.add( skeletonHelper );
  158. return mesh;
  159. }
  160. function setupDatGui() {
  161. gui.add( mesh, 'pose' ).name( 'mesh.pose()' );
  162. mesh.skeleton.bones
  163. .filter( ( bone ) => bone.name === 'target' )
  164. .forEach( function ( bone ) {
  165. const folder = gui.addFolder( bone.name );
  166. const delta = 20;
  167. folder.add( bone.position, 'x', - delta + bone.position.x, delta + bone.position.x );
  168. folder.add( bone.position, 'y', - bone.position.y, bone.position.y );
  169. folder.add( bone.position, 'z', - delta + bone.position.z, delta + bone.position.z );
  170. } );
  171. gui.add( ikSolver, 'update' ).name( 'ikSolver.update()' );
  172. gui.add( state, 'ikSolverAutoUpdate' );
  173. }
  174. function initBones() {
  175. const segmentHeight = 8;
  176. const segmentCount = 3;
  177. const height = segmentHeight * segmentCount;
  178. const halfHeight = height * 0.5;
  179. const sizing = {
  180. segmentHeight,
  181. segmentCount,
  182. height,
  183. halfHeight
  184. };
  185. const geometry = createGeometry( sizing );
  186. const bones = createBones( sizing );
  187. mesh = createMesh( geometry, bones );
  188. scene.add( mesh );
  189. //
  190. // ikSolver
  191. //
  192. const iks = [
  193. {
  194. target: 5,
  195. effector: 4,
  196. links: [ { index: 3 }, { index: 2 }, { index: 1 } ]
  197. }
  198. ];
  199. ikSolver = new CCDIKSolver( mesh, iks );
  200. scene.add( new CCDIKHelper( mesh, iks ) );
  201. }
  202. function render() {
  203. requestAnimationFrame( render );
  204. if ( state.ikSolverAutoUpdate ) {
  205. ikSolver?.update();
  206. }
  207. renderer.render( scene, camera );
  208. }
  209. initScene();
  210. render();
  211. </script>
  212. </body>
  213. </html>