You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
6.5 KiB

2 years ago
<!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/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../../build/three.module.js",
"three/addons/": "../../examples/jsm/"
}
}
</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,
Scene,
SkinnedMesh,
Skeleton,
SkeletonHelper,
Vector3,
Uint16BufferAttribute,
WebGLRenderer
} from 'three';
import { CCDIKSolver, CCDIKHelper } from 'three/addons/animation/CCDIKSolver.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/addons/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"
const 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>