Преглед на файлове

doc: CCDIKSolver with generic SkinnedMesh (#23449)

* doc: CCDIKSolver with generic SkinnedMesh

* whitespaces fix

* tabs indentation

* tweaks

* Update CCDIKSolver.html

* typo

* lint fixes

* zh version

* oops
Antoine BERNIER преди 3 години
родител
ревизия
d4ac8418d2
променени са 3 файла, в които са добавени 438 реда и са изтрити 26 реда
  1. 75 12
      docs/examples/en/animations/CCDIKSolver.html
  2. 77 14
      docs/examples/zh/animations/CCDIKSolver.html
  3. 286 0
      docs/scenes/ccdiksolver-browser.html

+ 75 - 12
docs/examples/en/animations/CCDIKSolver.html

@@ -11,31 +11,94 @@
 
 		<p class="desc"> A solver for IK with <a href="https://sites.google.com/site/auraliusproject/ccd-algorithm"><em>CCD Algorithm</em></a>. <br /><br />
 		[name] solves Inverse Kinematics Problem with CCD Algorithm.
-		[name] is designed to work with [page:SkinnedMesh] loaded by [page:MMDLoader] but also can be used for generic [page:SkinnedMesh].
+		[name] is designed to work with [page:SkinnedMesh] but also can be used with [page:MMDLoader] or [page:GLTFLoader] skeleton.
 		</p>
 
+		<iframe id="scene" src="scenes/ccdiksolver-browser.html"></iframe>
+
 		<h2>Code Example</h2>
 
 		<code>
 		let ikSolver;
 
-		// Load MMD resources and instantiate CCDIKSolver
-		new MMDLoader().load(
-			'models/mmd/miku.pmd',
-			function ( mesh ) {
+		//
+		// Bones hierarchy:
+		//
+		//   root
+		//     ├── bone0
+		//     │    └── bone1
+		//     │          └── bone2
+		//     │                └── bone3
+		//     └── target
+		//
+		// Positioned as follow on the cylinder:
+		//
+		//        o      <- target      (y =  20)
+		//        
+		//   +----o----+ <- bone3       (y =  12)
+		//   |         |
+		//   |    o    | <- bone2       (y =   4)
+		//   |         |
+		//   |    o    | <- bone1       (y =  -4)
+		//   |         |
+		//   +----oo---+ <- root, bone0 (y = -12)
+		//
+
+		let bones = []
+
+		// "root"
+		let rootBone = new Bone();
+		rootBone.position.y = -12;
+		bones.push( rootBone );
+
+		// "bone0"
+		let prevBone = new Bone();
+		prevBone.position.y = 0;
+		rootBone.add( prevBone );
+		bones.push( prevBone );
+
+		// "bone1", "bone2", "bone3"
+		for ( let i = 1; i <= 3; i ++ ) {
+			const bone = new Bone();
+			bone.position.y = 8;
+			bones.push( bone );
+			
+			prevBone.add( bone );
+			prevBone = bone;
+		}
+
+		// "target"
+		const targetBone = new Bone();
+		targetBone.position.y = 24 + 8
+		rootBone.add( targetBone );
+		bones.push( targetBone );
+
+		//
+		// skinned mesh
+		//
 
-				ikSolver = new CCDIKSolver( mesh, mesh.geometry.iks );
-				scene.add( mesh );
+		const mesh = new SkinnedMesh( geometry,	material );
+		const skeleton = new Skeleton( bones );
 
+		mesh.add( bones[ 0 ] ); // "root" bone
+		mesh.bind( skeleton );
+
+		//
+		// ikSolver
+		//
+
+		const iks = [
+			{
+				target: 5, // "target"
+				effector: 4, // "bone3"
+				links: [ { index: 3 }, { index: 2 }, { index: 1 } ] // "bone2", "bone1", "bone0"
 			}
-		);
+		];
+		ikSolver = new CCDIKSolver( mesh, iks );
 
 		function render() {
-
-			animate(); // update bones
-			if ( ikSolver !== undefined ) ikSolver.update();
+			ikSolver?.update();
 			renderer.render( scene, camera );
-
 		}
 		</code>
 

+ 77 - 14
docs/examples/zh/animations/CCDIKSolver.html

@@ -11,31 +11,94 @@
 
 		<p class="desc"> A solver for IK with <a href="https://sites.google.com/site/auraliusproject/ccd-algorithm"><em>CCD Algorithm</em></a>. <br /><br />
 		[name] solves Inverse Kinematics Problem with CCD Algorithm.
-		[name] is designed to work with [page:SkinnedMesh] loaded by [page:MMDLoader] but also can be used for generic [page:SkinnedMesh].
+		[name] is designed to work with [page:SkinnedMesh] but also can be used with [page:MMDLoader] or [page:GLTFLoader] skeleton.
 		</p>
 
+		<iframe id="scene" src="scenes/ccdiksolver-browser.html"></iframe>
+
 		<h2>代码示例</h2>
 
 		<code>
-		const ikSolver;
+		let ikSolver;
+
+		//
+		// Bones hierarchy:
+		//
+		//   root
+		//     ├── bone0
+		//     │    └── bone1
+		//     │          └── bone2
+		//     │                └── bone3
+		//     └── target
+		//
+		// Positioned as follow on the cylinder:
+		//
+		//        o      <- target      (y =  20)
+		//        
+		//   +----o----+ <- bone3       (y =  12)
+		//   |         |
+		//   |    o    | <- bone2       (y =   4)
+		//   |         |
+		//   |    o    | <- bone1       (y =  -4)
+		//   |         |
+		//   +----oo---+ <- root, bone0 (y = -12)
+		//
+
+		let bones = []
+
+		// "root"
+		let rootBone = new Bone();
+		rootBone.position.y = -12;
+		bones.push( rootBone );
+
+		// "bone0"
+		let prevBone = new Bone();
+		prevBone.position.y = 0;
+		rootBone.add( prevBone );
+		bones.push( prevBone );
+
+		// "bone1", "bone2", "bone3"
+		for ( let i = 1; i <= 3; i ++ ) {
+			const bone = new Bone();
+			bone.position.y = 8;
+			bones.push( bone );
+			
+			prevBone.add( bone );
+			prevBone = bone;
+		}
+
+		// "target"
+		const targetBone = new Bone();
+		targetBone.position.y = 24 + 8
+		rootBone.add( targetBone );
+		bones.push( targetBone );
+
+		//
+		// skinned mesh
+		//
 
-		// Load MMD resources and instantiate CCDIKSolver
-		new MMDLoader().load(
-			'models/mmd/miku.pmd',
-			function ( mesh ) {
+		const mesh = new SkinnedMesh( geometry,	material );
+		const skeleton = new Skeleton( bones );
 
-				ikSolver = new CCDIKSolver( mesh, mesh.geometry.iks );
-				scene.add( mesh );
+		mesh.add( bones[ 0 ] ); // "root" bone
+		mesh.bind( skeleton );
 
+		//
+		// ikSolver
+		//
+
+		const iks = [
+			{
+				target: 5, // "target"
+				effector: 4, // "bone3"
+				links: [ { index: 3 }, { index: 2 }, { index: 1 } ] // "bone2", "bone1", "bone0"
 			}
-		);
+		];
+		ikSolver = new CCDIKSolver( mesh, iks );
 
 		function render() {
-
-			animate(); // update bones
-			if ( ikSolver !== undefined ) ikSolver.update();
+			ikSolver?.update();
 			renderer.render( scene, camera );
-
 		}
 		</code>
 
@@ -92,7 +155,7 @@
 
 		<h3>[method:this update]()</h3>
 		<p>
-		Update bones quaternion by solving CCD algorithm.
+		Update IK bones quaternion by solving CCD algorithm.
 		</p>
 
 		<h3>[method:this updateOne]( [param:Object ikParam] )</h3>

+ 286 - 0
docs/scenes/ccdiksolver-browser.html

@@ -0,0 +1,286 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>Three.js CCDIKSolver Browser</title>
+		<link rel="shortcut icon" href="../../files/favicon.ico" />
+		<link rel="stylesheet" type="text/css" href="../../files/main.css">
+		<style>
+			canvas {
+				display: block;
+				width: 100%;
+				height: 100%;
+			}
+
+			#newWindow {
+				display: block;
+				position: absolute;
+				bottom: 0.3em;
+				left: 0.5em;
+				color: #fff;
+			}
+		</style>
+	</head>
+	<body>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../../build/three.module.js"
+				}
+			}
+		</script>
+
+		<a id='newWindow' href='./ccdiksolver-browser.html' target='_blank'>Open in New Window</a>
+
+		<script type="module">
+			//
+			// Forked from /docs/api/en/objects/SkinnedMesh example
+			//
+
+			import {
+				Bone,
+				Color,
+				CylinderGeometry,
+				DoubleSide,
+				Float32BufferAttribute,
+				MeshPhongMaterial,
+				PerspectiveCamera,
+				PointLight,
+				Scene,
+				SkinnedMesh,
+				Skeleton,
+				SkeletonHelper,
+				Vector3,
+				Uint16BufferAttribute,
+				WebGLRenderer
+			} from 'three';
+			import { CCDIKSolver, CCDIKHelper } from "../../examples/jsm/animation/CCDIKSolver.js";
+
+			import { GUI } from '../../examples/jsm/libs/lil-gui.module.min.js';
+			import { OrbitControls } from '../../examples/jsm/controls/OrbitControls.js';
+
+			let gui, scene, camera, renderer, orbit, mesh, bones, skeletonHelper, ikSolver;
+
+			const state = {
+				ikSolverAutoUpdate: true
+			};
+
+			function initScene() {
+
+				gui = new GUI();
+
+				scene = new Scene();
+				scene.background = new Color( 0x444444 );
+
+				camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 200 );
+				camera.position.z = 30;
+				camera.position.y = 30;
+
+				renderer = new WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				orbit = new OrbitControls( camera, renderer.domElement );
+				orbit.enableZoom = false;
+
+				window.addEventListener( 'resize', function () {
+
+					camera.aspect = window.innerWidth / window.innerHeight;
+					camera.updateProjectionMatrix();
+
+					renderer.setSize( window.innerWidth, window.innerHeight );
+
+				}, false );
+
+				initBones();
+				setupDatGui();
+
+			}
+
+			function createGeometry( sizing ) {
+
+				const geometry = new CylinderGeometry(
+					5, // radiusTop
+					5, // radiusBottom
+					sizing.height, // height
+					8, // radiusSegments
+					sizing.segmentCount * 1, // heightSegments
+					true // openEnded
+				);
+
+				const position = geometry.attributes.position;
+
+				const vertex = new Vector3();
+
+				const skinIndices = [];
+				const skinWeights = [];
+
+				for ( let i = 0; i < position.count; i ++ ) {
+
+					vertex.fromBufferAttribute( position, i );
+
+					const y = ( vertex.y + sizing.halfHeight );
+
+					const skinIndex = Math.floor( y / sizing.segmentHeight );
+					const skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight;
+
+					skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
+					skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );
+
+				}
+
+				geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) );
+				geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) );
+
+				return geometry;
+
+			}
+
+			function createBones( sizing ) {
+
+				bones = [];
+
+				// "root bone"
+				let rootBone = new Bone();
+				rootBone.name = "root";
+				rootBone.position.y = -sizing.halfHeight;
+				bones.push( rootBone );
+
+				//
+				// "bone0", "bone1", "bone2", "bone3"
+				//
+
+				// "bone0"
+				let prevBone = new Bone();
+				prevBone.position.y = 0;
+				rootBone.add(prevBone);
+				bones.push( prevBone );
+
+				// "bone1", "bone2", "bone3"
+				for ( let i = 1; i <= sizing.segmentCount; i ++ ) {
+
+					const bone = new Bone();
+					bone.position.y = sizing.segmentHeight;
+					bones.push( bone );
+					bone.name = `bone${i}`;
+					prevBone.add( bone );
+					prevBone = bone;
+
+				}
+
+				// "target"
+				const targetBone = new Bone();
+				targetBone.name = "target";
+				targetBone.position.y = sizing.height + sizing.segmentHeight; // relative to parent: rootBone
+				rootBone.add( targetBone );
+				bones.push( targetBone );
+
+				return bones;
+
+			}
+
+			function createMesh( geometry, bones ) {
+
+				const material = new MeshPhongMaterial( {
+					color: 0x156289,
+					emissive: 0x072534,
+					side: DoubleSide,
+					flatShading: true,
+					wireframe: true
+				} );
+
+				const mesh = new SkinnedMesh( geometry,	material );
+				const skeleton = new Skeleton( bones );
+
+				mesh.add( bones[ 0 ] );
+
+				mesh.bind( skeleton );
+
+				skeletonHelper = new SkeletonHelper( mesh );
+				skeletonHelper.material.linewidth = 2;
+				scene.add( skeletonHelper );
+
+				return mesh;
+
+			}
+
+			function setupDatGui() {
+
+				gui.add( mesh, 'pose' ).name( 'mesh.pose()' );
+
+				mesh.skeleton.bones
+					.filter((bone) => bone.name === "target")
+					.forEach(function (bone) {
+						const folder = gui.addFolder( bone.name );
+
+						const delta = 20;
+						folder.add( bone.position, 'x', - delta + bone.position.x, delta + bone.position.x );
+						folder.add( bone.position, 'y', - bone.position.y, bone.position.y );
+						folder.add( bone.position, 'z', - delta + bone.position.z, delta + bone.position.z );
+					});
+				
+				gui.add( ikSolver, 'update' ).name( 'ikSolver.update()' );
+				gui.add( state, 'ikSolverAutoUpdate' )
+
+			}
+
+			function initBones() {
+
+				const segmentHeight = 8;
+				const segmentCount = 3;
+				const height = segmentHeight * segmentCount;
+				const halfHeight = height * 0.5;
+
+				const sizing = {
+					segmentHeight,
+					segmentCount,
+					height,
+					halfHeight
+				};
+
+				const geometry = createGeometry( sizing );
+				const bones = createBones( sizing );
+				mesh = createMesh( geometry, bones );
+
+				scene.add( mesh );
+
+				//
+				// ikSolver
+				//
+
+				const iks = [
+					{
+						target: 5,
+						effector: 4,
+						links: [{ index: 3 }, { index: 2 }, { index: 1 }]
+					}
+				];
+				ikSolver = new CCDIKSolver( mesh, iks );
+				scene.add( new CCDIKHelper( mesh, iks ) );
+
+			}
+
+			function render() {
+
+				requestAnimationFrame( render );
+
+				if ( state.ikSolverAutoUpdate ) {
+					ikSolver?.update();
+				}
+
+				renderer.render( scene, camera );
+
+			}
+
+			initScene();
+			render();
+
+		</script>
+	</body>
+</html>