bones-browser.html 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Three.js Bones 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='./bones-browser.html' target='_blank'>Open in New Window</a>
  36. <script type="module">
  37. import {
  38. Bone,
  39. Color,
  40. CylinderGeometry,
  41. DirectionalLight,
  42. DoubleSide,
  43. Float32BufferAttribute,
  44. MeshPhongMaterial,
  45. PerspectiveCamera,
  46. Scene,
  47. SkinnedMesh,
  48. Skeleton,
  49. SkeletonHelper,
  50. Vector3,
  51. Uint16BufferAttribute,
  52. WebGLRenderer
  53. } from 'three';
  54. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  55. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  56. let gui, scene, camera, renderer, orbit, lights, mesh, bones, skeletonHelper;
  57. const state = {
  58. animateBones: false
  59. };
  60. function initScene() {
  61. gui = new GUI();
  62. scene = new Scene();
  63. scene.background = new Color( 0x444444 );
  64. camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 200 );
  65. camera.position.z = 30;
  66. camera.position.y = 30;
  67. renderer = new WebGLRenderer( { antialias: true } );
  68. renderer.setPixelRatio( window.devicePixelRatio );
  69. renderer.setSize( window.innerWidth, window.innerHeight );
  70. document.body.appendChild( renderer.domElement );
  71. orbit = new OrbitControls( camera, renderer.domElement );
  72. orbit.enableZoom = false;
  73. lights = [];
  74. lights[ 0 ] = new DirectionalLight( 0xffffff, 3 );
  75. lights[ 1 ] = new DirectionalLight( 0xffffff, 3 );
  76. lights[ 2 ] = new DirectionalLight( 0xffffff, 3 );
  77. lights[ 0 ].position.set( 0, 200, 0 );
  78. lights[ 1 ].position.set( 100, 200, 100 );
  79. lights[ 2 ].position.set( - 100, - 200, - 100 );
  80. scene.add( lights[ 0 ] );
  81. scene.add( lights[ 1 ] );
  82. scene.add( lights[ 2 ] );
  83. window.addEventListener( 'resize', function () {
  84. camera.aspect = window.innerWidth / window.innerHeight;
  85. camera.updateProjectionMatrix();
  86. renderer.setSize( window.innerWidth, window.innerHeight );
  87. }, false );
  88. initBones();
  89. setupDatGui();
  90. }
  91. function createGeometry( sizing ) {
  92. const geometry = new CylinderGeometry(
  93. 5, // radiusTop
  94. 5, // radiusBottom
  95. sizing.height, // height
  96. 8, // radiusSegments
  97. sizing.segmentCount * 3, // heightSegments
  98. true // openEnded
  99. );
  100. const position = geometry.attributes.position;
  101. const vertex = new Vector3();
  102. const skinIndices = [];
  103. const skinWeights = [];
  104. for ( let i = 0; i < position.count; i ++ ) {
  105. vertex.fromBufferAttribute( position, i );
  106. const y = ( vertex.y + sizing.halfHeight );
  107. const skinIndex = Math.floor( y / sizing.segmentHeight );
  108. const skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight;
  109. skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
  110. skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );
  111. }
  112. geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) );
  113. geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) );
  114. return geometry;
  115. }
  116. function createBones( sizing ) {
  117. bones = [];
  118. let prevBone = new Bone();
  119. bones.push( prevBone );
  120. prevBone.position.y = - sizing.halfHeight;
  121. for ( let i = 0; i < sizing.segmentCount; i ++ ) {
  122. const bone = new Bone();
  123. bone.position.y = sizing.segmentHeight;
  124. bones.push( bone );
  125. prevBone.add( bone );
  126. prevBone = bone;
  127. }
  128. return bones;
  129. }
  130. function createMesh( geometry, bones ) {
  131. const material = new MeshPhongMaterial( {
  132. color: 0x156289,
  133. emissive: 0x072534,
  134. side: DoubleSide,
  135. flatShading: true
  136. } );
  137. const mesh = new SkinnedMesh( geometry, material );
  138. const skeleton = new Skeleton( bones );
  139. mesh.add( bones[ 0 ] );
  140. mesh.bind( skeleton );
  141. skeletonHelper = new SkeletonHelper( mesh );
  142. skeletonHelper.material.linewidth = 2;
  143. scene.add( skeletonHelper );
  144. return mesh;
  145. }
  146. function setupDatGui() {
  147. let folder = gui.addFolder( 'General Options' );
  148. folder.add( state, 'animateBones' );
  149. folder.controllers[ 0 ].name( 'Animate Bones' );
  150. folder.add( mesh, 'pose' );
  151. folder.controllers[ 1 ].name( '.pose()' );
  152. const bones = mesh.skeleton.bones;
  153. for ( let i = 0; i < bones.length; i ++ ) {
  154. const bone = bones[ i ];
  155. folder = gui.addFolder( 'Bone ' + i );
  156. folder.add( bone.position, 'x', - 10 + bone.position.x, 10 + bone.position.x );
  157. folder.add( bone.position, 'y', - 10 + bone.position.y, 10 + bone.position.y );
  158. folder.add( bone.position, 'z', - 10 + bone.position.z, 10 + bone.position.z );
  159. folder.add( bone.rotation, 'x', - Math.PI * 0.5, Math.PI * 0.5 );
  160. folder.add( bone.rotation, 'y', - Math.PI * 0.5, Math.PI * 0.5 );
  161. folder.add( bone.rotation, 'z', - Math.PI * 0.5, Math.PI * 0.5 );
  162. folder.add( bone.scale, 'x', 0, 2 );
  163. folder.add( bone.scale, 'y', 0, 2 );
  164. folder.add( bone.scale, 'z', 0, 2 );
  165. folder.controllers[ 0 ].name( 'position.x' );
  166. folder.controllers[ 1 ].name( 'position.y' );
  167. folder.controllers[ 2 ].name( 'position.z' );
  168. folder.controllers[ 3 ].name( 'rotation.x' );
  169. folder.controllers[ 4 ].name( 'rotation.y' );
  170. folder.controllers[ 5 ].name( 'rotation.z' );
  171. folder.controllers[ 6 ].name( 'scale.x' );
  172. folder.controllers[ 7 ].name( 'scale.y' );
  173. folder.controllers[ 8 ].name( 'scale.z' );
  174. }
  175. }
  176. function initBones() {
  177. const segmentHeight = 8;
  178. const segmentCount = 4;
  179. const height = segmentHeight * segmentCount;
  180. const halfHeight = height * 0.5;
  181. const sizing = {
  182. segmentHeight: segmentHeight,
  183. segmentCount: segmentCount,
  184. height: height,
  185. halfHeight: halfHeight
  186. };
  187. const geometry = createGeometry( sizing );
  188. const bones = createBones( sizing );
  189. mesh = createMesh( geometry, bones );
  190. mesh.scale.multiplyScalar( 1 );
  191. scene.add( mesh );
  192. }
  193. function render() {
  194. requestAnimationFrame( render );
  195. const time = Date.now() * 0.001;
  196. //Wiggle the bones
  197. if ( state.animateBones ) {
  198. for ( let i = 0; i < mesh.skeleton.bones.length; i ++ ) {
  199. mesh.skeleton.bones[ i ].rotation.z = Math.sin( time ) * 2 / mesh.skeleton.bones.length;
  200. }
  201. }
  202. renderer.render( scene, camera );
  203. }
  204. initScene();
  205. render();
  206. </script>
  207. </body>
  208. </html>