فهرست منبع

New `animation / skinning / ik` example (#24652)

* kira example

* kira example

* adding link to the example into CCDIKSolver documentation

* removing old file

* updating final url

* lil-gui 0.16 + Tween

* fix eslint issues

* glb

* no dead comments

* remaining dead comments

* adding to the screenshot's exceptionList

* chore: textures from glb

* feat: no fancy

* re-enabling screenshot

* tabs

* followSphere off by default

* Update webgl_animation_skinning_ik.html

Clean up.

* fix(CCDIKSolver warning): links order count + removing 1 useless link

* chore: better camera angle

* fix: eslint --fix

* transformControls showX false

* new glb version

* updating screenshot

* fixing link to the experiment

* Update CCDIKSolver.js

* Update webgl_animation_skinning_ik.html

Co-authored-by: Michael Herzog <[email protected]>
Antoine BERNIER 2 سال پیش
والد
کامیت
a53977c4e7

+ 1 - 0
docs/examples/en/animations/CCDIKSolver.html

@@ -105,6 +105,7 @@
 		<h2>Examples</h2>
 
 		<p>
+			[example:webgl_animation_skinning_ik]<br />
 			[example:webgl_loader_mmd]<br />
 			[example:webgl_loader_mmd_pose]<br />
 			[example:webgl_loader_mmd_audio]

+ 1 - 0
examples/files.json

@@ -3,6 +3,7 @@
 		"webgl_animation_keyframes",
 		"webgl_animation_skinning_blending",
 		"webgl_animation_skinning_additive_blending",
+		"webgl_animation_skinning_ik",
 		"webgl_animation_skinning_morph",
 		"webgl_animation_multiple",
 		"webgl_camera",

+ 2 - 2
examples/jsm/animation/CCDIKSolver.js

@@ -283,7 +283,7 @@ function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv )
  */
 class CCDIKHelper extends Object3D {
 
-	constructor( mesh, iks = [] ) {
+	constructor( mesh, iks = [], sphereSize = 0.25 ) {
 
 		super();
 
@@ -293,7 +293,7 @@ class CCDIKHelper extends Object3D {
 		this.matrix.copy( mesh.matrixWorld );
 		this.matrixAutoUpdate = false;
 
-		this.sphereGeometry = new SphereGeometry( 0.25, 16, 8 );
+		this.sphereGeometry = new SphereGeometry( sphereSize, 16, 8 );
 
 		this.targetSphereMaterial = new MeshBasicMaterial( {
 			color: new Color( 0xff8888 ),

BIN
examples/models/gltf/kira.glb


BIN
examples/screenshots/webgl_animation_skinning_ik.jpg


+ 220 - 0
examples/webgl_animation_skinning_ik.html

@@ -0,0 +1,220 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - animation - skinning - ik</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<meta name="author" content="Antoine BERNIER (abernier)" />
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+		body {color:white;}
+		#info a {
+			color:inherit;
+		}
+		</style>
+	</head>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - <a href="https://assetstore.unity.com/packages/3d/characters/humanoids/humans/kira-lowpoly-character-100303" target="_blank" rel="noopener">Kira</a>'s "<a href="https://en.wikipedia.org/wiki/Hand_with_Reflecting_Sphere" target="_blank" rel="noopener">hand with Reflecting Sphere</a>"<br />
+			(see <a href="https://abernier.name/three.js/examples/webgl_esher.html" target="_blank" rel="noopener">full-experiment</a>)
+		</div>
+
+		<!-- 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",
+				"three/addons/": "./jsm/"
+			}
+		}
+		</script>
+
+		<script type="module">
+		import * as THREE from 'three';
+
+		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+		import { TransformControls } from 'three/addons/controls/TransformControls.js';
+		import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+		import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+		import { CCDIKSolver, CCDIKHelper } from './jsm/animation/CCDIKSolver.js';
+		import Stats from 'three/addons/libs/stats.module.js';
+		import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+		let scene, camera, renderer, orbitControls, transformControls;
+		let mirrorSphereCamera;
+
+		const OOI = {};
+		let IKSolver;
+
+		let stats, gui, conf;
+		const v0 = new THREE.Vector3();
+
+		init().then( animate );
+
+		async function init() {
+
+			conf = {
+				followSphere: false,
+				turnHead: true,
+				ik_solver: true,
+			};
+
+			scene = new THREE.Scene();
+			scene.fog = new THREE.FogExp2( 0xffffff, .17 );
+			scene.background = new THREE.Color( 0xdddddd );
+
+			camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.001, 5000 );
+			camera.position.set( 0.9728517749133652, 1.1044765132727201, 0.7316689528482836 );
+			camera.lookAt( scene.position );
+
+			const ambientLight = new THREE.AmbientLight( 0xffffff, 8 ); // soft white light
+			scene.add( ambientLight );
+
+			renderer = new THREE.WebGLRenderer( { antialias: true, logarithmicDepthBuffer: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.outputEncoding = THREE.sRGBEncoding;
+			renderer.physicallyCorrectLights = true;
+			document.body.appendChild( renderer.domElement );
+
+			stats = new Stats();
+			document.body.appendChild( stats.dom );
+
+			orbitControls = new OrbitControls( camera, renderer.domElement );
+			orbitControls.minDistance = .2;
+			orbitControls.maxDistance = 1.5;
+			orbitControls.enableDamping = true;
+
+			const dracoLoader = new DRACOLoader();
+			dracoLoader.setDecoderPath( 'js/libs/draco/' );
+			const gltfLoader = new GLTFLoader();
+			gltfLoader.setDRACOLoader( dracoLoader );
+		
+			const gltf = await gltfLoader.loadAsync( 'models/gltf/kira.glb' );
+			gltf.scene.traverse( n => {
+
+				if ( n.name === 'head' ) OOI.head = n;
+				if ( n.name === 'lowerarm_l' ) OOI.lowerarm_l = n;
+				if ( n.name === 'Upperarm_l' ) OOI.Upperarm_l = n;
+				if ( n.name === 'hand_l' ) OOI.hand_l = n;
+				if ( n.name === 'target_hand_l' ) OOI.target_hand_l = n;
+
+				if ( n.name === 'boule' ) OOI.sphere = n;
+				if ( n.name === 'Kira_Shirt' ) OOI.kira = n;
+
+				if ( n.isMesh ) n.frustumCulled = false;
+
+			} );
+			scene.add( gltf.scene );
+
+			orbitControls.target.copy( OOI.sphere.position ); // orbit controls lookAt the sphere
+			OOI.hand_l.attach( OOI.sphere );
+
+			// mirror sphere cube-camera
+			const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 1024 );
+			mirrorSphereCamera = new THREE.CubeCamera( 0.05, 50, cubeRenderTarget );
+			scene.add( mirrorSphereCamera );
+			const mirrorSphereMaterial = new THREE.MeshBasicMaterial( { envMap: cubeRenderTarget.texture } );
+			OOI.sphere.material = mirrorSphereMaterial;
+
+			transformControls = new TransformControls( camera, renderer.domElement );
+			transformControls.size = .75;
+			transformControls.showX = false;
+			transformControls.space = 'world';
+			transformControls.attach( OOI.target_hand_l );
+			scene.add( transformControls );
+			// disable orbitControls while using transformControls
+			transformControls.addEventListener( 'mouseDown', () => orbitControls.enabled = false );
+			transformControls.addEventListener( 'mouseUp', () => orbitControls.enabled = true );
+
+			OOI.kira.add( OOI.kira.skeleton.bones[ 0 ] );
+			const iks = [
+				{
+					target: 25, // "target_hand_l"
+					effector: 9, // "hand_l"
+					links: [
+						{
+							index: 8, // "lowerarm_l"
+							rotationMin: new THREE.Vector3( 1.2, - 1.8, - .4 ),
+							rotationMax: new THREE.Vector3( 1.7, - 1.1, .3 )
+						},
+						{
+							index: 7, // "Upperarm_l"
+							rotationMin: new THREE.Vector3( 0.1, - 0.7, - 1.8 ),
+							rotationMax: new THREE.Vector3( 1.1, 0, - 1.4 )
+						},
+					],
+				}
+			];
+			IKSolver = new CCDIKSolver( OOI.kira, iks );
+			const ccdikhelper = new CCDIKHelper( OOI.kira, iks, 0.01 );
+			scene.add( ccdikhelper );
+
+			gui = new GUI();
+			gui.add( conf, 'followSphere' ).name( 'follow sphere' );
+			gui.add( conf, 'turnHead' ).name( 'turn head' );
+			gui.add( conf, 'ik_solver' ).name( 'IK auto update' );
+			gui.add( IKSolver, 'update' ).name( 'IK manual update()' );
+			gui.open();
+
+			window.addEventListener( 'resize', onWindowResize, false );
+
+		}
+
+		function animate( ) {
+
+			if ( OOI.sphere && mirrorSphereCamera ) {
+
+				OOI.sphere.visible = false;
+				OOI.sphere.getWorldPosition( mirrorSphereCamera.position );
+				mirrorSphereCamera.update( renderer, scene );
+				OOI.sphere.visible = true;
+
+			}
+
+			if ( OOI.sphere && conf.followSphere ) {
+
+				// orbitControls follows the sphere
+				OOI.sphere.getWorldPosition( v0 );
+				orbitControls.target.lerp( v0, .1 );
+
+			}
+		
+			if ( OOI.head && OOI.sphere && conf.turnHead ) {
+
+				// turn head
+				OOI.sphere.getWorldPosition( v0 );
+				OOI.head.lookAt( v0 );
+				OOI.head.rotation.set( OOI.head.rotation.x, OOI.head.rotation.y + Math.PI, OOI.head.rotation.z );
+
+			}
+
+			if ( conf.ik_solver ) {
+
+				IKSolver?.update();
+
+			}
+
+			orbitControls.update();
+			renderer.render( scene, camera );
+
+			stats.update(); // fps stats
+
+			requestAnimationFrame( animate );
+
+		}
+
+		function onWindowResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( window.innerWidth, window.innerHeight );
+
+		}
+		</script>
+	</body>
+</html>