javascript fast iterative solver for Inverse Kinematics on three.js
4 | is a conversion from java to Caliko libs, Caliko library is an implementation
5 | of the FABRIK inverse kinematics algorithm by Dr. Andreas Aristidou
10 | Note: Fullik is now FIK
11 | new Fullik.Bone() -> new FIK.Bone3D()
12 | new Fullik.Bone() -> new FIK.Bone3D()
13 | new Fullik.Chain() -> new FIK.Chain3D()
14 | new Fullik.Structure() -> new FIK.Structure3D()
15 |
16 |
17 | ## Usage
18 |
19 | ```js
20 | const { Structure3D } = require('FIK');
21 | const scene = new THREE.Scene();
22 |
23 | const solver = Structure3D(scene);
24 |
25 | const n = {
26 | mesh : new THREE.Mesh( new THREE.SphereGeometry( 0.1, 6, 4 ), new THREE.MeshStandardMaterial({color:0xFFFF00, wireframe:true }) ),
27 | control : new THREE.TransformControls( camera, renderer.domElement ),
28 | }
29 |
30 | function updateSolver() {
31 | solver.update();
32 | }
33 |
34 | scene.add(n.mesh);
35 | scene.add(n.control);
36 |
37 | n.control.addEventListener('change', updateSolver);
38 |
39 | n.position = n.mesh.position; // publishes x, y, z to the engine...
40 |
41 | ```
42 |
43 | From [Demo 2](https://github.com/lo-th/fullik/blob/gh-pages/demos/3d/demo_2.js)
44 |
45 | ```js
46 | var numChains = 3;
47 | var rotStep = 360 / numChains;
48 | var constraintAngleDegs = 45;
49 | var color = 0xFF0000;
50 |
51 | for (var i = 0; i < numChains; i++ ){
52 |
53 | switch (i){
54 | case 0: color = 0x550000; break;
55 | case 1: color = 0x005500; break;
56 | case 2: color = 0x000055; break;
57 | }
58 |
59 | var chain = new FIK.Chain3D( color )
60 |
61 | var startLoc = new FIK.V3(0, 0, -40);
62 | startLoc = FIK._Math.rotateYDegs( startLoc, rotStep * i );
63 | var endLoc = startLoc.clone();
64 | endLoc.z -= defaultBoneLength;
65 |
66 | var basebone = new FIK.Bone3D( startLoc, endLoc );
67 | chain.addBone( basebone );
68 |
69 | for (var j = 0; j < 7; j++) {
70 |
71 | chain.addConsecutiveRotorConstrainedBone( defaultBoneDirection, defaultBoneLength, constraintAngleDegs );
72 |
73 | };
74 |
75 | solver.add( chain, target, true );
76 |
77 | }
78 | ```
79 |
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
79 |
--------------------------------------------------------------------------------
/demos/2d/demo_0.js:
--------------------------------------------------------------------------------
1 | tell("Demo 0 - stress test.");
2 |
3 | var n = 60;
4 | var pi = Math.PI;
5 | var numBones = 20;
6 | var x, y, r = 100, r2 = 95;
7 | var v1, v2, uv
8 |
9 | for( var j=0; j < n; j++ ){
10 |
11 | x = Math.cos( 2 * j * pi / n );
12 | y = Math.sin( 2 * j * pi / n );
13 |
14 | v1 = new FIK.V2( x*r, y*r );
15 | v2 = new FIK.V2( x*r2, y*r2 );
16 |
17 |
18 | var chain = new FIK.Chain2D( FIK.math.rand(0x555555, 0xFFFFFF) );
19 | var boneLength = 5;
20 | var basebone = new FIK.Bone2D( v1, v2 );
21 | chain.addBone( basebone );
22 |
23 | uv = v2.minus( v1 ).normalize();
24 |
25 | for(var i=0; i
2 |
3 |
4 |
5 | fiK
6 |
7 |
21 |
22 |
23 |
24 |
FULLIK GITHUB
25 |
26 |
27 |
28 |
33 |
452 |
453 |
454 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fullik",
3 | "version": "1.3.3",
4 | "description": "JavaScript 3D library",
5 | "main": "build/fik.js",
6 | "repository": "lo-th/Fullik",
7 | "jsnext:main": "build/fik.module.js",
8 | "module": "build/fik.module.js",
9 | "files": [
10 | "package.json",
11 | "LICENSE",
12 | "README.md",
13 | "build/fik.js",
14 | "build/fik.min.js",
15 | "build/fik.module.js",
16 | "src"
17 | ],
18 | "directories": {
19 | "example": "examples"
20 | },
21 | "eslintConfig": {
22 | "extends": "mdcs"
23 | },
24 | "scripts": {
25 | "build": "rollup -c",
26 | "dev": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c utils/rollup.config.js -w -m inline\" \"servez -p 8111 --index\"",
27 | "start": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c utils/rollup.config.js -w -m inline\" \"http-server -c-1 -p 8111 -o index.html\""
28 | },
29 | "keywords": [
30 | "fullik",
31 | "fik",
32 | "fik.js",
33 | "ui"
34 | ],
35 | "author": "lo-th",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/lo-th/Fullik/issues"
39 | },
40 | "homepage": "http://lo-th.github.io/Fullik/",
41 | "devDependencies": {
42 | "@babel/core": "^7.12.16",
43 | "@babel/plugin-proposal-class-properties": "^7.12.13",
44 | "@babel/preset-env": "^7.12.16",
45 | "@rollup/plugin-babel": "^5.3.0",
46 | "@rollup/plugin-node-resolve": "^11.2.0",
47 | "concurrently": "^5.3.0",
48 | "regenerator-runtime": "^0.13.7",
49 | "rollup": "^2.39.0",
50 | "rollup-plugin-terser": "^7.0.2",
51 | "servez": "^1.11.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import { terser } from 'rollup-plugin-terser';
3 |
4 | function babelCleanup() {
5 |
6 | const doubleSpaces = / {2}/g;
7 |
8 | return {
9 |
10 | transform( code ) {
11 |
12 | code = code.replace( doubleSpaces, '\t' );
13 |
14 | return {
15 | code: code,
16 | map: null
17 | };
18 |
19 | }
20 |
21 | };
22 |
23 | }
24 |
25 |
26 | function header() {
27 |
28 | return {
29 |
30 | renderChunk( code ) {
31 |
32 | return `/**
33 | * @license
34 | * Copyright 2010-2022 fik.js Authors
35 | * SPDX-License-Identifier: MIT
36 | */
37 | ${ code }`;
38 |
39 | }
40 |
41 | };
42 |
43 | }
44 |
45 |
46 |
47 | const babelrc = {
48 | presets: [
49 | [
50 | '@babel/preset-env',
51 | {
52 | modules: false,
53 | // the supported browsers of the three.js browser bundle
54 | // https://browsersl.ist/?q=%3E0.3%25%2C+not+dead
55 | targets: '>1%',
56 | loose: true,
57 | bugfixes: true,
58 | }
59 | ]
60 | ],
61 | plugins: [
62 | [
63 | "@babel/plugin-proposal-class-properties",
64 | {
65 | "loose": true
66 | }
67 | ]
68 | ]
69 | };
70 |
71 | export default [
72 | {
73 | input: 'src/FIK.js',
74 | plugins: [
75 | header()
76 | ],
77 | output: [
78 | {
79 | format: 'esm',
80 | file: 'build/fik.module.js'
81 | }
82 | ]
83 | },
84 | {
85 | input: 'src/FIK.js',
86 | plugins: [
87 | babel( {
88 | babelHelpers: 'bundled',
89 | compact: false,
90 | babelrc: false,
91 | ...babelrc
92 | } ),
93 | babelCleanup(),
94 | header()
95 | ],
96 | output: [
97 | {
98 | format: 'umd',
99 | name: 'FIK',
100 | file: 'build/fik.js',
101 | indent: '\t'
102 | }
103 | ]
104 | },
105 | {
106 | input: 'src/FIK.js',
107 | plugins: [
108 | babel( {
109 | babelHelpers: 'bundled',
110 | babelrc: false,
111 | ...babelrc
112 | } ),
113 | babelCleanup(),
114 | terser(),
115 | header()
116 | ],
117 | output: [
118 | {
119 | format: 'umd',
120 | name: 'FIK',
121 | file: 'build/fik.min.js'
122 | }
123 | ]
124 | }
125 |
126 | ];
127 |
128 |
--------------------------------------------------------------------------------
/src/FIK.js:
--------------------------------------------------------------------------------
1 | export { math } from './math/Math.js';
2 | export { V2 } from './math/V2.js';
3 | export { V3 } from './math/V3.js';
4 | export { M3 } from './math/M3.js';
5 |
6 | export { Joint3D } from './core/Joint3D.js';
7 | export { Bone3D } from './core/Bone3D.js';
8 | export { Chain3D } from './core/Chain3D.js';
9 | export { Structure3D } from './core/Structure3D.js';
10 |
11 | export { Joint2D } from './core/Joint2D.js';
12 | export { Bone2D } from './core/Bone2D.js';
13 | export { Chain2D } from './core/Chain2D.js';
14 | export { Structure2D } from './core/Structure2D.js';
15 |
16 | export { IKSolver } from './solver/IKSolver.js';
17 | export { HISolver } from './solver/HISolver.js';
18 |
19 | export * from './constants.js';
--------------------------------------------------------------------------------
/src/FIK_dev.js:
--------------------------------------------------------------------------------
1 | //import './polyfills.js';
2 |
3 | import { math } from './math/Math.js';
4 | import { V2 } from './math/V2.js';
5 | import { V3 } from './math/V3.js';
6 | import { M3 } from './math/M3.js';
7 |
8 | import { Joint3D } from './core/Joint3D.js';
9 | import { Bone3D } from './core/Bone3D.js';
10 | import { Chain3D } from './core/Chain3D.js';
11 | import { Structure3D } from './core/Structure3D.js';
12 |
13 | import { Joint2D } from './core/Joint2D.js';
14 | import { Bone2D } from './core/Bone2D.js';
15 | import { Chain2D } from './core/Chain2D.js';
16 | import { Structure2D } from './core/Structure2D.js';
17 |
18 | import { IKSolver } from './solver/IKSolver.js';
19 | import { HISolver } from './solver/HISolver.js';
20 |
21 | import { REVISION, X_AXE, Y_AXE, Z_AXE, X_NEG, Y_NEG, Z_NEG, UP, DOWN, LEFT, RIGHT } from './constants.js';
22 |
23 | export const FIK = {
24 |
25 | REVISION : REVISION,
26 |
27 | X_AXE : X_AXE,
28 | Y_AXE : Y_AXE,
29 | Z_AXE : Z_AXE,
30 | X_NEG : X_NEG,
31 | Y_NEG : Y_NEG,
32 | Z_NEG : Z_NEG,
33 | UP : UP,
34 | DOWN : DOWN,
35 | LEFT : LEFT,
36 | RIGHT : RIGHT,
37 |
38 | math:math,
39 | V2:V2,
40 | V3:V3,
41 | M3:M3,
42 |
43 | Bone3D:Bone3D,
44 | Chain3D:Chain3D,
45 | Joint3D:Joint3D,
46 | Structure3D:Structure3D,
47 |
48 | Bone2D:Bone2D,
49 | Chain2D:Chain2D,
50 | Joint2D:Joint2D,
51 | Structure2D:Structure2D,
52 |
53 | IKSolver:IKSolver,
54 | HISolver:HISolver,
55 |
56 | }
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A list of constants built-in for
3 | * the Fik engine.
4 | */
5 | import { V3 } from './math/V3.js';
6 | import { V2 } from './math/V2.js';
7 | import { M3 } from './math/M3.js';
8 |
9 | export const REVISION = '1.4.0';
10 |
11 | export const PRECISION = 0.001;
12 | export const PRECISION_DEG = 0.01;
13 | export const MAX_VALUE = Infinity;
14 |
15 | // chain Basebone Constraint Type
16 |
17 | export const NONE = 1; // No constraint
18 | // 3D
19 | export const GLOBAL_ROTOR = 2;// World-space rotor constraint
20 | export const GLOBAL_HINGE = 3;// World-space hinge constraint
21 | export const LOCAL_ROTOR = 4;// Rotor constraint in the coordinate space of (i.e. relative to) the direction of the connected bone
22 | export const LOCAL_HINGE = 5;// Hinge constraint in the coordinate space of (i.e. relative to) the direction of the connected bone
23 |
24 | // 2D
25 | export const GLOBAL_ABSOLUTE = 6; // Constrained about a world-space direction
26 | export const LOCAL_RELATIVE = 7; // Constrained about the direction of the connected bone
27 | export const LOCAL_ABSOLUTE = 8; // Constrained about a direction with relative to the direction of the connected bone
28 |
29 | // joint Type
30 | export const J_BALL = 10;
31 | export const J_LOCAL = 11;
32 | export const J_GLOBAL = 12;
33 |
34 | export const START = 20;
35 | export const END = 21;
36 |
37 | // Define world-space axis
38 |
39 | export const X_AXE = new V3( 1, 0, 0 );
40 | export const Y_AXE = new V3( 0, 1, 0 );
41 | export const Z_AXE = new V3( 0, 0, 1 );
42 |
43 | export const X_NEG = new V3( -1, 0, 0 );
44 | export const Y_NEG = new V3( 0, -1, 0 );
45 | export const Z_NEG = new V3( 0, 0, -1 );
46 |
47 | // Define world-space 2D cardinal axes
48 |
49 | export const UP = new V2( 0, 1 );
50 | export const DOWN = new V2( 0, -1 );
51 | export const LEFT = new V2( -1, 0 );
52 | export const RIGHT = new V2( 1, 0 );
53 |
--------------------------------------------------------------------------------
/src/core/Bone2D.js:
--------------------------------------------------------------------------------
1 | import { END, START } from '../constants.js';
2 | import { Joint2D } from './Joint2D.js';
3 | import { V2 } from '../math/V2.js';
4 |
5 |
6 | export class Bone2D {
7 |
8 | constructor( Start, End, directionUV, length, clockwiseDegs, anticlockwiseDegs, color ) {
9 |
10 | this.isBone2D = true
11 |
12 | this.start = new V2();
13 | this.end = new V2();
14 | this.length = length || 0;
15 |
16 | this.joint = new Joint2D( clockwiseDegs, anticlockwiseDegs );
17 |
18 | this.globalConstraintUV = new V2(1, 0);
19 | this.boneConnectionPoint = END;
20 |
21 | this.color = color || null;
22 | this.name = '';
23 |
24 | // init
25 |
26 | this.setStartLocation( Start );
27 |
28 | if( End ){
29 |
30 | this.setEndLocation( End );
31 | if( this.length === 0 ) this.length = this.getLength();
32 |
33 | } else if ( directionUV ) {
34 |
35 | this.setEndLocation( this.start.plus( directionUV.normalised().multiplyScalar( this.length ) ) );
36 |
37 | }
38 |
39 | }
40 |
41 | clone() {
42 |
43 | let b = new Bone2D( this.start, this.end );
44 | b.length = this.length;
45 | b.globalConstraintUV = this.globalConstraintUV;
46 | b.boneConnectionPoint = this.boneConnectionPoint;
47 | b.joint = this.joint.clone();
48 | b.color = this.color;
49 | b.name = this.name;
50 | return b;
51 |
52 | }
53 |
54 | // SET
55 |
56 | setName( name ) {
57 |
58 | this.name = name;
59 |
60 | }
61 |
62 | setColor( c ) {
63 |
64 | this.color = c;
65 |
66 | }
67 |
68 | setBoneConnectionPoint( bcp ) {
69 |
70 | this.boneConnectionPoint = bcp;
71 |
72 | }
73 |
74 | setStartLocation( v ) {
75 |
76 | this.start.copy( v );
77 |
78 | }
79 |
80 | setEndLocation( v ) {
81 |
82 | this.end.copy( v );
83 |
84 | }
85 |
86 | setLengt( length ) {
87 |
88 | if ( length > 0 ) this.length = length;
89 |
90 | }
91 |
92 | setGlobalConstraintUV( v ) {
93 |
94 | this.globalConstraintUV = v;
95 |
96 | }
97 |
98 | // SET JOINT
99 |
100 | setJoint( joint ) {
101 |
102 | this.joint = joint;
103 |
104 | }
105 |
106 | setClockwiseConstraintDegs( angleDegs ) {
107 |
108 | this.joint.setClockwiseConstraintDegs( angleDegs );
109 |
110 | }
111 |
112 | setAnticlockwiseConstraintDegs( angleDegs ) {
113 |
114 | this.joint.setAnticlockwiseConstraintDegs( angleDegs );
115 |
116 | }
117 |
118 | setJointConstraintCoordinateSystem ( coordSystem ) {
119 |
120 | this.joint.setConstraintCoordinateSystem( coordSystem );
121 |
122 | }
123 |
124 |
125 | // GET
126 |
127 | getGlobalConstraintUV() {
128 |
129 | return this.globalConstraintUV;
130 |
131 | }
132 |
133 | getBoneConnectionPoint() {
134 |
135 | return this.boneConnectionPoint;
136 |
137 | }
138 |
139 | getDirectionUV() {
140 |
141 | return this.end.minus( this.start ).normalize();
142 |
143 | }
144 |
145 | getLength() {
146 |
147 | return this.start.distanceTo( this.end );
148 |
149 | }
150 |
151 |
152 | }
--------------------------------------------------------------------------------
/src/core/Bone3D.js:
--------------------------------------------------------------------------------
1 | import { END, START } from '../constants.js';
2 | import { Joint3D } from './Joint3D.js';
3 | import { V3 } from '../math/V3.js';
4 |
5 |
6 | export class Bone3D {
7 |
8 | constructor( startLocation, endLocation, directionUV, length, color ) {
9 |
10 | this.isBone3D = true
11 |
12 | this.joint = new Joint3D();
13 | this.start = new V3();
14 | this.end = new V3();
15 |
16 | this.dir = new V3();
17 |
18 | this.boneConnectionPoint = END;
19 | this.length = 0;
20 |
21 | this.color = color || 0xFFFFFF;
22 | this.name = '';
23 |
24 | this.init( startLocation, endLocation, directionUV, length );
25 | this.dir = this.getDirection()
26 |
27 | }
28 |
29 | init( startLocation, endLocation, directionUV, length ){
30 |
31 | this.setStartLocation( startLocation );
32 | if( endLocation ){
33 | this.setEndLocation( endLocation );
34 | this.length = this.getLength();
35 |
36 | } else {
37 | this.setLength( length );
38 | this.setEndLocation( this.start.plus( directionUV.normalised().multiplyScalar( length ) ) );
39 | }
40 |
41 |
42 |
43 | }
44 |
45 | clone() {
46 |
47 | let b = new this.constructor( this.start, this.end );
48 | b.joint = this.joint.clone();
49 | return b;
50 |
51 | }
52 |
53 | // SET
54 |
55 | setColor( c ) {
56 |
57 | this.color = c;
58 |
59 | }
60 |
61 | setBoneConnectionPoint( bcp ) {
62 |
63 | this.boneConnectionPoint = bcp;
64 |
65 | }
66 |
67 | setHingeClockwise( angle ) {
68 |
69 |
70 | this.joint.setHingeClockwise( angle );
71 |
72 | }
73 |
74 | setHingeAnticlockwise( angle ) {
75 |
76 | this.joint.setHingeAnticlockwise( angle );
77 |
78 | }
79 |
80 | setBallJointConstraintDegs( angle ) {
81 |
82 | this.joint.setBallJointConstraintDegs( angle );
83 |
84 | }
85 |
86 | setStartLocation( location ) {
87 |
88 | this.start.copy ( location );
89 |
90 | }
91 |
92 | setEndLocation( location ) {
93 |
94 | this.end.copy ( location );
95 |
96 | }
97 |
98 | getDirection() {
99 |
100 | return this.end.clone().min( this.start ).normalize();
101 |
102 | }
103 |
104 | setLength( lng ) {
105 |
106 | if ( lng > 0 ) this.length = lng;
107 |
108 | }
109 |
110 | setJoint( joint ) {
111 |
112 | this.joint = joint;
113 |
114 | }
115 |
116 |
117 | // GET
118 |
119 | getBoneConnectionPoint() {
120 |
121 | return this.boneConnectionPoint;
122 |
123 | }
124 |
125 | getDirectionUV () {
126 |
127 | return this.end.minus( this.start ).normalize();
128 |
129 | }
130 |
131 | getLength(){
132 |
133 | return this.start.distanceTo( this.end );
134 |
135 | }
136 |
137 | }
--------------------------------------------------------------------------------
/src/core/Chain2D.js:
--------------------------------------------------------------------------------
1 | import { NONE, GLOBAL_ABSOLUTE, LOCAL_RELATIVE, LOCAL_ABSOLUTE, END, START, J_LOCAL, J_GLOBAL, MAX_VALUE } from '../constants.js';
2 | import { math } from '../math/Math.js';
3 | import { V2 } from '../math/V2.js';
4 | import { Bone2D } from './Bone2D.js';
5 | import { Tools } from './Tools.js';
6 |
7 |
8 | export class Chain2D {
9 |
10 | constructor( color ) {
11 |
12 | this.isChain2D = true;
13 | this.tmpTarget = new V2();
14 |
15 | this.bones = [];
16 | this.name = '';
17 |
18 | this.solveDistanceThreshold = 1.0;
19 | this.minIterationChange = 0.01;
20 | this.maxIteration = 15;
21 | this.precision = 0.001;
22 |
23 | this.chainLength = 0;
24 | this.numBones = 0;
25 |
26 | this.baseLocation = new V2();
27 | this.fixedBaseMode = true;
28 |
29 | this.baseboneConstraintType = NONE;
30 |
31 | this.baseboneConstraintUV = new V2();
32 | this.baseboneRelativeConstraintUV = new V2();
33 |
34 | this.lastTargetLocation = new V2( MAX_VALUE, MAX_VALUE );
35 | this.lastBaseLocation = new V2( MAX_VALUE, MAX_VALUE );
36 |
37 | this.boneConnectionPoint = END;
38 |
39 | this.currentSolveDistance = MAX_VALUE;
40 | this.connectedChainNumber = -1;
41 | this.connectedBoneNumber = -1;
42 |
43 | this.color = color || 0xFFFFFF;
44 |
45 | this.embeddedTarget = new V2();
46 | this.useEmbeddedTarget = false;
47 |
48 | }
49 |
50 | clone() {
51 |
52 | let c = new this.constructor()
53 |
54 | c.solveDistanceThreshold = this.solveDistanceThreshold;
55 | c.minIterationChange = this.minIterationChange;
56 | c.maxIteration = this.maxIteration;
57 | c.precision = this.precision;
58 |
59 | c.bones = this.cloneBones();
60 | c.baseLocation.copy( this.baseLocation );
61 | c.lastTargetLocation.copy( this.lastTargetLocation );
62 | c.lastBaseLocation.copy( this.lastBaseLocation );
63 |
64 | // Copy the basebone constraint UV if there is one to copy
65 | if ( !(this.baseboneConstraintType === NONE) ){
66 | c.baseboneConstraintUV.copy( this.baseboneConstraintUV );
67 | c.baseboneRelativeConstraintUV.copy( this.baseboneRelativeConstraintUV );
68 | }
69 |
70 | // Native copy by value for primitive members
71 | c.fixedBaseMode = this.fixedBaseMode;
72 |
73 | c.chainLength = this.chainLength;
74 | c.numBones = this.numBones;
75 | c.currentSolveDistance = this.currentSolveDistance;
76 |
77 | c.boneConnectionPoint = this.boneConnectionPoint;
78 | c.connectedChainNumber = this.connectedChainNumber;
79 | c.connectedBoneNumber = this.connectedBoneNumber;
80 | c.baseboneConstraintType = this.baseboneConstraintType;
81 |
82 | c.color = this.color;
83 |
84 | c.embeddedTarget = this.embeddedTarget.clone();
85 | c.useEmbeddedTarget = this.useEmbeddedTarget;
86 |
87 | return c;
88 |
89 | }
90 |
91 | clear() {
92 |
93 | let i = this.numBones;
94 | while(i--){
95 | this.removeBone(i);
96 | }
97 |
98 | }
99 |
100 | addBone( bone ) {
101 |
102 | if( bone.color === null ) bone.setColor( this.color );
103 |
104 | // Add the new bone to the end of the ArrayList of bones
105 | this.bones.push( bone );
106 |
107 |
108 | // If this is the basebone...
109 | if ( this.numBones === 0 ){
110 | // ...then keep a copy of the fixed start location...
111 | this.baseLocation.copy( bone.start );
112 |
113 | // ...and set the basebone constraint UV to be around the initial bone direction
114 | this.baseboneConstraintUV.copy( bone.getDirectionUV() );
115 |
116 | }
117 |
118 | // Increment the number of bones in the chain and update the chain length
119 | this.numBones ++;
120 |
121 | // Increment the number of bones in the chain and update the chain length
122 | this.updateChainLength();
123 |
124 | }
125 |
126 | removeBone( id ) {
127 |
128 | if ( id < this.numBones ){
129 | // ...then remove the bone, decrease the bone count and update the chain length.
130 | this.bones.splice(id, 1)
131 | this.numBones --;
132 | this.updateChainLength();
133 | }
134 |
135 | }
136 |
137 | addConsecutiveBone( directionUV, length, clockwiseDegs, anticlockwiseDegs, color ) {
138 |
139 | if ( this.numBones === 0 ){ Tools.error('Chain is empty ! need first bone'); return };
140 |
141 | if( directionUV.isBone2D ) { // first argument is bone
142 |
143 | let bone = directionUV;
144 |
145 | // Validate the direction unit vector - throws an IllegalArgumentException if it has a magnitude of zero
146 | let dir = bone.getDirectionUV();
147 | math.validateDirectionUV( dir );
148 |
149 | // Validate the length of the bone - throws an IllegalArgumentException if it is not a positive value
150 | let len = bone.length;
151 | math.validateLength( len );
152 |
153 | let prevBoneEnd = this.bones[ this.numBones-1 ].end;
154 |
155 | bone.setStartLocation( prevBoneEnd );
156 | bone.setEndLocation( prevBoneEnd.plus( dir.multiplyScalar( len ) ) );
157 |
158 | // Add a bone to the end of this IK chain
159 | this.addBone( bone );
160 |
161 | } else if( directionUV.isVector2 ) {
162 |
163 | color = color || this.color;
164 |
165 | // Validate the direction unit vector - throws an IllegalArgumentException if it has a magnitude of zero
166 | math.validateDirectionUV( directionUV );
167 |
168 | // Validate the length of the bone - throws an IllegalArgumentException if it is not a positive value
169 | math.validateLength( length );
170 |
171 | // Get the end location of the last bone, which will be used as the start location of the new bone
172 | let prevBoneEnd = this.bones[ this.numBones-1 ].end;
173 |
174 | // Add a bone to the end of this IK chain
175 | this.addBone( new Bone2D( prevBoneEnd, null, directionUV.normalised(), length, clockwiseDegs, anticlockwiseDegs, color ) );
176 |
177 |
178 | }
179 |
180 | }
181 |
182 |
183 | // -------------------------------
184 | // GET
185 | // -------------------------------
186 |
187 | getBoneConnectionPoint() {
188 |
189 | return this.boneConnectionPoint;
190 |
191 | }
192 |
193 | getConnectedBoneNumber() {
194 |
195 | return this.connectedBoneNumber;
196 |
197 | }
198 |
199 | getConnectedChainNumber(){
200 |
201 | return this.connectedChainNumber;
202 |
203 | }
204 |
205 | getEmbeddedTarget() {
206 |
207 | return this.embeddedTarget;
208 |
209 | }
210 |
211 | getBaseboneConstraintType() {
212 |
213 | return this.baseboneConstraintType;
214 |
215 | }
216 |
217 | getBaseboneConstraintUV() {
218 |
219 | if ( !(this.baseboneConstraintType === NONE) ) return this.baseboneConstraintUV;
220 |
221 | }
222 |
223 | getBaseLocation() {
224 |
225 | return this.bones[0].start;
226 |
227 | }
228 |
229 | getEffectorLocation() {
230 |
231 | return this.bones[this.numBones-1].end;
232 |
233 | }
234 |
235 | getLastTargetLocation() {
236 |
237 | return this.lastTargetLocation;
238 |
239 | }
240 |
241 | getLiveChainLength() {
242 |
243 | let lng = 0;
244 | let i = this.numBones;
245 | while( i-- ) lng += this.bones[i].getLength();
246 | return lng;
247 |
248 | }
249 |
250 |
251 | // -------------------------------
252 | // SET
253 | // -------------------------------
254 |
255 | setColor( color ) {
256 |
257 | this.color = color;
258 | let i = this.numBones;
259 | while( i-- ) this.bones[i].setColor( this.color );
260 |
261 | }
262 |
263 | setBaseboneRelativeConstraintUV( constraintUV ) {
264 |
265 | this.baseboneRelativeConstraintUV = constraintUV;
266 |
267 | }
268 |
269 | setConnectedBoneNumber( boneNumber ) {
270 |
271 | this.connectedBoneNumber = boneNumber;
272 |
273 | }
274 |
275 | setConnectedChainNumber( chainNumber ) {
276 |
277 | this.connectedChainNumber = chainNumber;
278 |
279 | }
280 |
281 | setBoneConnectionPoint( point ) {
282 |
283 | this.boneConnectionPoint = point;
284 |
285 | }
286 |
287 | setBaseboneConstraintUV( constraintUV ) {
288 |
289 | math.validateDirectionUV( constraintUV );
290 | this.baseboneConstraintUV.copy( constraintUV.normalised() );
291 |
292 | }
293 |
294 | setBaseLocation( baseLocation ){
295 |
296 | this.baseLocation.copy( baseLocation );
297 |
298 | }
299 |
300 | setBaseboneConstraintType( value ) {
301 |
302 | this.baseboneConstraintType = value;
303 |
304 | }
305 |
306 | setFixedBaseMode( value ) {
307 |
308 | // Enforce that a chain connected to another chain stays in fixed base mode (i.e. it moves with the chain it's connected to instead of independently)
309 | if ( !value && this.connectedChainNumber !== -1) return;
310 | if ( this.baseboneConstraintType === GLOBAL_ABSOLUTE && !value ) return;
311 | // Above conditions met? Set the fixedBaseMode
312 | this.fixedBaseMode = value;
313 |
314 | }
315 |
316 | setMaxIterationAttempts( maxIteration ) {
317 |
318 | if ( maxIteration < 1 ) return;
319 | this.maxIteration = maxIteration;
320 |
321 | }
322 |
323 | setMinIterationChange( minIterationChange ) {
324 |
325 | if (minIterationChange < 0) return;
326 | this.minIterationChange = minIterationChange;
327 |
328 | }
329 |
330 | setSolveDistanceThreshold( solveDistance ) {
331 |
332 | if ( solveDistance < 0 ) return;
333 | this.solveDistanceThreshold = solveDistance;
334 |
335 | }
336 |
337 | // -------------------------------
338 | //
339 | // UPDATE TARGET
340 | //
341 | // -------------------------------
342 |
343 | solveForEmbeddedTarget() {
344 |
345 | if ( this.useEmbeddedTarget ) return this.solveForTarget( this.embeddedTarget );
346 |
347 | }
348 |
349 | resetTarget(){
350 |
351 | this.lastBaseLocation = new V2( MAX_VALUE, MAX_VALUE );
352 | this.currentSolveDistance = MAX_VALUE;
353 |
354 | }
355 |
356 |
357 | // Solve the IK chain for this target to the best of our ability.
358 | // The end result of running this method is that the IK chain configuration is updated.
359 |
360 | // To minimuse CPU usage, this method dynamically aborts if:
361 | // - The solve distance (i.e. distance between the end effector and the target) is below the solveDistanceThreshold,
362 | // - A solution incrementally improves on the previous solution by less than the minIterationChange, or
363 | // - The number of attempts to solve the IK chain exceeds the maxIteration.
364 |
365 | solveForTarget( t ) {
366 |
367 | this.tmpTarget.set( t.x, t.y );
368 | let p = this.precision;
369 |
370 | let isSameBaseLocation = this.lastBaseLocation.approximatelyEquals( this.baseLocation, p );
371 |
372 | // If we have both the same target and base location as the last run then do not solve
373 | if ( this.lastTargetLocation.approximatelyEquals( this.tmpTarget, p ) && isSameBaseLocation ) return this.currentSolveDistance;
374 |
375 | // Keep starting solutions and distance
376 | let startingDistance;
377 | let startingSolution = null;
378 |
379 | // If the base location of a chain hasn't moved then we may opt to keep the current solution if our
380 | // best new solution is worse...
381 | if ( isSameBaseLocation ) {
382 | startingDistance = this.bones[ this.numBones-1 ].end.distanceTo( this.tmpTarget );
383 | startingSolution = this.cloneBones();
384 | } else {
385 | // Base has changed? Then we have little choice but to recalc the solution and take that new solution.
386 | startingDistance = MAX_VALUE;
387 | }
388 |
389 | // Not the same target? Then we must solve the chain for the new target.
390 | // We'll start by creating a list of bones to store our best solution
391 | let bestSolution = [];
392 |
393 | // We'll keep track of our best solve distance, starting it at a huge value which will be beaten on first attempt
394 | let bestSolveDistance = MAX_VALUE;
395 | let lastPassSolveDistance = MAX_VALUE;
396 |
397 | // Allow up to our iteration limit attempts at solving the chain
398 | let solveDistance;
399 |
400 | let i = this.maxIteration;
401 |
402 | while( i-- ){
403 |
404 | // Solve the chain for this target
405 | solveDistance = this.solveIK( this.tmpTarget );
406 |
407 | // Did we solve it for distance? If so, update our best distance and best solution, and also
408 | // update our last pass solve distance. Note: We will ALWAYS beat our last solve distance on the first run
409 |
410 | if ( solveDistance < bestSolveDistance ) {
411 |
412 | bestSolveDistance = solveDistance;
413 | bestSolution = this.cloneBones();
414 |
415 | // If we are happy that this solution meets our distance requirements then we can exit the loop now
416 | if ( solveDistance <= this.solveDistanceThreshold ) break;
417 |
418 | } else {
419 |
420 | // Did not solve to our satisfaction? Okay...
421 | // Did we grind to a halt? If so break out of loop to set the best distance and solution that we have
422 | if ( Math.abs( solveDistance - lastPassSolveDistance ) < this.minIterationChange ) break;
423 |
424 | }
425 |
426 | // Update the last pass solve distance
427 | lastPassSolveDistance = solveDistance;
428 |
429 | }
430 |
431 | // Did we get a solution that's better than the starting solution's to the new target location?
432 | if ( bestSolveDistance < startingDistance ){
433 | // If so, set the newly found solve distance and solution as the best found.
434 | this.currentSolveDistance = bestSolveDistance;
435 | this.bones = bestSolution;
436 | } else {
437 | // Did we make things worse? Then we keep our starting distance and solution!
438 | this.currentSolveDistance = startingDistance;
439 | this.bones = startingSolution;
440 | }
441 |
442 | // Update our last base and target locations so we know whether we need to solve for this start/end configuration next time
443 | this.lastBaseLocation.copy( this.baseLocation );
444 | this.lastTargetLocation.copy( this.tmpTarget );
445 |
446 | return this.currentSolveDistance;
447 |
448 | }
449 |
450 | // -------------------------------
451 | //
452 | // SOLVE IK
453 | //
454 | // -------------------------------
455 |
456 | // Solve the IK chain for the given target using the FABRIK algorithm.
457 | // retun the best solve distance found between the end-effector of this chain and the provided target.
458 |
459 | solveIK( target ) {
460 |
461 | if ( this.numBones === 0 ) return;
462 |
463 | let bone, boneLength, angle, nextBone, startPosition, endPosition, directionUV, baselineUV;
464 |
465 | // ---------- Forward pass from end effector to base -----------
466 |
467 | // Loop over all bones in the chain, from the end effector (numBones-1) back to the basebone (0)
468 | let i = this.numBones;
469 |
470 | while( i-- ){
471 |
472 | // Get the length of the bone we're working on
473 | bone = this.bones[i];
474 | boneLength = bone.length;
475 |
476 |
477 | // If we are NOT working on the end effector bone
478 | if ( i !== this.numBones - 1 ) {
479 |
480 | nextBone = this.bones[i+1];
481 |
482 | // Get the outer-to-inner unit vector of this bone
483 | directionUV = bone.getDirectionUV().negate();
484 |
485 | // Get the outer-to-inner unit vector of the bone further out
486 | baselineUV = bone.joint.coordinateSystem === J_LOCAL ? nextBone.getDirectionUV().negate() : bone.getGlobalConstraintUV().negated();
487 | directionUV.constrainedUV( baselineUV, nextBone.joint.min, nextBone.joint.max );
488 |
489 | // At this stage we have a outer-to-inner unit vector for this bone which is within our constraints,
490 | // so we can set the new inner joint location to be the end joint location of this bone plus the
491 | // outer-to-inner direction unit vector multiplied by the length of the bone.
492 | startPosition = bone.end.plus( directionUV.multiplyScalar( boneLength ) );
493 |
494 | // Set the new start joint location for this bone
495 | bone.setStartLocation( startPosition );
496 |
497 | // If we are not working on the basebone, then we also set the end joint location of
498 | // the previous bone in the chain (i.e. the bone closer to the base) to be the new
499 | // start joint location of this bone.
500 | if ( i > 0 ) this.bones[i-1].setEndLocation( startPosition );
501 |
502 | } else { // If we ARE working on the end effector bone...
503 |
504 | // Snap the end effector's end location to the target
505 | bone.setEndLocation( target );
506 |
507 | // update directionUV
508 | directionUV = bone.getDirectionUV().negate();
509 |
510 | if ( i > 0 ){
511 |
512 | // The end-effector bone is NOT the basebone as well
513 | // Get the outer-to-inner unit vector of the bone further in
514 | baselineUV = bone.joint.coordinateSystem === J_LOCAL ? this.bones[i-1].getDirectionUV().negate() : bone.getGlobalConstraintUV().negated();
515 | directionUV.constrainedUV( baselineUV, bone.joint.min, bone.joint.max );
516 |
517 | } else {
518 |
519 | if(bone.joint.coordinateSystem !== J_LOCAL){
520 |
521 | // Can constrain if constraining against global coordinate system
522 | baselineUV = bone.getGlobalConstraintUV().negated();
523 | directionUV.constrainedUV( baselineUV, bone.joint.min, bone.joint.max );
524 |
525 | }
526 |
527 | }
528 |
529 | // Calculate the new start joint location as the end joint location plus the outer-to-inner direction UV
530 | // multiplied by the length of the bone.
531 | startPosition = bone.end.plus( directionUV.multiplyScalar( boneLength ) );
532 |
533 | // Set the new start joint location for this bone to be new start location...
534 | bone.setStartLocation( startPosition );
535 |
536 | // ...and set the end joint location of the bone further in to also be at the new start location.
537 | if ( i > 0 ) this.bones[i-1].setEndLocation( startPosition );
538 |
539 | }
540 |
541 | } // End of forward pass loop over all bones
542 |
543 | // ---------- Step 2 of 2 - Backward pass from base to end effector -----------
544 |
545 | for ( i = 0; i < this.numBones; i++ ){
546 |
547 | bone = this.bones[i];
548 | boneLength = bone.length;
549 |
550 | // If we are not working on the basebone
551 | if ( i !== 0 ){
552 |
553 | // Get the inner-to-outer direction of this bone as well as the previous bone to use as a baseline
554 | directionUV = bone.getDirectionUV();
555 | // Constrain the angle between this bone and the inner bone.
556 | baselineUV = bone.joint.coordinateSystem === J_LOCAL ? this.bones[i-1].getDirectionUV() : bone.getGlobalConstraintUV();
557 | directionUV.constrainedUV( baselineUV, bone.joint.min, bone.joint.max );
558 |
559 | // At this stage we have an inner-to-outer unit vector for this bone which is within our constraints,
560 | // so we can set the new end location to be the start location of this bone plus the constrained
561 | // inner-to-outer direction unit vector multiplied by the length of this bone.
562 | endPosition = bone.start.plus( directionUV.multiplyScalar(boneLength) );
563 |
564 | // Set the new end joint location for this bone
565 | bone.setEndLocation( endPosition );
566 |
567 | // If we are not working on the end bone, then we set the start joint location of
568 | // the next bone in the chain (i.e. the bone closer to the end effector) to be the
569 | // new end joint location of this bone also.
570 | if ( i < this.numBones-1 ) this.bones[i+1].setStartLocation( endPosition );
571 |
572 | } else {// If we ARE working on the base bone...
573 |
574 | // If the base location is fixed then snap the start location of the base bone back to the fixed base
575 | if ( this.fixedBaseMode ){
576 |
577 | bone.setStartLocation( this.baseLocation );
578 |
579 | } else {// If the base location is not fixed...
580 |
581 | // ...then set the new base bone start location to be its the end location minus the
582 | // bone direction multiplied by the length of the bone (i.e. projected backwards).
583 | startPosition = bone.end.minus( bone.getDirectionUV().multiplyScalar( boneLength ) );
584 | bone.setStartLocation( startPosition );
585 |
586 | }
587 |
588 | // update directionUV
589 | directionUV = bone.getDirectionUV();
590 |
591 | // If the base bone is unconstrained then process it as usual...
592 | if ( this.baseboneConstraintType === NONE ){
593 |
594 | // Calculate the new end location as the start location plus the direction multiplyScalar by the length of the bone
595 | endPosition = bone.start.plus( directionUV.multiplyScalar( boneLength ) );
596 |
597 | // Set the new end joint location
598 | bone.setEndLocation( endPosition );
599 |
600 | // Also, set the start location of the next bone to be the end location of this bone
601 | if ( this.numBones > 1 ) this.bones[1].setStartLocation( endPosition );
602 |
603 | } else {
604 |
605 | // ...otherwise we must constrain it to the basebone constraint unit vector
606 |
607 | // LOCAL_ABSOLUTE? (i.e. local-space directional constraint) - then we must constraint about the relative basebone constraint UV...
608 | baselineUV = this.baseboneConstraintType === LOCAL_ABSOLUTE ? this.baseboneRelativeConstraintUV : this.baseboneConstraintUV;
609 | directionUV.constrainedUV( baselineUV, bone.joint.min, bone.joint.max );
610 |
611 | //directionUV = bone.getDirectionUV();
612 |
613 | // At this stage we have an inner-to-outer unit vector for this bone which is within our constraints,
614 | // so we can set the new end location to be the start location of this bone plus the constrained
615 | // inner-to-outer direction unit vector multiplied by the length of the bone.
616 | endPosition = bone.start.plus( directionUV.multiplyScalar( boneLength ) );
617 |
618 | // Set the new end joint location for this bone
619 | bone.setEndLocation( endPosition );
620 |
621 | // If we are not working on the end bone, then we set the start joint location of
622 | // the next bone in the chain (i.e. the bone closer to the end effector) to be the
623 | // new end joint location of this bone.
624 | if ( i < (this.numBones - 1) ) { this.bones[i+1].setStartLocation( endPosition ); }
625 |
626 |
627 | } // End of basebone constraint enforcement section
628 |
629 | } // End of base bone handling section
630 |
631 | } // End of backward-pass loop over all bones
632 |
633 | // Update our last target location
634 | this.lastTargetLocation.copy( target );
635 |
636 | // ...and calculate and return the distance between the current effector location and the target.
637 | return this.bones[this.numBones-1].end.distanceTo( target );
638 |
639 | }
640 |
641 | updateChainLength() {
642 |
643 | // Loop over all the bones in the chain, adding the length of each bone to the mChainLength property
644 | this.chainLength = 0;
645 | let i = this.numBones;
646 | while(i--) this.chainLength += this.bones[i].length;
647 |
648 | }
649 |
650 | cloneBones() {
651 |
652 | // Use clone to create a new Bone with the values from the source Bone.
653 | let chain = [];
654 | for ( let i = 0, n = this.bones.length; i < n; i++ ) chain.push( this.bones[i].clone() );
655 | return chain;
656 |
657 | }
658 |
659 | }
--------------------------------------------------------------------------------
/src/core/Chain3D.js:
--------------------------------------------------------------------------------
1 | import { NONE, GLOBAL_ROTOR, GLOBAL_HINGE, LOCAL_ROTOR, LOCAL_HINGE, J_BALL, J_GLOBAL, J_LOCAL, END, START, MAX_VALUE, PRECISION, PRECISION_DEG } from '../constants.js';
2 | import { math } from '../math/Math.js';
3 | import { V3 } from '../math/V3.js';
4 | import { M3 } from '../math/M3.js';
5 | import { Bone3D } from './Bone3D.js';
6 | import { Tools } from './Tools.js';
7 |
8 |
9 | export class Chain3D {
10 |
11 | constructor( color ) {
12 |
13 | this.isChain3D = true
14 |
15 | this.tmpTarget = new V3();
16 | this.tmpMtx = new M3();
17 |
18 | this.bones = [];
19 | this.name = '';
20 | this.color = color || 0xFFFFFF;
21 |
22 | this.solveDistanceThreshold = 1.0;
23 | this.minIterationChange = 0.01;
24 | this.maxIteration = 20;
25 | this.precision = 0.001;
26 |
27 | this.chainLength = 0;
28 | this.numBones = 0;
29 |
30 | this.baseLocation = new V3();
31 | this.fixedBaseMode = true;
32 |
33 | this.baseboneConstraintType = NONE;
34 |
35 | this.baseboneConstraintUV = new V3();
36 | this.baseboneRelativeConstraintUV = new V3();
37 | this.baseboneRelativeReferenceConstraintUV = new V3();
38 | this.lastTargetLocation = new V3( MAX_VALUE, MAX_VALUE, MAX_VALUE );
39 |
40 | this.lastBaseLocation = new V3( MAX_VALUE, MAX_VALUE, MAX_VALUE );
41 | this.currentSolveDistance = MAX_VALUE;
42 |
43 | this.connectedChainNumber = -1;
44 | this.connectedBoneNumber = -1;
45 | this.boneConnectionPoint = END;
46 |
47 | // test full restrict angle
48 | this.isFullForward = false;
49 |
50 |
51 |
52 | this.embeddedTarget = new V3();
53 | this.useEmbeddedTarget = false;
54 |
55 | }
56 |
57 | clone() {
58 |
59 | let c = new this.constructor();
60 |
61 | c.solveDistanceThreshold = this.solveDistanceThreshold;
62 | c.minIterationChange = this.minIterationChange;
63 | c.maxIteration = this.maxIteration;
64 | c.precision = this.precision
65 |
66 | c.bones = this.cloneBones();
67 | c.baseLocation.copy( this.baseLocation );
68 | c.lastTargetLocation.copy( this.lastTargetLocation );
69 | c.lastBaseLocation.copy( this.lastBaseLocation );
70 |
71 | // Copy the basebone constraint UV if there is one to copy
72 | if ( !(this.baseboneConstraintType === NONE) ){
73 | c.baseboneConstraintUV.copy( this.baseboneConstraintUV );
74 | c.baseboneRelativeConstraintUV.copy( this.baseboneRelativeConstraintUV );
75 | }
76 |
77 | // Native copy by value for primitive members
78 | c.fixedBaseMode = this.fixedBaseMode;
79 |
80 | c.chainLength = this.chainLength;
81 | c.numBones = this.numBones;
82 | c.currentSolveDistance = this.currentSolveDistance;
83 |
84 | c.boneConnectionPoint = this.boneConnectionPoint;
85 | c.connectedChainNumber = this.connectedChainNumber;
86 | c.connectedBoneNumber = this.connectedBoneNumber;
87 | c.baseboneConstraintType = this.baseboneConstraintType;
88 |
89 | c.color = this.color;
90 |
91 | c.embeddedTarget = this.embeddedTarget.clone();
92 | c.useEmbeddedTarget = this.useEmbeddedTarget;
93 |
94 | return c;
95 |
96 | }
97 |
98 | clear() {
99 |
100 | let i = this.numBones;
101 | while(i--) this.removeBone(i);
102 | this.numBones = 0;
103 |
104 | }
105 |
106 | addBone( bone ) {
107 |
108 | bone.setColor( this.color );
109 |
110 | // Add the new bone to the end of the ArrayList of bones
111 | this.bones.push( bone );
112 | // Increment the number of bones in the chain and update the chain length
113 | this.numBones ++;
114 |
115 | // If this is the basebone...
116 | if ( this.numBones === 1 ){
117 | // ...then keep a copy of the fixed start location...
118 | this.baseLocation.copy( bone.start );
119 |
120 | // ...and set the basebone constraint UV to be around the initial bone direction
121 | this.baseboneConstraintUV.copy( bone.getDirectionUV() );
122 | }
123 |
124 | // Increment the number of bones in the chain and update the chain length
125 | this.updateChainLength();
126 |
127 | }
128 |
129 | removeBone( id ) {
130 |
131 | if ( id < this.numBones ){
132 | // ...then remove the bone, decrease the bone count and update the chain length.
133 | this.bones.splice(id, 1)
134 | this.numBones --;
135 | this.updateChainLength();
136 |
137 | }
138 |
139 | }
140 |
141 | addConsecutiveBone( directionUV, length ) {
142 |
143 | if (this.numBones > 0) {
144 | // Get the end location of the last bone, which will be used as the start location of the new bone
145 | // Add a bone to the end of this IK chain
146 | // Note: We use a normalised version of the bone direction
147 | this.addBone( new Bone3D( this.bones[ this.numBones-1 ].end, undefined, directionUV.normalised(), length ) );
148 | }
149 |
150 | }
151 |
152 | addConsecutiveFreelyRotatingHingedBone( directionUV, length, type, hingeRotationAxis ) {
153 |
154 | this.addConsecutiveHingedBone( directionUV, length, type, hingeRotationAxis, 180, 180, math.genPerpendicularVectorQuick( hingeRotationAxis ) );
155 |
156 | }
157 |
158 | addConsecutiveHingedBone( DirectionUV, length, type, HingeRotationAxis, clockwiseDegs, anticlockwiseDegs, hingeReferenceAxis ) {
159 |
160 | // Cannot add a consectuive bone of any kind if the there is no basebone
161 | if ( this.numBones === 0 ) return;
162 |
163 | // Normalise the direction and hinge rotation axis
164 | let directionUV = DirectionUV.normalised();
165 | let hingeRotationAxis = HingeRotationAxis.normalised();
166 |
167 | // Create a bone, get the end location of the last bone, which will be used as the start location of the new bone
168 | let bone = new Bone3D( this.bones[ this.numBones-1 ].end, undefined, directionUV, length, this.color );
169 |
170 | type = type || 'global';
171 |
172 | // ...set up a joint which we'll apply to that bone.
173 | bone.joint.setHinge( type === 'global' ? J_GLOBAL : J_LOCAL, hingeRotationAxis, clockwiseDegs, anticlockwiseDegs, hingeReferenceAxis );
174 |
175 | // Finally, add the bone to this chain
176 | this.addBone( bone );
177 |
178 | }
179 |
180 | addConsecutiveRotorConstrainedBone( boneDirectionUV, length, constraintAngleDegs ) {
181 |
182 | if (this.numBones === 0) return;
183 |
184 | // Create the bone starting at the end of the previous bone, set its direction, constraint angle and colour
185 | // then add it to the chain. Note: The default joint type of a new Bone is J_BALL.
186 | boneDirectionUV = boneDirectionUV.normalised();
187 | let bone = new Bone3D( this.bones[ this.numBones-1 ].end, undefined , boneDirectionUV, length );
188 | bone.joint.setAsBallJoint( constraintAngleDegs );
189 | this.addBone( bone );
190 |
191 | }
192 |
193 | // -------------------------------
194 | // GET
195 | // -------------------------------
196 |
197 | getBoneConnectionPoint() {
198 |
199 | return this.boneConnectionPoint;
200 |
201 | }
202 |
203 | getConnectedBoneNumber(){
204 |
205 | return this.connectedBoneNumber;
206 |
207 | }
208 |
209 | getConnectedChainNumber(){
210 |
211 | return this.connectedChainNumber;
212 |
213 | }
214 |
215 | getBaseboneConstraintType(){
216 |
217 | return this.baseboneConstraintType;
218 |
219 | }
220 |
221 | getBaseboneConstraintUV(){
222 |
223 | if ( !(this.baseboneConstraintType === NONE) ) return this.baseboneConstraintUV;
224 |
225 | }
226 |
227 | getBaseLocation() {
228 |
229 | return this.bones[0].start;
230 |
231 | }
232 |
233 | getEffectorLocation() {
234 |
235 | return this.bones[this.numBones-1].end;
236 |
237 | }
238 |
239 | getLastTargetLocation() {
240 |
241 | return this.lastTargetLocation;
242 |
243 | }
244 |
245 | getLiveChainLength() {
246 |
247 | let lng = 0;
248 | let i = this.numBones;
249 | while( i-- ) lng += this.bones[i].getLength();
250 | return lng;
251 |
252 | }
253 |
254 |
255 | getBaseboneRelativeReferenceConstraintUV() {
256 |
257 | return this.baseboneRelativeReferenceConstraintUV;
258 |
259 | }
260 |
261 | // -------------------------------
262 | // SET
263 | // -------------------------------
264 |
265 | setConnectedBoneNumber( boneNumber ) {
266 |
267 | this.connectedBoneNumber = boneNumber;
268 |
269 | }
270 |
271 | setConnectedChainNumber( chainNumber ) {
272 |
273 | this.connectedChainNumber = chainNumber;
274 |
275 | }
276 |
277 | setBoneConnectionPoint( point ) {
278 |
279 | this.boneConnectionPoint = point;
280 |
281 | }
282 |
283 | setColor( c ) {
284 |
285 | this.color = c;
286 | let i = this.numBones;
287 | while( i-- ) this.bones[i].setColor( this.color );
288 |
289 | }
290 |
291 | setBaseboneRelativeConstraintUV( uv ) {
292 |
293 | this.baseboneRelativeConstraintUV = uv.normalised();
294 |
295 | }
296 |
297 | setBaseboneRelativeReferenceConstraintUV( uv ) {
298 |
299 | this.baseboneRelativeReferenceConstraintUV = uv.normalised();
300 |
301 | }
302 |
303 | setBaseboneConstraintUV( uv ) {
304 |
305 | this.baseboneConstraintUV = uv.normalised();
306 |
307 | }
308 |
309 | setRotorBaseboneConstraint( type, constraintAxis, angleDegs ) {
310 |
311 | // Sanity checking
312 | if (this.numBones === 0){ Tools.error("Chain must contain a basebone before we can specify the basebone constraint type."); return; }
313 | if ( !(constraintAxis.length() > 0) ){ Tools.error("Constraint axis cannot be zero."); return;}
314 |
315 | type = type || 'global';
316 | // Set the constraint type, axis and angle
317 | this.baseboneConstraintType = type === 'global' ? GLOBAL_ROTOR : LOCAL_ROTOR;
318 | this.baseboneConstraintUV = constraintAxis.normalised();
319 | this.baseboneRelativeConstraintUV.copy( this.baseboneConstraintUV );
320 | this.bones[0].joint.setAsBallJoint( angleDegs );
321 |
322 | }
323 |
324 | setHingeBaseboneConstraint( type, hingeRotationAxis, cwDegs, acwDegs, hingeReferenceAxis ) {
325 |
326 | // Sanity checking
327 | if ( this.numBones === 0){ Tools.error("Chain must contain a basebone before we can specify the basebone constraint type."); return; }
328 | if ( hingeRotationAxis.length() <= 0 ){ Tools.error("Hinge rotation axis cannot be zero."); return; }
329 | if ( hingeReferenceAxis.length() <= 0 ){ Tools.error("Hinge reference axis cannot be zero."); return; }
330 | if ( !( math.perpendicular( hingeRotationAxis, hingeReferenceAxis ) ) ){ Tools.error("The hinge reference axis must be in the plane of the hinge rotation axis, that is, they must be perpendicular."); return; }
331 |
332 | type = type || 'global';
333 |
334 | // Set the constraint type, axis and angle
335 | this.baseboneConstraintType = type === 'global' ? GLOBAL_HINGE : LOCAL_HINGE;
336 | this.baseboneConstraintUV = hingeRotationAxis.normalised();
337 | this.bones[0].joint.setHinge( type === 'global' ? J_GLOBAL : J_LOCAL, hingeRotationAxis, cwDegs, acwDegs, hingeReferenceAxis );
338 |
339 | }
340 |
341 | setFreelyRotatingGlobalHingedBasebone( hingeRotationAxis ) {
342 |
343 | this.setHingeBaseboneConstraint( 'global', hingeRotationAxis, 180, 180, math.genPerpendicularVectorQuick( hingeRotationAxis ) );
344 | }
345 |
346 | setGlobalHingedBasebone( hingeRotationAxis, cwDegs, acwDegs, hingeReferenceAxis ) {
347 |
348 | this.setHingeBaseboneConstraint( 'global', hingeRotationAxis, cwDegs, acwDegs, hingeReferenceAxis );
349 |
350 | }
351 |
352 | setFreelyRotatingLocalHingedBasebone( hingeRotationAxis ) {
353 |
354 | this.setHingeBaseboneConstraint( 'local', hingeRotationAxis, 180, 180, math.genPerpendicularVectorQuick( hingeRotationAxis ) );
355 |
356 | }
357 |
358 | setLocalHingedBasebone( hingeRotationAxis, cwDegs, acwDegs, hingeReferenceAxis ) {
359 |
360 | this.setHingeBaseboneConstraint( 'local', hingeRotationAxis, cwDegs, acwDegs, hingeReferenceAxis );
361 |
362 | }
363 |
364 | setBaseLocation( baseLocation ) {
365 |
366 | this.baseLocation.copy( baseLocation );
367 |
368 | }
369 |
370 | setFixedBaseMode( value ){
371 |
372 | // Enforce that a chain connected to another chain stays in fixed base mode (i.e. it moves with the chain it's connected to instead of independently)
373 | if ( !value && this.connectedChainNumber !== -1) return;
374 | if ( this.baseboneConstraintType === GLOBAL_ROTOR && !value ) return;
375 | // Above conditions met? Set the fixedBaseMode
376 | this.fixedBaseMode = value;
377 |
378 | }
379 |
380 | setMaxIterationAttempts( maxIterations ) {
381 |
382 | if (maxIterations < 1) return;
383 | this.maxIteration = maxIterations;
384 |
385 | }
386 |
387 | setMinIterationChange( minIterationChange ) {
388 |
389 | if (minIterationChange < 0) return;
390 | this.minIterationChange = minIterationChange;
391 |
392 | }
393 |
394 | setSolveDistanceThreshold( solveDistance ) {
395 |
396 | if (solveDistance < 0) return;
397 | this.solveDistanceThreshold = solveDistance;
398 |
399 | }
400 |
401 | // -------------------------------
402 | //
403 | // UPDATE TARGET
404 | //
405 | // -------------------------------
406 |
407 | solveForEmbeddedTarget() {
408 |
409 | if ( this.useEmbeddedTarget ) return this.solveForTarget( this.embeddedTarget );
410 |
411 | }
412 |
413 | resetTarget() {
414 |
415 | this.lastBaseLocation = new V3( MAX_VALUE, MAX_VALUE, MAX_VALUE );
416 | this.currentSolveDistance = MAX_VALUE;
417 |
418 | }
419 |
420 |
421 | // Method to solve this IK chain for the given target location.
422 | // The end result of running this method is that the IK chain configuration is updated.
423 |
424 | // To minimuse CPU usage, this method dynamically aborts if:
425 | // - The solve distance (i.e. distance between the end effector and the target) is below the solveDistanceThreshold,
426 | // - A solution incrementally improves on the previous solution by less than the minIterationChange, or
427 | // - The number of attempts to solve the IK chain exceeds the maxIteration.
428 |
429 | solveForTarget( t ) {
430 |
431 | this.tmpTarget.set( t.x, t.y, t.z );
432 | let p = this.precision;
433 |
434 | let isSameBaseLocation = this.lastBaseLocation.approximatelyEquals( this.baseLocation, p );
435 |
436 | // If we have both the same target and base location as the last run then do not solve
437 | if ( this.lastTargetLocation.approximatelyEquals( this.tmpTarget, p ) && isSameBaseLocation ) return this.currentSolveDistance;
438 |
439 | // Keep starting solutions and distance
440 | let startingDistance;
441 | let startingSolution = null;
442 |
443 | // If the base location of a chain hasn't moved then we may opt to keep the current solution if our
444 | // best new solution is worse...
445 | if ( isSameBaseLocation ) {
446 | startingDistance = this.bones[ this.numBones-1 ].end.distanceTo( this.tmpTarget );
447 | startingSolution = this.cloneBones();
448 | } else {
449 | // Base has changed? Then we have little choice but to recalc the solution and take that new solution.
450 | startingDistance = MAX_VALUE;
451 | }
452 |
453 | /*
454 | * NOTE: We must allow the best solution of THIS run to be used for a new target or base location - we cannot
455 | * just use the last solution (even if it's better) - because that solution was for a different target / base
456 | * location combination and NOT for the current setup.
457 | */
458 |
459 | // Not the same target? Then we must solve the chain for the new target.
460 | // We'll start by creating a list of bones to store our best solution
461 | let bestSolution = [];
462 |
463 | // We'll keep track of our best solve distance, starting it at a huge value which will be beaten on first attempt
464 | let bestSolveDistance = MAX_VALUE;
465 | let lastPassSolveDistance = MAX_VALUE;
466 |
467 | // Allow up to our iteration limit attempts at solving the chain
468 | let solveDistance;
469 |
470 | let i = this.maxIteration;
471 |
472 | while( i-- ){
473 |
474 | // Solve the chain for this target
475 | solveDistance = this.solveIK( this.tmpTarget );
476 |
477 | // Did we solve it for distance? If so, update our best distance and best solution, and also
478 | // update our last pass solve distance. Note: We will ALWAYS beat our last solve distance on the first run.
479 | if ( solveDistance < bestSolveDistance ) {
480 |
481 | bestSolveDistance = solveDistance;
482 | bestSolution = this.cloneBones();
483 |
484 | // If we are happy that this solution meets our distance requirements then we can exit the loop now
485 | if ( solveDistance <= this.solveDistanceThreshold ) break;
486 |
487 | } else {// Did not solve to our satisfaction? Okay...
488 |
489 | // Did we grind to a halt? If so break out of loop to set the best distance and solution that we have
490 | if ( Math.abs( solveDistance - lastPassSolveDistance ) < this.minIterationChange ) break;
491 |
492 | }
493 |
494 | // Update the last pass solve distance
495 | lastPassSolveDistance = solveDistance;
496 |
497 | } // End of loop
498 |
499 | // Did we get a solution that's better than the starting solution's to the new target location?
500 | if ( bestSolveDistance < startingDistance ){
501 | // If so, set the newly found solve distance and solution as the best found.
502 | this.currentSolveDistance = bestSolveDistance;
503 | this.bones = bestSolution;
504 | } else {
505 | // Did we make things worse? Then we keep our starting distance and solution!
506 | this.currentSolveDistance = startingDistance;
507 | this.bones = startingSolution;
508 | }
509 |
510 | // Update our base and target locations
511 | this.lastBaseLocation.copy( this.baseLocation );
512 | this.lastTargetLocation.copy( this.tmpTarget );
513 |
514 | return this.currentSolveDistance;
515 |
516 | }
517 |
518 | // -------------------------------
519 | //
520 | // SOLVE IK
521 | //
522 | // -------------------------------
523 |
524 | // Solve the IK chain for the given target using the FABRIK algorithm.
525 | // retun the best solve distance found between the end-effector of this chain and the provided target.
526 |
527 | solveIK( target ) {
528 |
529 | if ( this.numBones === 0 ) return;
530 |
531 | let bone, boneLength, joint, jointType, nextBone;
532 | let hingeRotationAxis, hingeReferenceAxis;
533 | let tmpMtx = this.tmpMtx;
534 |
535 | // ---------- Forward pass from end effector to base -----------
536 |
537 | // Loop over all bones in the chain, from the end effector (numBones-1) back to the basebone (0)
538 | let i = this.numBones;
539 |
540 | while( i-- ){
541 |
542 |
543 | // Get the length of the bone we're working on
544 | bone = this.bones[i];
545 | boneLength = bone.length;
546 | joint = bone.joint;
547 | jointType = joint.type;
548 |
549 | // If we are NOT working on the end effector bone
550 | if ( i !== this.numBones - 1 ) {
551 |
552 | nextBone = this.bones[i+1];
553 |
554 | // Get the outer-to-inner unit vector of the bone further out
555 | let outerBoneOuterToInnerUV = nextBone.getDirectionUV().negate();
556 |
557 | // Get the outer-to-inner unit vector of this bone
558 | let boneOuterToInnerUV = bone.getDirectionUV().negate();
559 |
560 | // Get the joint type for this bone and handle constraints on boneInnerToOuterUV
561 |
562 | /*if( this.isFullForward ){
563 |
564 | switch ( jointType ) {
565 | case J_BALL:
566 | // Constrain to relative angle between this bone and the next bone if required
567 | boneOuterToInnerUV.limitAngle( outerBoneOuterToInnerUV, tmpMtx, nextBone.joint.rotor );
568 | break;
569 | case J_GLOBAL:
570 |
571 | hingeRotationAxis = nextBone.joint.getHingeRotationAxis().negated();
572 | hingeReferenceAxis = nextBone.joint.getHingeReferenceAxis().negated();
573 |
574 | // Project this bone outer-to-inner direction onto the hinge rotation axis
575 | boneOuterToInnerUV.projectOnPlane( hingeRotationAxis );
576 |
577 | // NOTE: Constraining about the hinge reference axis on this forward pass leads to poor solutions... so we won't.
578 | if( !nextBone.joint.freeHinge ) boneOuterToInnerUV.constrainedUV( hingeReferenceAxis, hingeRotationAxis, tmpMtx, nextBone.joint.min, nextBone.joint.max );
579 |
580 |
581 | break;
582 | case J_LOCAL:
583 |
584 |
585 | if ( i > 0 ) {// Not a basebone? Then construct a rotation matrix based on the previous bones inner-to-to-inner direction...
586 | // ...and transform the hinge rotation axis into the previous bones frame of reference.
587 |
588 | tmpMtx.createRotationMatrix( outerBoneOuterToInnerUV );
589 | hingeRotationAxis = nextBone.joint.getHingeRotationAxis().clone().negate().applyM3( tmpMtx );
590 | hingeReferenceAxis = nextBone.joint.getHingeReferenceAxis().clone().negate().applyM3( tmpMtx );
591 |
592 |
593 |
594 | } else {// ...basebone? Need to construct matrix from the relative constraint UV.
595 |
596 | hingeRotationAxis = this.baseboneRelativeConstraintUV.negated();
597 | hingeReferenceAxis = this.baseboneRelativeReferenceConstraintUV.negated();
598 |
599 | }
600 |
601 | // Project this bone's outer-to-inner direction onto the plane described by the relative hinge rotation axis
602 | boneOuterToInnerUV.projectOnPlane( hingeRotationAxis );
603 |
604 | // NOTE: Constraining about the hinge reference axis on this forward pass leads to poor solutions... so we won't.
605 | if( !nextBone.joint.freeHinge ){
606 |
607 | boneOuterToInnerUV.constrainedUV( hingeReferenceAxis, hingeRotationAxis, tmpMtx, nextBone.joint.min, nextBone.joint.max );
608 |
609 | }
610 | break;
611 | }
612 | } else {*/
613 |
614 | switch ( jointType ) {
615 | case J_BALL:
616 | // Constrain to relative angle between this bone and the next bone if required
617 | boneOuterToInnerUV.limitAngle( outerBoneOuterToInnerUV, tmpMtx, joint.rotor );
618 | break;
619 | case J_GLOBAL:
620 |
621 | hingeRotationAxis = joint.getHingeRotationAxis();
622 |
623 | // Project this bone outer-to-inner direction onto the hinge rotation axis
624 | boneOuterToInnerUV.projectOnPlane( hingeRotationAxis );
625 |
626 | // NOTE: Constraining about the hinge reference axis on this forward pass leads to poor solutions... so we won't.
627 | break;
628 | case J_LOCAL:
629 |
630 |
631 | if ( i > 0 ) {// Not a basebone? Then construct a rotation matrix based on the previous bones inner-to-to-inner direction...
632 | // ...and transform the hinge rotation axis into the previous bones frame of reference.
633 |
634 | tmpMtx.createRotationMatrix( this.bones[i-1].getDirectionUV() );
635 | hingeRotationAxis = joint.getHingeRotationAxis().clone().applyM3( tmpMtx );
636 |
637 | } else {// ...basebone? Need to construct matrix from the relative constraint UV.
638 |
639 | hingeRotationAxis = this.baseboneRelativeConstraintUV;
640 |
641 | }
642 |
643 | // Project this bone's outer-to-inner direction onto the plane described by the relative hinge rotation axis
644 | boneOuterToInnerUV.projectOnPlane( hingeRotationAxis );
645 |
646 | // NOTE: Constraining about the hinge reference axis on this forward pass leads to poor solutions... so we won't.
647 | break;
648 | }
649 | //}
650 |
651 | // At this stage we have a outer-to-inner unit vector for this bone which is within our constraints,
652 | // so we can set the new inner joint location to be the end joint location of this bone plus the
653 | // outer-to-inner direction unit vector multiplied by the length of the bone.
654 | let newStartLocation = bone.end.plus( boneOuterToInnerUV.multiplyScalar( boneLength ) );
655 |
656 | // Set the new start joint location for this bone
657 | bone.setStartLocation( newStartLocation );
658 |
659 | // If we are not working on the basebone, then we also set the end joint location of
660 | // the previous bone in the chain (i.e. the bone closer to the base) to be the new
661 | // start joint location of this bone.
662 | if (i > 0) this.bones[i-1].setEndLocation( newStartLocation );
663 |
664 | } else { // If we ARE working on the end effector bone...
665 |
666 | // Snap the end effector's end location to the target
667 | bone.setEndLocation( target );
668 |
669 | // Get the UV between the target / end-location (which are now the same) and the start location of this bone
670 | let boneOuterToInnerUV = bone.getDirectionUV().negated();
671 |
672 | // If the end effector is global hinged then we have to snap to it, then keep that
673 | // resulting outer-to-inner UV in the plane of the hinge rotation axis
674 | switch ( jointType ) {
675 | case J_BALL:
676 | // Ball joints do not get constrained on this forward pass
677 | break;
678 | case J_GLOBAL:
679 | hingeRotationAxis = joint.getHingeRotationAxis();
680 | // Global hinges get constrained to the hinge rotation axis, but not the reference axis within the hinge plane
681 | boneOuterToInnerUV.projectOnPlane( hingeRotationAxis );
682 | break;
683 | case J_LOCAL:
684 | // Local hinges get constrained to the hinge rotation axis, but not the reference axis within the hinge plane
685 |
686 | // Construct a rotation matrix based on the previous bones inner-to-to-inner direction...
687 | tmpMtx.createRotationMatrix( this.bones[i-1].getDirectionUV() );
688 |
689 | // ...and transform the hinge rotation axis into the previous bones frame of reference.
690 | hingeRotationAxis = joint.getHingeRotationAxis().clone().applyM3( tmpMtx );
691 |
692 | // Project this bone's outer-to-inner direction onto the plane described by the relative hinge rotation axis
693 | boneOuterToInnerUV.projectOnPlane( hingeRotationAxis );
694 | break;
695 | }
696 |
697 | // Calculate the new start joint location as the end joint location plus the outer-to-inner direction UV
698 | // multiplied by the length of the bone.
699 | let newStartLocation = target.plus( boneOuterToInnerUV.multiplyScalar( boneLength ) );
700 |
701 | // Set the new start joint location for this bone to be new start location...
702 | bone.setStartLocation( newStartLocation );
703 |
704 | // ...and set the end joint location of the bone further in to also be at the new start location (if there IS a bone
705 | // further in - this may be a single bone chain)
706 | if (i > 0) this.bones[i-1].setEndLocation( newStartLocation );
707 |
708 | }
709 |
710 | } // End of forward pass
711 |
712 | // ---------- Backward pass from base to end effector -----------
713 |
714 | for ( i = 0; i < this.numBones; i++ ){
715 |
716 | bone = this.bones[i];
717 | boneLength = bone.length;
718 | joint = bone.joint;
719 | jointType = joint.type;
720 |
721 | // If we are not working on the basebone
722 | if ( i !== 0 ){
723 |
724 | // Get the inner-to-outer direction of this bone as well as the previous bone to use as a baseline
725 | let boneInnerToOuterUV = bone.getDirectionUV();
726 |
727 | let prevBoneInnerToOuterUV = this.bones[i-1].getDirectionUV();
728 |
729 | //let hingeRotationAxis, hingeReferenceAxis;
730 |
731 | switch ( jointType ) {
732 |
733 | case J_BALL:
734 |
735 | // Keep this bone direction constrained within the rotor about the previous bone direction
736 | boneInnerToOuterUV.limitAngle( prevBoneInnerToOuterUV, tmpMtx, joint.rotor );
737 |
738 | break;
739 | case J_GLOBAL:
740 |
741 | // Get the hinge rotation axis and project our inner-to-outer UV onto it
742 | hingeRotationAxis = joint.getHingeRotationAxis();
743 | hingeReferenceAxis = joint.getHingeReferenceAxis();
744 |
745 | boneInnerToOuterUV.projectOnPlane( hingeRotationAxis );
746 |
747 | // Constrain rotation about reference axis if required
748 | if( !joint.freeHinge ) boneInnerToOuterUV.constrainedUV( hingeReferenceAxis, hingeRotationAxis, tmpMtx, joint.min, joint.max );
749 |
750 | break;
751 | case J_LOCAL:
752 |
753 | // Transform the hinge rotation axis to be relative to the previous bone in the chain
754 | // Construct a rotation matrix based on the previous bone's direction
755 | tmpMtx.createRotationMatrix( prevBoneInnerToOuterUV );
756 |
757 | // Transform the hinge rotation axis into the previous bone's frame of reference
758 | hingeRotationAxis = joint.getHingeRotationAxis().clone().applyM3( tmpMtx );
759 | hingeReferenceAxis = joint.getHingeReferenceAxis().clone().applyM3( tmpMtx );
760 |
761 |
762 | // Project this bone direction onto the plane described by the hinge rotation axis
763 | // Note: The returned vector is normalised.
764 | boneInnerToOuterUV.projectOnPlane( hingeRotationAxis );
765 |
766 | // Constrain rotation about reference axis if required
767 | if( !joint.freeHinge ) boneInnerToOuterUV.constrainedUV( hingeReferenceAxis, hingeRotationAxis, tmpMtx, joint.min, joint.max );
768 |
769 | break;
770 |
771 | }
772 |
773 | // At this stage we have a outer-to-inner unit vector for this bone which is within our constraints,
774 | // so we can set the new inner joint location to be the end joint location of this bone plus the
775 | // outer-to-inner direction unit vector multiplied by the length of the bone.
776 | let newEndLocation = bone.start.plus( boneInnerToOuterUV.multiplyScalar( boneLength ) );
777 |
778 | // Set the new start joint location for this bone
779 | bone.setEndLocation( newEndLocation );
780 |
781 | // If we are not working on the end effector bone, then we set the start joint location of the next bone in
782 | // the chain (i.e. the bone closer to the target) to be the new end joint location of this bone.
783 | if (i < (this.numBones - 1)) { this.bones[i+1].setStartLocation( newEndLocation ); }
784 |
785 | } else { // If we ARE working on the basebone...
786 |
787 | // If the base location is fixed then snap the start location of the basebone back to the fixed base...
788 | if ( this.fixedBaseMode ){
789 |
790 | bone.setStartLocation( this.baseLocation );
791 |
792 | } else { // ...otherwise project it backwards from the end to the start by its length.
793 |
794 | bone.setStartLocation( bone.end.minus( bone.getDirectionUV().multiplyScalar( boneLength ) ) );
795 |
796 | }
797 |
798 | // Get the inner-to-outer direction of this bone
799 | let boneInnerToOuterUV = bone.getDirectionUV();
800 |
801 | let hingeRotationAxis, hingeReferenceAxis;
802 |
803 | switch ( this.baseboneConstraintType ){
804 |
805 | case NONE: // Nothing to do because there's no basebone constraint
806 | break;
807 | case GLOBAL_ROTOR:
808 |
809 | boneInnerToOuterUV.limitAngle( this.baseboneConstraintUV, tmpMtx, joint.rotor );
810 |
811 | break;
812 | case LOCAL_ROTOR:
813 |
814 | boneInnerToOuterUV.limitAngle( this.baseboneRelativeConstraintUV, tmpMtx, joint.rotor );
815 |
816 | break;
817 | case GLOBAL_HINGE:
818 |
819 | hingeRotationAxis = joint.getHingeRotationAxis();
820 | hingeReferenceAxis = joint.getHingeReferenceAxis();
821 |
822 | // Get the inner-to-outer direction of this bone and project it onto the global hinge rotation axis
823 | boneInnerToOuterUV.projectOnPlane( hingeRotationAxis );
824 |
825 | // Constrain as necessary
826 | if( !joint.freeHinge ) boneInnerToOuterUV.constrainedUV( hingeReferenceAxis, hingeRotationAxis, tmpMtx, joint.min, joint.max );
827 |
828 | break;
829 | case LOCAL_HINGE:
830 |
831 | hingeRotationAxis = this.baseboneRelativeConstraintUV;
832 | hingeReferenceAxis = this.baseboneRelativeReferenceConstraintUV;
833 |
834 | // Get the inner-to-outer direction of this bone and project it onto the global hinge rotation axis
835 | //let boneInnerToOuterUV = bone.getDirectionUV();
836 | boneInnerToOuterUV.projectOnPlane( hingeRotationAxis );
837 |
838 | // Constrain as necessary
839 | if( !joint.freeHinge ) boneInnerToOuterUV.constrainedUV( hingeReferenceAxis, hingeRotationAxis, tmpMtx, joint.min, joint.max );
840 |
841 | break;
842 |
843 | }
844 |
845 |
846 | // Set the new end location of this bone, and if there are more bones,
847 | // then set the start location of the next bone to be the end location of this bone
848 | let newEndLocation = bone.start.plus( boneInnerToOuterUV.multiplyScalar( boneLength ) );
849 | bone.setEndLocation( newEndLocation );
850 |
851 | if ( this.numBones > 1 ) { this.bones[1].setStartLocation( newEndLocation ); }
852 |
853 | } // End of basebone handling section
854 |
855 | } // End of backward-pass i over all bones
856 |
857 | // Update our last target location
858 | this.lastTargetLocation.copy( target );
859 |
860 | // DEBUG - check the live chain length and the originally calculated chain length are the same
861 | // if (Math.abs( this.getLiveChainLength() - chainLength) > 0.01) Tools.error(""Chain length off by > 0.01");
862 |
863 | // Finally, calculate and return the distance between the current effector location and the target.
864 | //return math.distanceBetween( this.bones[this.numBones-1].end, target );
865 | return this.bones[this.numBones-1].end.distanceTo( target );
866 |
867 | }
868 |
869 | updateChainLength() {
870 |
871 | // Loop over all the bones in the chain, adding the length of each bone to the chainLength property
872 | this.chainLength = 0;
873 | let i = this.numBones;
874 | while(i--) this.chainLength += this.bones[i].length;
875 |
876 | }
877 |
878 | cloneBones() {
879 |
880 | // Use clone to create a new Bone with the values from the source Bone.
881 | let chain = [];
882 | for ( let i = 0, n = this.bones.length; i < n; i++ ) chain.push( this.bones[i].clone() );
883 | return chain;
884 |
885 | }
886 |
887 | }
--------------------------------------------------------------------------------
/src/core/Joint2D.js:
--------------------------------------------------------------------------------
1 | import { math } from '../math/Math.js';
2 | import { J_LOCAL, J_GLOBAL } from '../constants.js';
3 |
4 | export class Joint2D {
5 |
6 | constructor( clockwise, antiClockwise, coordSystem ) {
7 |
8 | this.isJoint2D = true;
9 |
10 | this.coordinateSystem = coordSystem || J_LOCAL;
11 |
12 | if( clockwise < 0 ) clockwise *= -1;
13 |
14 | this.min = clockwise !== undefined ? -clockwise * math.toRad : -math.PI;
15 | this.max = antiClockwise !== undefined ? antiClockwise * math.toRad : math.PI;
16 |
17 | }
18 |
19 | clone() {
20 |
21 | let j = new this.constructor();
22 | j.coordinateSystem = this.coordinateSystem;
23 | j.max = this.max;
24 | j.min = this.min;
25 | return j;
26 |
27 | }
28 |
29 | validateAngle( a ) {
30 |
31 | a = a < 0 ? 0 : a;
32 | a = a > 180 ? 180 : a;
33 | return a;
34 |
35 | }
36 |
37 | // SET
38 |
39 | set( joint ) {
40 |
41 | this.max = joint.max;
42 | this.min = joint.min;
43 | this.coordinateSystem = joint.coordinateSystem;
44 |
45 | }
46 |
47 | setClockwiseConstraintDegs( angle ) {
48 |
49 | // 0 to -180 degrees represents clockwise rotation
50 | if( angle < 0 ) angle *= -1;
51 | this.min = - (this.validateAngle( angle ) * math.toRad);
52 |
53 | }
54 |
55 | setAnticlockwiseConstraintDegs( angle ) {
56 |
57 | // 0 to 180 degrees represents anti-clockwise rotation
58 | this.max = this.validateAngle( angle ) * math.toRad;
59 |
60 | }
61 |
62 | setConstraintCoordinateSystem( coordSystem ) {
63 |
64 | this.coordinateSystem = coordSystem;
65 |
66 | }
67 |
68 |
69 | // GET
70 |
71 | getConstraintCoordinateSystem() {
72 |
73 | return this.coordinateSystem;
74 |
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/src/core/Joint3D.js:
--------------------------------------------------------------------------------
1 | import { V3 } from '../math/V3.js';
2 | import { math } from '../math/Math.js';
3 | import { J_BALL, J_GLOBAL, J_LOCAL } from '../constants.js';
4 |
5 |
6 | export class Joint3D {
7 |
8 | constructor() {
9 |
10 | this.isJoint3D = true;
11 |
12 | this.rotor = math.PI;
13 | this.min = -math.PI;
14 | this.max = math.PI;
15 |
16 | this.freeHinge = true;
17 |
18 | this.rotationAxisUV = new V3();
19 | this.referenceAxisUV = new V3();
20 | this.type = J_BALL;
21 |
22 | }
23 |
24 | clone() {
25 |
26 | let j = new this.constructor();
27 | j.type = this.type;
28 | j.rotor = this.rotor;
29 | j.max = this.max;
30 | j.min = this.min;
31 | j.freeHinge = this.freeHinge;
32 | j.rotationAxisUV.copy( this.rotationAxisUV );
33 | j.referenceAxisUV.copy( this.referenceAxisUV );
34 |
35 | return j
36 |
37 | }
38 |
39 | testAngle() {
40 |
41 | if( this.max === math.PI && this.min === -math.PI ) this.freeHinge = true;
42 | else this.freeHinge = false;
43 |
44 | }
45 |
46 | validateAngle( a ) {
47 |
48 | a = a < 0 ? 0 : a;
49 | a = a > 180 ? 180 : a;
50 | return a;
51 |
52 | }
53 |
54 | setAsBallJoint( angle ) {
55 |
56 | this.rotor = this.validateAngle( angle ) * math.toRad;
57 | this.type = J_BALL;
58 |
59 | }
60 |
61 | // Specify this joint to be a hinge with the provided settings
62 |
63 | setHinge( type, rotationAxis, clockwise, anticlockwise, referenceAxis ) {
64 |
65 | this.type = type;
66 | if( clockwise < 0 ) clockwise *= -1;
67 | this.min = -this.validateAngle( clockwise ) * math.toRad;
68 | this.max = this.validateAngle( anticlockwise ) * math.toRad;
69 |
70 | this.testAngle();
71 |
72 | this.rotationAxisUV.copy( rotationAxis ).normalize();
73 | this.referenceAxisUV.copy( referenceAxis ).normalize();
74 |
75 | }
76 |
77 | // GET
78 |
79 | getHingeReferenceAxis() {
80 |
81 | return this.referenceAxisUV;
82 |
83 | }
84 |
85 | getHingeRotationAxis() {
86 |
87 | return this.rotationAxisUV;
88 |
89 | }
90 |
91 | // SET
92 |
93 | setBallJointConstraintDegs( angle ) {
94 |
95 | this.rotor = this.validateAngle( angle ) * math.toRad;
96 |
97 | }
98 |
99 | setHingeClockwise( angle ) {
100 |
101 | if( angle < 0 ) angle *= -1;
102 | this.min = -this.validateAngle( angle ) * math.toRad;
103 | this.testAngle();
104 |
105 | }
106 |
107 | setHingeAnticlockwise( angle ) {
108 |
109 | this.max = this.validateAngle( angle ) * math.toRad;
110 | this.testAngle();
111 |
112 | }
113 |
114 | /*setHingeRotationAxis: function ( axis ) {
115 |
116 | this.rotationAxisUV.copy( axis ).normalize();
117 |
118 | },
119 |
120 | setHingeReferenceAxis: function ( referenceAxis ) {
121 |
122 | this.referenceAxisUV.copy( referenceAxis ).normalize();
123 |
124 | },*/
125 |
126 |
127 |
128 | }
--------------------------------------------------------------------------------
/src/core/Structure2D.js:
--------------------------------------------------------------------------------
1 | import { UP, NONE, GLOBAL_ABSOLUTE, LOCAL_RELATIVE, LOCAL_ABSOLUTE, END, START } from '../constants.js';
2 | import { math } from '../math/Math.js';
3 | import { V2 } from '../math/V2.js';
4 | import { Tools } from './Tools.js';
5 |
6 | export class Structure2D {
7 |
8 | constructor( scene, THREE ) {
9 |
10 | this.THREE = THREE
11 |
12 | this.isStructure2D = true;
13 |
14 | this.fixedBaseMode = true;
15 |
16 | this.chains = [];
17 | this.meshChains = [];
18 | this.angleChains= [];
19 | this.targets = [];
20 | this.numChains = 0;
21 |
22 | this.scene = scene || null;
23 |
24 | this.isWithMesh = false;
25 |
26 | }
27 |
28 | update() {
29 |
30 | //console.log('up')
31 |
32 | let chain, mesh, bone, target, tmp = new this.THREE.Vector3();
33 | let hostChainNumber;
34 | let hostChain, hostBone, constraintType;
35 | let a, angle;
36 |
37 | for( let i = 0; i < this.numChains; i++ ){
38 |
39 | chain = this.chains[i];
40 |
41 | target = this.targets[i] || null;
42 |
43 | hostChainNumber = chain.getConnectedChainNumber();
44 |
45 | // Get the basebone constraint type of the chain we're working on
46 | constraintType = chain.getBaseboneConstraintType();
47 |
48 | // If this chain is not connected to another chain and the basebone constraint type of this chain is not global absolute
49 | // then we must update the basebone constraint UV for LOCAL_RELATIVE and the basebone relative constraint UV for LOCAL_ABSOLUTE connection types.
50 | // Note: For NONE or GLOBAL_ABSOLUTE we don't need to update anything before calling solveForTarget().
51 | if ( hostChainNumber !== -1 && constraintType !== GLOBAL_ABSOLUTE ) {
52 | // Get the bone which this chain is connected to in the 'host' chain
53 | let hostBone = this.chains[ hostChainNumber ].bones[ chain.getConnectedBoneNumber() ];
54 |
55 | chain.setBaseLocation( chain.getBoneConnectionPoint() === START ? hostBone.start : hostBone.end );
56 |
57 |
58 | // If the basebone is constrained to the direction of the bone it's connected to...
59 | let hostBoneUV = hostBone.getDirectionUV();
60 |
61 | if ( constraintType === LOCAL_RELATIVE ){
62 |
63 | // ...then set the basebone constraint UV to be the direction of the bone we're connected to.
64 | chain.setBaseboneConstraintUV( hostBoneUV );
65 |
66 | } else if ( constraintType === LOCAL_ABSOLUTE ) {
67 |
68 | // Note: LOCAL_ABSOLUTE directions are directions which are in the local coordinate system of the host bone.
69 | // For example, if the baseboneConstraintUV is Vec2f(-1.0f, 0.0f) [i.e. left], then the baseboneConnectionConstraintUV
70 | // will be updated to be left with regard to the host bone.
71 |
72 | // Get the angle between UP and the hostbone direction
73 | angle = UP.getSignedAngle( hostBoneUV );
74 |
75 | // ...then apply that same rotation to this chain's basebone constraint UV to get the relative constraint UV...
76 | let relativeConstraintUV = chain.getBaseboneConstraintUV().clone().rotate( angle );
77 |
78 | // ...which we then update.
79 | chain.setBaseboneRelativeConstraintUV( relativeConstraintUV );
80 |
81 | }
82 |
83 | // NOTE: If the basebone constraint type is NONE then we don't do anything with the basebone constraint of the connected chain.
84 |
85 | } // End of if chain is connected to another chain section
86 |
87 | // Finally, update the target and solve the chain
88 |
89 | if ( !chain.useEmbeddedTarget ) chain.solveForTarget( target );
90 | else console.log('embed', chain.solveForEmbeddedTarget());
91 |
92 |
93 | // update 3d mesh
94 |
95 | if( this.isWithMesh ){
96 |
97 | mesh = this.meshChains[i];
98 | angle = this.angleChains[i];
99 |
100 | for ( let j = 0; j < chain.numBones; j++ ) {
101 |
102 | bone = chain.bones[j];
103 | mesh[j].position.set( bone.start.x, bone.start.y, 0 );
104 | a = math.unwrapRad( -Math.atan2( bone.start.x - bone.end.x, bone.start.y - bone.end.y ));
105 | a = math.unwrapRad( -Math.atan2( bone.end.x - bone.start.x, bone.end.y - bone.start.y ));
106 | mesh[j].rotation.z = a
107 | angle[j] = a * math.toDeg;
108 |
109 | //mesh[j].lookAt( tmp.set( bone.end.x, bone.end.y, 0 ) );
110 | }
111 |
112 | }
113 |
114 |
115 | }
116 |
117 | }
118 |
119 | setFixedBaseMode( value ) {
120 |
121 | // Update our flag and set the fixed base mode on the first (i.e. 0th) chain in this structure.
122 | this.fixedBaseMode = value;
123 | let i = this.numChains, host;
124 | while(i--){
125 | host = this.chains[i].getConnectedChainNumber();
126 | if(host===-1) this.chains[i].setFixedBaseMode( this.fixedBaseMode );
127 | }
128 | //this.chains[0].setFixedBaseMode( this.fixedBaseMode );
129 |
130 | }
131 |
132 | clear() {
133 |
134 | this.clearAllBoneMesh();
135 |
136 | let i, j;
137 |
138 | i = this.numChains;
139 | while(i--){
140 | this.remove(i);
141 | }
142 |
143 | this.chains = [];
144 | this.meshChains = [];
145 | this.angleChains = [];
146 | this.targets = [];
147 |
148 | }
149 |
150 | add( chain, target, meshBone ) {
151 |
152 | this.chains.push( chain );
153 | this.numChains ++;
154 |
155 | //if( target.isVector3 ) target = new V2(target.x, target.y);
156 |
157 | if(target) this.targets.push( target );
158 |
159 |
160 | if( meshBone ) this.addChainMeshs( chain );
161 |
162 | }
163 |
164 | remove( id ){
165 |
166 | this.chains[id].clear();
167 | this.chains.splice(id, 1);
168 | this.meshChains.splice(id, 1);
169 | this.angleChains.splice(id, 1);
170 | this.targets.splice(id, 1);
171 | this.numChains --;
172 |
173 | }
174 |
175 | /*setFixedBaseMode:function( fixedBaseMode ){
176 | for ( let i = 0; i < this.numChains; i++) {
177 | this.chains[i].setFixedBaseMode( fixedBaseMode );
178 | }
179 | },*/
180 |
181 | getNumChains() {
182 |
183 | return this.numChains;
184 |
185 | }
186 |
187 | getChain( id ) {
188 |
189 | return this.chains[id];
190 |
191 | }
192 |
193 | connectChain( Chain, chainNumber, boneNumber, point, target, meshBone, color ) {
194 |
195 | let c = chainNumber;
196 | let n = boneNumber;
197 |
198 | point = point || 'end';
199 |
200 | if ( c > this.numChains ){ Tools.error('Chain not existe !'); return };
201 | if ( n > this.chains[ c ].numBones ){ Tools.error('Bone not existe !'); return };
202 |
203 | // Make a copy of the provided chain so any changes made to the original do not affect this chain
204 | let chain = Chain.clone();
205 |
206 | chain.setBoneConnectionPoint( point === 'end' ? END : START );
207 | chain.setConnectedChainNumber( c );
208 | chain.setConnectedBoneNumber( n );
209 |
210 | // The chain as we were provided should be centred on the origin, so we must now make it
211 | // relative to the start or end position of the given bone in the given chain.
212 |
213 | let position = point === 'end' ? this.chains[ c ].bones[ n ].end : this.chains[ c ].bones[ n ].start;
214 |
215 | //if ( connectionPoint === 'start' ) connectionLocation = this.chains[chainNumber].getBone(boneNumber).start;
216 | //else connectionLocation = this.chains[chainNumber].getBone(boneNumber).end;
217 |
218 |
219 | chain.setBaseLocation( position );
220 |
221 | // When we have a chain connected to a another 'host' chain, the chain is which is connecting in
222 | // MUST have a fixed base, even though that means the base location is 'fixed' to the connection
223 | // point on the host chain, rather than a static location.
224 | chain.setFixedBaseMode( true );
225 |
226 | // Translate the chain we're connecting to the connection point
227 | for ( let i = 0; i < chain.numBones; i++ ){
228 |
229 | chain.bones[i].start.add( position );
230 | chain.bones[i].end.add( position );
231 |
232 | }
233 |
234 | this.add( chain, target, meshBone );
235 |
236 | }
237 |
238 | // 3D THREE
239 |
240 | addChainMeshs( chain, id ) {
241 |
242 | this.isWithMesh = true;
243 |
244 | let meshBone = [];
245 | let angle = [];
246 |
247 | let lng = chain.bones.length;
248 | for(let i = 0; i this.numChains ) return;
187 | if ( boneNumber > this.chains[chainNumber].numBones ) return;
188 |
189 | // Make a copy of the provided chain so any changes made to the original do not affect this chain
190 | let chain = Chain.clone();//new Fullik.Chain( newChain );
191 | if( color !== undefined ) chain.setColor( color );
192 |
193 | // Connect the copy of the provided chain to the specified chain and bone in this structure
194 | //chain.connectToStructure( this, chainNumber, boneNumber );
195 |
196 | chain.setBoneConnectionPoint( point === 'end' ? END : START );
197 | chain.setConnectedChainNumber( c );
198 | chain.setConnectedBoneNumber( n );
199 |
200 | // The chain as we were provided should be centred on the origin, so we must now make it
201 | // relative to the start location of the given bone in the given chain.
202 |
203 | let position = point === 'end' ? this.chains[ c ].bones[ n ].end : this.chains[ c ].bones[ n ].start;
204 |
205 |
206 | chain.setBaseLocation( position );
207 | // When we have a chain connected to a another 'host' chain, the chain is which is connecting in
208 | // MUST have a fixed base, even though that means the base location is 'fixed' to the connection
209 | // point on the host chain, rather than a static location.
210 | chain.setFixedBaseMode( true );
211 |
212 | // Translate the chain we're connecting to the connection point
213 | for ( let i = 0; i < chain.numBones; i++ ){
214 |
215 | chain.bones[i].start.add( position );
216 | chain.bones[i].end.add( position );
217 |
218 | }
219 |
220 | this.add( chain, target, meshBone );
221 |
222 | }
223 |
224 |
225 | // 3D THREE
226 |
227 | addChainMeshs( chain, id ) {
228 |
229 | this.isWithMesh = true;
230 |
231 | let meshBone = [];
232 | let lng = chain.bones.length;
233 | for(let i = 0; i < lng; i++ ){
234 | meshBone.push( this.addBoneMesh( chain.bones[i], i-1, meshBone, chain ));
235 | }
236 |
237 | this.meshChains.push( meshBone );
238 |
239 | }
240 |
241 | addBoneMesh( bone, prev, ar, chain ) {
242 |
243 | let s = 2, r = 2, a1, a2, axe;
244 | let size = bone.length;
245 | let color = bone.color;
246 | let g = new this.THREE.CylinderGeometry ( 1, 0.5, size, 4 );
247 | g.rotateX( -Math.PI * 0.5 );
248 | g.translate( 0, 0, size*0.5 );
249 | //g.applyMatrix4( new this.THREE.Matrix4().makeRotationX( -Math.PI*0.5 ) )
250 | //g.applyMatrix4( new this.THREE.Matrix4().makeTranslation( 0, 0, size*0.5 ) );
251 | let m = new this.THREE.MeshStandardMaterial({ color:color, wireframe:false, shadowSide:this.THREE.DoubleSide });
252 |
253 | let m2 = new this.THREE.MeshBasicMaterial({ wireframe : true });
254 | //let m4 = new this.THREE.MeshBasicMaterial({ wireframe : true, color:color, transparent:true, opacity:0.3 });
255 |
256 | let extraMesh = null;
257 | let extraGeo;
258 |
259 | let type = bone.joint.type;
260 | switch(type){
261 | case J_BALL :
262 | m2.color.setHex(0xFF6600);
263 | let angle = bone.joint.rotor;
264 |
265 | if(angle === Math.PI) break;
266 | s = 2//size/4;
267 | r = 2;//
268 | extraGeo = new this.THREE.CylinderGeometry ( 0, r, s, 6,1, true );
269 | extraGeo.rotateX( -Math.PI * 0.5 );
270 | extraGeo.translate( 0, 0, s*0.5 );
271 | //extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( -Math.PI*0.5 ) )
272 | //extraGeo.applyMatrix4( new this.THREE.Matrix4().makeTranslation( 0, 0, s*0.5 ) );
273 | extraMesh = new this.THREE.Mesh( extraGeo, m2 );
274 | break;
275 | case J_GLOBAL :
276 | axe = bone.joint.getHingeRotationAxis();
277 | a1 = bone.joint.min;
278 | a2 = bone.joint.max;
279 | r = 2;
280 | //console.log('global', a1, a2)
281 | m2.color.setHex(0xFFFF00);
282 | extraGeo = new this.THREE.CircleGeometry( r, 12, a1, -a1+a2 );
283 |
284 |
285 | extraGeo.rotateX( -Math.PI * 0.5 );
286 | //extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( -Math.PI*0.5 ) );
287 | if( axe.z === 1 ) extraGeo.rotateX( -Math.PI * 0.5 );//extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( -Math.PI*0.5 ) );
288 | if( axe.y === 1 ) {extraGeo.rotateY( -Math.PI * 0.5 ); extraGeo.rotateX( -Math.PI * 0.5 ) }//{extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationY( -Math.PI*0.5 ) );extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( -Math.PI*0.5 ) );}
289 | if( axe.x === 1 ) {extraGeo.rotateY( Math.PI * 0.5 );/*extraGeo.applyMatrix4(new this.THREE.Matrix4().makeRotationY( Math.PI*0.5 ));*/}
290 |
291 | extraMesh = new this.THREE.Mesh( extraGeo, m2 );
292 | break;
293 | case J_LOCAL :
294 |
295 | axe = bone.joint.getHingeRotationAxis();
296 | a1 = bone.joint.min;
297 | a2 = bone.joint.max;
298 | r = 2;
299 |
300 | m2.color.setHex(0x00FFFF);
301 | extraGeo = new this.THREE.CircleGeometry( r, 12, a1, -a1+a2 );
302 | extraGeo.rotateX( -Math.PI * 0.5 )
303 |
304 | // extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( -Math.PI*0.5 ) );
305 | if( axe.z === 1 ) { extraGeo.rotateY( -Math.PI * 0.5 ); extraGeo.rotateX( Math.PI * 0.5 ); }
306 | if( axe.x === 1 ) extraGeo.rotateZ( -Math.PI * 0.5 )
307 | if( axe.y === 1 ) { extraGeo.rotateX( Math.PI * 0.5 ); extraGeo.rotateY( Math.PI * 0.5 ) }
308 |
309 | /*if( axe.z === 1 ) { extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationY( -Math.PI*0.5 ) ); extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( Math.PI*0.5 ) );}
310 | if( axe.x === 1 ) extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationZ( -Math.PI*0.5 ) );
311 | if( axe.y === 1 ) { extraGeo.applyMatrix4( new this.THREE.Matrix4().makeRotationX( Math.PI*0.5 ) ); extraGeo.applyMatrix4(new this.THREE.Matrix4().makeRotationY( Math.PI*0.5 ));}*/
312 |
313 | extraMesh = new this.THREE.Mesh( extraGeo, m2 );
314 | break;
315 | }
316 |
317 | const axis = new this.THREE.AxesHelper(2);
318 | //let bw = new this.THREE.Mesh( g, m4 );
319 |
320 | let b = new this.THREE.Mesh( g, m );
321 |
322 | b.add(axis);
323 | //b.add(bw);
324 | this.scene.add( b );
325 |
326 | b.castShadow = true;
327 | b.receiveShadow = true;
328 |
329 | if( prev !== -1 ){
330 | if( extraMesh !== null ){
331 | if(type!==J_GLOBAL){
332 | extraMesh.position.z = chain.bones[prev].length;
333 | ar[prev].add( extraMesh );
334 | } else {
335 | b.add( extraMesh );
336 | b.userData.extra = extraMesh;
337 | b.userData.axe = axe;
338 | //extraMesh.position=(b.position)
339 | //this.scene.add( extraMesh )
340 | }
341 |
342 | }
343 | } else {
344 | if( extraMesh !== null ){
345 |
346 | b.add( extraMesh );
347 | b.userData.extra = extraMesh;
348 | b.userData.axe =new this.THREE.Vector3(0,0,1);
349 | //extraMesh.position=(b.position)
350 | //this.scene.add( extraMesh )
351 | }
352 | }
353 |
354 | return b;
355 |
356 | }
357 |
358 | clearAllBoneMesh() {
359 |
360 | if(!this.isWithMesh) return;
361 |
362 | let i, j, b;
363 |
364 | i = this.meshChains.length;
365 | while(i--){
366 | j = this.meshChains[i].length;
367 | while(j--){
368 | b = this.meshChains[i][j];
369 | this.scene.remove( b );
370 | b.geometry.dispose();
371 | b.material.dispose();
372 | }
373 | this.meshChains[i] = [];
374 | }
375 | this.meshChains = [];
376 |
377 | }
378 |
379 | }
--------------------------------------------------------------------------------
/src/core/Tools.js:
--------------------------------------------------------------------------------
1 | export const Tools = {
2 |
3 | error: function (str) {
4 |
5 | console.error( str )
6 |
7 | },
8 |
9 |
10 | }
--------------------------------------------------------------------------------
/src/math/M3.js:
--------------------------------------------------------------------------------
1 | import { V3 } from './V3.js';
2 | import { math } from './Math.js';
3 |
4 | export class M3 {
5 |
6 | constructor(n11, n12, n13, n21, n22, n23, n31, n32, n33) {
7 |
8 | M3.prototype.isMatrix3 = true;
9 |
10 | this.elements = [
11 |
12 | 1, 0, 0,
13 | 0, 1, 0,
14 | 0, 0, 1
15 |
16 | ];
17 |
18 | if ( arguments.length > 0 ) {
19 |
20 | console.error( 'M3: the constructor no longer reads arguments. use .set() instead.' );
21 |
22 | }
23 | if ( n11 !== undefined ) {
24 |
25 | this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 );
26 |
27 | }
28 |
29 | }
30 |
31 | get XBasis(){ return new V3(this.elements[0], this.elements[1], this.elements[2]); }
32 | set XBasis(v){this.elements[0] = v.x; this.elements[1] = v.y; this.elements[2] = v.z;}
33 |
34 | get YBasis(){ return new V3(this.elements[3], this.elements[4], this.elements[5]); }
35 | set YBasis(v){this.elements[3] = v.x; this.elements[4] = v.y; this.elements[5] = v.z;}
36 |
37 | get ZBasis(){ return new V3(this.elements[6], this.elements[7], this.elements[8]); }
38 | set ZBasis(v){this.elements[6] = v.x; this.elements[7] = v.y; this.elements[8] = v.z;}
39 |
40 |
41 | set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
42 |
43 | let te = this.elements;
44 |
45 | te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;
46 | te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;
47 | te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;
48 |
49 | return this;
50 |
51 | }
52 |
53 | identity() {
54 |
55 | this.set(
56 |
57 | 1, 0, 0,
58 | 0, 1, 0,
59 | 0, 0, 1
60 |
61 | );
62 |
63 | return this;
64 |
65 | }
66 |
67 | setV3( xAxis, yAxis, zAxis ) {
68 |
69 | const te = this.elements;
70 |
71 | te[ 0 ] = xAxis.x;
72 | te[ 3 ] = xAxis.y;
73 | te[ 6 ] = xAxis.z;
74 |
75 | te[ 1 ] = yAxis.x;
76 | te[ 4 ] = yAxis.y;
77 | te[ 7 ] = yAxis.z;
78 |
79 | te[ 2 ] = zAxis.x;
80 | te[ 5 ] = zAxis.y;
81 | te[ 8 ] = zAxis.z;
82 |
83 | return this;
84 |
85 | }
86 |
87 | transpose() {
88 |
89 | let tmp, m = this.elements;
90 |
91 | tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;
92 | tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;
93 | tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;
94 |
95 | return this;
96 |
97 | }
98 |
99 | createRotationMatrix( referenceDirection ) {
100 |
101 | // NEW VERSION - 1.3.8
102 |
103 | let zAxis = referenceDirection;
104 | let xAxis = new V3(1, 0, 0);
105 | let yAxis = new V3(0, 1, 0);
106 |
107 | // Singularity fix
108 | if ( Math.abs( referenceDirection.y ) > 0.9999 ){
109 |
110 | yAxis.copy( xAxis ).cross( zAxis ).normalize();
111 |
112 | } else {
113 |
114 | xAxis.copy( zAxis ).cross( yAxis ).normalize();
115 | yAxis.copy( xAxis ).cross( zAxis ).normalize();
116 |
117 | }
118 |
119 | return this.setV3( xAxis, yAxis, zAxis );
120 |
121 | }
122 |
123 | rotateDegs( axis, angleDegs ){
124 | return this.rotateRads(axis, angleDegs * math.toRad);
125 | }
126 |
127 | rotateRads( axis, angle ){
128 |
129 | const dest = new this.constructor();
130 | let sin = Math.sin( angle );
131 | let cos = Math.cos( angle );
132 | let oneMinusCos = 1.0 - cos;
133 |
134 | let xy = axis.x * axis.y;
135 | let yz = axis.y * axis.z;
136 | let xz = axis.x * axis.z;
137 | let xs = axis.x * sin;
138 | let ys = axis.y * sin;
139 | let zs = axis.z * sin;
140 |
141 | let f00 = axis.x * axis.x * oneMinusCos + cos;
142 | let f01 = xy * oneMinusCos + zs;
143 | let f02 = xz * oneMinusCos - ys;
144 |
145 | let f10 = xy * oneMinusCos - zs;
146 | let f11 = axis.y * axis.y * oneMinusCos + cos;
147 | let f12 = yz * oneMinusCos + xs;
148 |
149 | let f20 = xz * oneMinusCos + ys;
150 | let f21 = yz * oneMinusCos - xs;
151 | let f22 = axis.z * axis.z * oneMinusCos + cos;
152 |
153 | let m = this.elements
154 |
155 | let t00 = m[0] * f00 + m[3] * f01 + m[6] * f02;
156 | let t01 = m[1] * f00 + m[4] * f01 + m[7] * f02;
157 | let t02 = m[2] * f00 + m[5] * f01 + m[8] * f02;
158 |
159 | let t10 = m[0] * f10 + m[3] * f11 + m[6] * f12;
160 | let t11 = m[1] * f10 + m[4] * f11 + m[7] * f12;
161 | let t12 = m[2] * f10 + m[5] * f11 + m[8] * f12;
162 |
163 | let k = //dest.elements
164 | // Construct and return rotation matrix
165 | k[6] = m[0] * f20 + m[3] * f21 + m[6] * f22;
166 | k[7] = m[1] * f20 + m[4] * f21 + m[7] * f22;
167 | k[8] = m[2] * f20 + m[5] * f21 + m[8] * f22;
168 |
169 | k[0] = t00;
170 | k[1] = t01;
171 | k[2] = t02;
172 |
173 | k[3] = t10;
174 | k[4] = t11;
175 | k[5] = t12;
176 |
177 | return dest;
178 |
179 | }
180 |
181 | rotateAboutAxis( v, angle, axis ){
182 |
183 | let sin = Math.sin( angle );
184 | let cos = Math.cos( angle );
185 | let oneMinuscos = 1.0 - cos;
186 |
187 | // It's quicker to pre-calc these and reuse than calculate x * y, then y * x later (same thing).
188 | let xyOne = axis.x * axis.y * oneMinuscos;
189 | let xzOne = axis.x * axis.z * oneMinuscos;
190 | let yzOne = axis.y * axis.z * oneMinuscos;
191 |
192 | let te = this.elements;
193 |
194 | // Calculate rotated x-axis
195 | te[ 0 ] = axis.x * axis.x * oneMinuscos + cos;
196 | te[ 3 ] = xyOne + axis.z * sin;
197 | te[ 6 ] = xzOne - axis.y * sin;
198 |
199 | // Calculate rotated y-axis
200 | te[ 1 ] = xyOne - axis.z * sin;
201 | te[ 4 ] = axis.y * axis.y * oneMinuscos + cos;
202 | te[ 7 ] = yzOne + axis.x * sin;
203 |
204 | // Calculate rotated z-axis
205 | te[ 2 ] = xzOne + axis.y * sin;
206 | te[ 5 ] = yzOne - axis.x * sin;
207 | te[ 8 ] = axis.z * axis.z * oneMinuscos + cos;
208 |
209 | // this.identity()
210 | const mm = this//.rotateRads(axis, angle);
211 |
212 | // Multiply the source by the rotation matrix we just created to perform the rotation
213 | return v.clone().applyM3( mm );
214 |
215 | }
216 |
217 |
218 | ///////
219 |
220 | determinant() {
221 |
222 | const te = this.elements;
223 |
224 | const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],
225 | d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],
226 | g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];
227 |
228 | return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
229 |
230 | }
231 |
232 | invert() {
233 |
234 | const te = this.elements,
235 |
236 | n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ],
237 | n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ],
238 | n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ],
239 |
240 | t11 = n33 * n22 - n32 * n23,
241 | t12 = n32 * n13 - n33 * n12,
242 | t13 = n23 * n12 - n22 * n13,
243 |
244 | det = n11 * t11 + n21 * t12 + n31 * t13;
245 |
246 | if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
247 |
248 | const detInv = 1 / det;
249 |
250 | te[ 0 ] = t11 * detInv;
251 | te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;
252 | te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;
253 |
254 | te[ 3 ] = t12 * detInv;
255 | te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;
256 | te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;
257 |
258 | te[ 6 ] = t13 * detInv;
259 | te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;
260 | te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;
261 |
262 | return this;
263 |
264 | }
265 |
266 | multiplyMatrices( a, b ) {
267 |
268 | const ae = a.elements;
269 | const be = b.elements;
270 | const te = this.elements;
271 |
272 | const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
273 | const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
274 | const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
275 |
276 | const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
277 | const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
278 | const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
279 |
280 | te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
281 | te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
282 | te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
283 |
284 | te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
285 | te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
286 | te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
287 |
288 | te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
289 | te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
290 | te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
291 |
292 | return this;
293 |
294 | }
295 |
296 | premultiply( m ) {
297 |
298 | return this.multiplyMatrices( m, this );
299 |
300 | }
301 |
302 | multiply( m ) {
303 |
304 | return this.multiplyMatrices( this, m );
305 |
306 | }
307 |
308 | setFromMatrix4( m ) {
309 |
310 | const me = m.elements;
311 |
312 | this.set(
313 |
314 | me[ 0 ], me[ 4 ], me[ 8 ],
315 | me[ 1 ], me[ 5 ], me[ 9 ],
316 | me[ 2 ], me[ 6 ], me[ 10 ]
317 |
318 | );
319 |
320 | return this;
321 |
322 | }
323 |
324 | copy( m ) {
325 |
326 | const te = this.elements;
327 | const me = m.elements;
328 |
329 | te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];
330 | te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];
331 | te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];
332 |
333 | return this;
334 |
335 | }
336 |
337 | fromArray( array, offset = 0 ) {
338 |
339 | for ( let i = 0; i < 9; i ++ ) {
340 |
341 | this.elements[ i ] = array[ i + offset ];
342 |
343 | }
344 |
345 | return this;
346 |
347 | }
348 |
349 | toArray( array = [], offset = 0 ) {
350 |
351 | const te = this.elements;
352 |
353 | array[ offset ] = te[ 0 ];
354 | array[ offset + 1 ] = te[ 1 ];
355 | array[ offset + 2 ] = te[ 2 ];
356 |
357 | array[ offset + 3 ] = te[ 3 ];
358 | array[ offset + 4 ] = te[ 4 ];
359 | array[ offset + 5 ] = te[ 5 ];
360 |
361 | array[ offset + 6 ] = te[ 6 ];
362 | array[ offset + 7 ] = te[ 7 ];
363 | array[ offset + 8 ] = te[ 8 ];
364 |
365 | return array;
366 |
367 | }
368 |
369 | clone() {
370 |
371 | return new this.constructor().fromArray( this.elements );
372 |
373 | }
374 |
375 | isOrthogonal(){
376 |
377 | const xCrossYDot = this.XBasis.dot(this.YBasis);
378 | const xCrossZDot = this.XBasis.dot(this.ZBasis);
379 | const yCrossZDot = this.YBasis.dot(this.ZBasis);
380 | if ( math.nearEquals(xCrossYDot, 0.0, 0.01) && math.nearEquals(xCrossZDot, 0.0, 0.01) && math.nearEquals(yCrossZDot, 0.0, 0.01) ) return true;
381 | return false;
382 |
383 | }
384 |
385 | }
--------------------------------------------------------------------------------
/src/math/Math - Copie.js:
--------------------------------------------------------------------------------
1 | import { Tools } from '../core/Tools.js';
2 |
3 | export const _Math = {
4 |
5 | toRad: Math.PI / 180,
6 | toDeg: 180 / Math.PI,
7 | pi90: Math.PI * 0.5,
8 | twoPI: Math.PI * 2,
9 |
10 | // Center point is p1; angle returned in Radians
11 | //findAngle: function ( p0, p1, p2 ) {
12 | findAngle: function ( b0, b1 ) {
13 |
14 | /*let a = p1.minus(p2).lengthSq(),
15 | b = p1.minus(p0).lengthSq(),
16 | c = p2.minus(p0).lengthSq(),*/
17 | let a = b0.end.minus(b1.end).lengthSq(),
18 | b = b0.end.minus(b0.start).lengthSq(),
19 | c = b1.end.minus(b0.start).lengthSq(),
20 | angle, r, sign;
21 |
22 | r = ( a + b - c ) / Math.sqrt( 4 * a * b );
23 | if( r <= -1 ) angle = Math.PI;
24 | else if( r >= 1 ) angle = 0;
25 | else angle = Math.acos( r );
26 | // sign
27 | sign = b0.end.x * b1.end.y - b0.end.y * b1.end.x;
28 | return sign >= 0 ? angle : -angle;
29 |
30 | },
31 |
32 | clamp: function ( v, min, max ) {
33 |
34 | v = v < min ? min : v;
35 | v = v > max ? max : v;
36 | return v;
37 |
38 | },
39 |
40 | /*clamp: function ( value, min, max ) {
41 |
42 | return Math.max( min, Math.min( max, value ) );
43 |
44 | },*/
45 |
46 | lerp: function ( x, y, t ) {
47 |
48 | return ( 1 - t ) * x + t * y;
49 |
50 | },
51 |
52 | rand: function ( low, high ) {
53 |
54 | return low + Math.random() * ( high - low );
55 |
56 | },
57 |
58 | randInt: function ( low, high ) {
59 |
60 | return low + Math.floor( Math.random() * ( high - low + 1 ) );
61 |
62 | },
63 |
64 | nearEquals: function (a, b, t) {
65 |
66 | return Math.abs(a - b) <= t ? true : false;
67 |
68 | },
69 |
70 | perpendicular: function ( a, b ) {
71 |
72 | return _Math.nearEquals( a.dot(b), 0.0, 0.01 ) ? true : false;
73 |
74 | //return _Math.nearEquals( _Math.dotProduct(a, b), 0.0, 0.01 ) ? true : false;
75 |
76 | },
77 |
78 | genPerpendicularVectorQuick: function ( v ) {
79 |
80 | //return _Math.genPerpendicularVectorFrisvad( v );
81 |
82 | let p = v.clone();
83 | // cross(v, UP) : cross(v, RIGHT)
84 | return Math.abs( v.y ) < 0.99 ? p.set( -v.z, 0, v.x ).normalize() : p.set( 0, v.z, -v.y ).normalize();
85 |
86 | },
87 |
88 | /*genPerpendicularVectorHM: function ( v ) {
89 |
90 | let a = v.abs();
91 | let b = v.clone();
92 | if (a.x <= a.y && a.x <= a.z) return b.set(0, -v.z, v.y).normalize();
93 | else if (a.y <= a.x && a.y <= a.z) return b.set(-v.z, 0, v.x).normalize();
94 | else return b.set(-v.y, v.x, 0).normalize();
95 |
96 | },*/
97 |
98 | genPerpendicularVectorFrisvad: function ( v ) {
99 |
100 | let nv = v.clone();
101 | if ( v.z < -0.9999999 ) return nv.set(0, -1, 0);// Handle the singularity
102 | let a = 1/(1 + v.z);
103 | return nv.set( 1 - v.x * v.x * a, - v.x * v.y * a, -v.x ).normalize();
104 |
105 | },
106 |
107 | // rotation
108 |
109 | rotateXDegs: function ( v, angle ) { return v.clone().rotate( angle * _Math.toRad, 'X' ); },
110 | rotateYDegs: function ( v, angle ) { return v.clone().rotate( angle * _Math.toRad, 'Y' ) },
111 | rotateZDegs: function ( v, angle ) { return v.clone().rotate( angle * _Math.toRad, 'Z' ) },
112 |
113 | // distance
114 |
115 | withinManhattanDistance: function ( v1, v2, distance ) {
116 |
117 | if (Math.abs(v2.x - v1.x) > distance) return false; // Too far in x direction
118 | if (Math.abs(v2.y - v1.y) > distance) return false; // Too far in y direction
119 | if (Math.abs(v2.z - v1.z) > distance) return false; // Too far in z direction
120 | return true;
121 |
122 | },
123 |
124 | manhattanDistanceBetween: function ( v1, v2 ) {
125 |
126 | return Math.abs(v2.x - v1.x) + Math.abs(v2.x - v1.x) + Math.abs(v2.x - v1.x);
127 |
128 | },
129 |
130 | distanceBetween: function ( v1, v2 ) {
131 |
132 | let dx = v2.x - v1.x;
133 | let dy = v2.y - v1.y;
134 | let dz = v1.z !== undefined ? v2.z - v1.z : 0;
135 | return Math.sqrt( dx * dx + dy * dy + dz * dz );
136 |
137 | },
138 |
139 | unwrapDeg: ( r ) => (r - (Math.floor((r + 180)/360))*360),
140 | unwrapRad: ( r ) => (r - (Math.floor((r + Math.PI)/(2*Math.PI)))*2*Math.PI),
141 |
142 |
143 | /*unwrapDeg: function ( r ) {
144 |
145 | r = r % 360;
146 | if (r > 180) r -= 360;
147 | if (r < -180) r += 360;
148 | return r;
149 |
150 | },
151 |
152 | unwrapRad: function( r ){
153 |
154 | r = r % _Math.twoPI;
155 | if (r > Math.Pi ) r -= _Math.twoPI;
156 | if (r < - Math.Pi ) r += _Math.twoPI;
157 | return r;
158 |
159 | },*/
160 |
161 |
162 | // ______________________________ 2D _____________________________
163 |
164 | rotateDegs: function( v, angle ) {
165 |
166 | return v.clone().rotate( angle * _Math.toRad )
167 |
168 | },
169 |
170 |
171 | validateDirectionUV: function( directionUV ) {
172 |
173 | if( directionUV.length() < 0 ) Tools.error("vector direction unit vector cannot be zero.");
174 |
175 | },
176 |
177 | validateLength: function( length ) {
178 |
179 | if( length < 0 ) Tools.error("Length must be a greater than or equal to zero.");
180 |
181 | },
182 |
183 |
184 |
185 | }
--------------------------------------------------------------------------------
/src/math/Math.js:
--------------------------------------------------------------------------------
1 | import { Tools } from '../core/Tools.js';
2 |
3 | export const math = {
4 |
5 | PI:Math.PI,
6 | toRad: Math.PI / 180,
7 | toDeg: 180 / Math.PI,
8 | pi90: Math.PI * 0.5,
9 | twoPI: Math.PI * 2,
10 |
11 | // Center point is p1; angle returned in Radians
12 | //findAngle: function ( p0, p1, p2 ) {
13 | findAngle: function ( b0, b1 ) {
14 |
15 | //return Math.atan2( b1.end - b0.end, b1.start - b0.start )
16 |
17 | /*let a = p1.minus(p2).lengthSq(),
18 | b = p1.minus(p0).lengthSq(),
19 | c = p2.minus(p0).lengthSq(),*/
20 | let a = b0.end.minus(b1.end).lengthSq(),
21 | b = b0.end.minus(b0.start).lengthSq(),
22 | c = b1.end.minus(b0.start).lengthSq(),
23 | angle, r, sign;
24 |
25 | r = ( a + b - c ) / Math.sqrt( 4 * a * b );
26 | if( r <= -1 ) angle = Math.PI;
27 | else if( r >= 1 ) angle = 0;
28 | else angle = Math.acos( r );
29 | // sign
30 | sign = b0.end.x * b1.end.y - b0.end.y * b1.end.x;
31 | return sign >= 0 ? angle : -angle;
32 |
33 | },
34 |
35 | clamp: function ( v, min, max ) {
36 |
37 | v = v < min ? min : v;
38 | v = v > max ? max : v;
39 | return v;
40 |
41 | },
42 |
43 | /*clamp: function ( value, min, max ) {
44 |
45 | return Math.max( min, Math.min( max, value ) );
46 |
47 | },*/
48 |
49 | lerp: function ( x, y, t ) {
50 |
51 | return ( 1 - t ) * x + t * y;
52 |
53 | },
54 |
55 | rand: function ( low, high ) {
56 |
57 | return low + Math.random() * ( high - low );
58 |
59 | },
60 |
61 | randInt: function ( low, high ) {
62 |
63 | return low + Math.floor( Math.random() * ( high - low + 1 ) );
64 |
65 | },
66 |
67 | nearEquals: function (a, b, t) {
68 |
69 | return Math.abs(a - b) <= t ? true : false;
70 |
71 | },
72 |
73 | perpendicular: function ( a, b ) {
74 |
75 | return math.nearEquals( a.dot(b), 0.0, 0.01 ) ? true : false;
76 |
77 | //return math.nearEquals( math.dotProduct(a, b), 0.0, 0.01 ) ? true : false;
78 |
79 | },
80 |
81 | genPerpendicularVectorQuick: function ( v ) {
82 |
83 | //return math.genPerpendicularVectorFrisvad( v );
84 |
85 | let p = v.clone();
86 | // cross(v, UP) : cross(v, RIGHT)
87 | return Math.abs( v.y ) < 0.99 ? p.set( -v.z, 0, v.x ).normalize() : p.set( 0, v.z, -v.y ).normalize();
88 |
89 | },
90 |
91 | /*genPerpendicularVectorHM: function ( v ) {
92 |
93 | let a = v.abs();
94 | let b = v.clone();
95 | if (a.x <= a.y && a.x <= a.z) return b.set(0, -v.z, v.y).normalize();
96 | else if (a.y <= a.x && a.y <= a.z) return b.set(-v.z, 0, v.x).normalize();
97 | else return b.set(-v.y, v.x, 0).normalize();
98 |
99 | },*/
100 |
101 | genPerpendicularVectorFrisvad: function ( v ) {
102 |
103 | let nv = v.clone();
104 | if ( v.z < -0.9999999 ) return nv.set(0, -1, 0);// Handle the singularity
105 | let a = 1/(1 + v.z);
106 | return nv.set( 1 - v.x * v.x * a, - v.x * v.y * a, -v.x ).normalize();
107 |
108 | },
109 |
110 | nearEquals(a,b,tolerance){
111 | return (Math.abs(a - b) <= tolerance) ? true : false;
112 | },
113 |
114 | // rotation
115 |
116 | rotateXDegs: function ( v, angle ) { return v.clone().rotate( angle * math.toRad, 'X' ); },
117 | rotateYDegs: function ( v, angle ) { return v.clone().rotate( angle * math.toRad, 'Y' ) },
118 | rotateZDegs: function ( v, angle ) { return v.clone().rotate( angle * math.toRad, 'Z' ) },
119 |
120 | // distance
121 |
122 | withinManhattanDistance: function ( v1, v2, distance ) {
123 |
124 | if (Math.abs(v2.x - v1.x) > distance) return false; // Too far in x direction
125 | if (Math.abs(v2.y - v1.y) > distance) return false; // Too far in y direction
126 | if (Math.abs(v2.z - v1.z) > distance) return false; // Too far in z direction
127 | return true;
128 |
129 | },
130 |
131 | manhattanDistanceBetween: function ( v1, v2 ) {
132 |
133 | return Math.abs(v2.x - v1.x) + Math.abs(v2.y - v1.y) + Math.abs(v2.z - v1.z);
134 |
135 | },
136 |
137 | distanceBetween: function ( v1, v2 ) {
138 |
139 | let dx = v2.x - v1.x;
140 | let dy = v2.y - v1.y;
141 | let dz = v1.z !== undefined ? v2.z - v1.z : 0;
142 | return Math.sqrt( dx * dx + dy * dy + dz * dz );
143 |
144 | },
145 |
146 | unwrapDeg: ( r ) => (r - (Math.floor((r + 180)/360))*360),
147 | unwrapRad: ( r ) => (r - (Math.floor((r + Math.PI)/(2*Math.PI)))*2*Math.PI),
148 |
149 |
150 | /*unwrapDeg: function ( r ) {
151 |
152 | r = r % 360;
153 | if (r > 180) r -= 360;
154 | if (r < -180) r += 360;
155 | return r;
156 |
157 | },
158 |
159 | unwrapRad: function( r ){
160 |
161 | r = r % math.twoPI;
162 | if (r > Math.Pi ) r -= math.twoPI;
163 | if (r < - Math.Pi ) r += math.twoPI;
164 | return r;
165 |
166 | },*/
167 |
168 |
169 | // ______________________________ 2D _____________________________
170 |
171 | rotateDegs: function( v, angle ) {
172 |
173 | return v.clone().rotate( angle * math.toRad )
174 |
175 | },
176 |
177 |
178 | validateDirectionUV: function( directionUV ) {
179 |
180 | if( directionUV.length() < 0 ) Tools.error("vector direction unit vector cannot be zero.");
181 |
182 | },
183 |
184 | validateLength: function( length ) {
185 |
186 | if( length < 0 ) Tools.error("Length must be a greater than or equal to zero.");
187 |
188 | },
189 |
190 |
191 |
192 | }
--------------------------------------------------------------------------------
/src/math/Q.js:
--------------------------------------------------------------------------------
1 |
2 | import { math } from './Math.js';
3 | //import { MathUtils } from './MathUtils.js';
4 |
5 | export class Q {
6 |
7 | constructor( x = 0, y = 0, z = 0, w = 1 ) {
8 |
9 | this.isQuaternion = true;
10 |
11 | this._x = x;
12 | this._y = y;
13 | this._z = z;
14 | this._w = w;
15 |
16 | }
17 |
18 | static slerp( qa, qb, qm, t ) {
19 |
20 | return qm.copy( qa ).slerp( qb, t );
21 |
22 | }
23 |
24 | static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
25 |
26 | // fuzz-free, array-based Quaternion SLERP operation
27 |
28 | let x0 = src0[ srcOffset0 + 0 ],
29 | y0 = src0[ srcOffset0 + 1 ],
30 | z0 = src0[ srcOffset0 + 2 ],
31 | w0 = src0[ srcOffset0 + 3 ];
32 |
33 | const x1 = src1[ srcOffset1 + 0 ],
34 | y1 = src1[ srcOffset1 + 1 ],
35 | z1 = src1[ srcOffset1 + 2 ],
36 | w1 = src1[ srcOffset1 + 3 ];
37 |
38 | if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
39 |
40 | let s = 1 - t;
41 | const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
42 | dir = ( cos >= 0 ? 1 : - 1 ),
43 | sqrSin = 1 - cos * cos;
44 |
45 | // Skip the Slerp for tiny steps to avoid numeric problems:
46 | if ( sqrSin > Number.EPSILON ) {
47 |
48 | const sin = Math.sqrt( sqrSin ),
49 | len = Math.atan2( sin, cos * dir );
50 |
51 | s = Math.sin( s * len ) / sin;
52 | t = Math.sin( t * len ) / sin;
53 |
54 | }
55 |
56 | const tDir = t * dir;
57 |
58 | x0 = x0 * s + x1 * tDir;
59 | y0 = y0 * s + y1 * tDir;
60 | z0 = z0 * s + z1 * tDir;
61 | w0 = w0 * s + w1 * tDir;
62 |
63 | // Normalize in case we just did a lerp:
64 | if ( s === 1 - t ) {
65 |
66 | const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
67 |
68 | x0 *= f;
69 | y0 *= f;
70 | z0 *= f;
71 | w0 *= f;
72 |
73 | }
74 |
75 | }
76 |
77 | dst[ dstOffset ] = x0;
78 | dst[ dstOffset + 1 ] = y0;
79 | dst[ dstOffset + 2 ] = z0;
80 | dst[ dstOffset + 3 ] = w0;
81 |
82 | }
83 |
84 | static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {
85 |
86 | const x0 = src0[ srcOffset0 ];
87 | const y0 = src0[ srcOffset0 + 1 ];
88 | const z0 = src0[ srcOffset0 + 2 ];
89 | const w0 = src0[ srcOffset0 + 3 ];
90 |
91 | const x1 = src1[ srcOffset1 ];
92 | const y1 = src1[ srcOffset1 + 1 ];
93 | const z1 = src1[ srcOffset1 + 2 ];
94 | const w1 = src1[ srcOffset1 + 3 ];
95 |
96 | dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
97 | dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
98 | dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
99 | dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
100 |
101 | return dst;
102 |
103 | }
104 |
105 | get x() {
106 |
107 | return this._x;
108 |
109 | }
110 |
111 | set x( value ) {
112 |
113 | this._x = value;
114 | this._onChangeCallback();
115 |
116 | }
117 |
118 | get y() {
119 |
120 | return this._y;
121 |
122 | }
123 |
124 | set y( value ) {
125 |
126 | this._y = value;
127 | this._onChangeCallback();
128 |
129 | }
130 |
131 | get z() {
132 |
133 | return this._z;
134 |
135 | }
136 |
137 | set z( value ) {
138 |
139 | this._z = value;
140 | this._onChangeCallback();
141 |
142 | }
143 |
144 | get w() {
145 |
146 | return this._w;
147 |
148 | }
149 |
150 | set w( value ) {
151 |
152 | this._w = value;
153 | this._onChangeCallback();
154 |
155 | }
156 |
157 | set( x, y, z, w ) {
158 |
159 | this._x = x;
160 | this._y = y;
161 | this._z = z;
162 | this._w = w;
163 |
164 | this._onChangeCallback();
165 |
166 | return this;
167 |
168 | }
169 |
170 | clone() {
171 |
172 | return new this.constructor( this._x, this._y, this._z, this._w );
173 |
174 | }
175 |
176 | copy( quaternion ) {
177 |
178 | this._x = quaternion.x;
179 | this._y = quaternion.y;
180 | this._z = quaternion.z;
181 | this._w = quaternion.w;
182 |
183 | this._onChangeCallback();
184 |
185 | return this;
186 |
187 | }
188 |
189 | setFromEuler( euler, update ) {
190 |
191 | if ( ! ( euler && euler.isEuler ) ) {
192 |
193 | throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );
194 |
195 | }
196 |
197 | const x = euler._x, y = euler._y, z = euler._z, order = euler._order;
198 |
199 | // http://www.mathworks.com/matlabcentral/fileexchange/
200 | // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
201 | // content/SpinCalc.m
202 |
203 | const cos = Math.cos;
204 | const sin = Math.sin;
205 |
206 | const c1 = cos( x / 2 );
207 | const c2 = cos( y / 2 );
208 | const c3 = cos( z / 2 );
209 |
210 | const s1 = sin( x / 2 );
211 | const s2 = sin( y / 2 );
212 | const s3 = sin( z / 2 );
213 |
214 | switch ( order ) {
215 |
216 | case 'XYZ':
217 | this._x = s1 * c2 * c3 + c1 * s2 * s3;
218 | this._y = c1 * s2 * c3 - s1 * c2 * s3;
219 | this._z = c1 * c2 * s3 + s1 * s2 * c3;
220 | this._w = c1 * c2 * c3 - s1 * s2 * s3;
221 | break;
222 |
223 | case 'YXZ':
224 | this._x = s1 * c2 * c3 + c1 * s2 * s3;
225 | this._y = c1 * s2 * c3 - s1 * c2 * s3;
226 | this._z = c1 * c2 * s3 - s1 * s2 * c3;
227 | this._w = c1 * c2 * c3 + s1 * s2 * s3;
228 | break;
229 |
230 | case 'ZXY':
231 | this._x = s1 * c2 * c3 - c1 * s2 * s3;
232 | this._y = c1 * s2 * c3 + s1 * c2 * s3;
233 | this._z = c1 * c2 * s3 + s1 * s2 * c3;
234 | this._w = c1 * c2 * c3 - s1 * s2 * s3;
235 | break;
236 |
237 | case 'ZYX':
238 | this._x = s1 * c2 * c3 - c1 * s2 * s3;
239 | this._y = c1 * s2 * c3 + s1 * c2 * s3;
240 | this._z = c1 * c2 * s3 - s1 * s2 * c3;
241 | this._w = c1 * c2 * c3 + s1 * s2 * s3;
242 | break;
243 |
244 | case 'YZX':
245 | this._x = s1 * c2 * c3 + c1 * s2 * s3;
246 | this._y = c1 * s2 * c3 + s1 * c2 * s3;
247 | this._z = c1 * c2 * s3 - s1 * s2 * c3;
248 | this._w = c1 * c2 * c3 - s1 * s2 * s3;
249 | break;
250 |
251 | case 'XZY':
252 | this._x = s1 * c2 * c3 - c1 * s2 * s3;
253 | this._y = c1 * s2 * c3 - s1 * c2 * s3;
254 | this._z = c1 * c2 * s3 + s1 * s2 * c3;
255 | this._w = c1 * c2 * c3 + s1 * s2 * s3;
256 | break;
257 |
258 | default:
259 | console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order );
260 |
261 | }
262 |
263 | if ( update !== false ) this._onChangeCallback();
264 |
265 | return this;
266 |
267 | }
268 |
269 | setFromAxisAngle( axis, angle ) {
270 |
271 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
272 |
273 | // assumes axis is normalized
274 |
275 | const halfAngle = angle / 2, s = Math.sin( halfAngle );
276 |
277 | this._x = axis.x * s;
278 | this._y = axis.y * s;
279 | this._z = axis.z * s;
280 | this._w = Math.cos( halfAngle );
281 |
282 | this._onChangeCallback();
283 |
284 | return this;
285 |
286 | }
287 |
288 | setFromRotationMatrix( m ) {
289 |
290 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
291 |
292 | // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
293 |
294 | const te = m.elements,
295 |
296 | m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
297 | m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
298 | m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
299 |
300 | trace = m11 + m22 + m33;
301 |
302 | if ( trace > 0 ) {
303 |
304 | const s = 0.5 / Math.sqrt( trace + 1.0 );
305 |
306 | this._w = 0.25 / s;
307 | this._x = ( m32 - m23 ) * s;
308 | this._y = ( m13 - m31 ) * s;
309 | this._z = ( m21 - m12 ) * s;
310 |
311 | } else if ( m11 > m22 && m11 > m33 ) {
312 |
313 | const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
314 |
315 | this._w = ( m32 - m23 ) / s;
316 | this._x = 0.25 * s;
317 | this._y = ( m12 + m21 ) / s;
318 | this._z = ( m13 + m31 ) / s;
319 |
320 | } else if ( m22 > m33 ) {
321 |
322 | const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
323 |
324 | this._w = ( m13 - m31 ) / s;
325 | this._x = ( m12 + m21 ) / s;
326 | this._y = 0.25 * s;
327 | this._z = ( m23 + m32 ) / s;
328 |
329 | } else {
330 |
331 | const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
332 |
333 | this._w = ( m21 - m12 ) / s;
334 | this._x = ( m13 + m31 ) / s;
335 | this._y = ( m23 + m32 ) / s;
336 | this._z = 0.25 * s;
337 |
338 | }
339 |
340 | this._onChangeCallback();
341 |
342 | return this;
343 |
344 | }
345 |
346 | setFromUnitVectors( vFrom, vTo ) {
347 |
348 | // assumes direction vectors vFrom and vTo are normalized
349 |
350 | const EPS = 0.000001;
351 |
352 | let r = vFrom.dot( vTo ) + 1;
353 |
354 | if ( r < EPS ) {
355 |
356 | r = 0;
357 |
358 | if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
359 |
360 | this._x = - vFrom.y;
361 | this._y = vFrom.x;
362 | this._z = 0;
363 | this._w = r;
364 |
365 | } else {
366 |
367 | this._x = 0;
368 | this._y = - vFrom.z;
369 | this._z = vFrom.y;
370 | this._w = r;
371 |
372 | }
373 |
374 | } else {
375 |
376 | // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3
377 |
378 | this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
379 | this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
380 | this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
381 | this._w = r;
382 |
383 | }
384 |
385 | return this.normalize();
386 |
387 | }
388 |
389 | angleTo( q ) {
390 |
391 | return 2 * Math.acos( Math.abs( math.clamp( this.dot( q ), - 1, 1 ) ) );
392 |
393 | }
394 |
395 | rotateTowards( q, step ) {
396 |
397 | const angle = this.angleTo( q );
398 |
399 | if ( angle === 0 ) return this;
400 |
401 | const t = Math.min( 1, step / angle );
402 |
403 | this.slerp( q, t );
404 |
405 | return this;
406 |
407 | }
408 |
409 | identity() {
410 |
411 | return this.set( 0, 0, 0, 1 );
412 |
413 | }
414 |
415 | invert() {
416 |
417 | // quaternion is assumed to have unit length
418 |
419 | return this.conjugate();
420 |
421 | }
422 |
423 | conjugate() {
424 |
425 | this._x *= - 1;
426 | this._y *= - 1;
427 | this._z *= - 1;
428 |
429 | this._onChangeCallback();
430 |
431 | return this;
432 |
433 | }
434 |
435 | dot( v ) {
436 |
437 | return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
438 |
439 | }
440 |
441 | lengthSq() {
442 |
443 | return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
444 |
445 | }
446 |
447 | length() {
448 |
449 | return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
450 |
451 | }
452 |
453 | normalize() {
454 |
455 | let l = this.length();
456 |
457 | if ( l === 0 ) {
458 |
459 | this._x = 0;
460 | this._y = 0;
461 | this._z = 0;
462 | this._w = 1;
463 |
464 | } else {
465 |
466 | l = 1 / l;
467 |
468 | this._x = this._x * l;
469 | this._y = this._y * l;
470 | this._z = this._z * l;
471 | this._w = this._w * l;
472 |
473 | }
474 |
475 | this._onChangeCallback();
476 |
477 | return this;
478 |
479 | }
480 |
481 | multiply( q, p ) {
482 |
483 | if ( p !== undefined ) {
484 |
485 | console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
486 | return this.multiplyQuaternions( q, p );
487 |
488 | }
489 |
490 | return this.multiplyQuaternions( this, q );
491 |
492 | }
493 |
494 | premultiply( q ) {
495 |
496 | return this.multiplyQuaternions( q, this );
497 |
498 | }
499 |
500 | multiplyQuaternions( a, b ) {
501 |
502 | // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
503 |
504 | const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
505 | const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
506 |
507 | this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
508 | this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
509 | this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
510 | this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
511 |
512 | this._onChangeCallback();
513 |
514 | return this;
515 |
516 | }
517 |
518 | slerp( qb, t ) {
519 |
520 | if ( t === 0 ) return this;
521 | if ( t === 1 ) return this.copy( qb );
522 |
523 | const x = this._x, y = this._y, z = this._z, w = this._w;
524 |
525 | // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
526 |
527 | let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
528 |
529 | if ( cosHalfTheta < 0 ) {
530 |
531 | this._w = - qb._w;
532 | this._x = - qb._x;
533 | this._y = - qb._y;
534 | this._z = - qb._z;
535 |
536 | cosHalfTheta = - cosHalfTheta;
537 |
538 | } else {
539 |
540 | this.copy( qb );
541 |
542 | }
543 |
544 | if ( cosHalfTheta >= 1.0 ) {
545 |
546 | this._w = w;
547 | this._x = x;
548 | this._y = y;
549 | this._z = z;
550 |
551 | return this;
552 |
553 | }
554 |
555 | const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
556 |
557 | if ( sqrSinHalfTheta <= Number.EPSILON ) {
558 |
559 | const s = 1 - t;
560 | this._w = s * w + t * this._w;
561 | this._x = s * x + t * this._x;
562 | this._y = s * y + t * this._y;
563 | this._z = s * z + t * this._z;
564 |
565 | this.normalize();
566 | this._onChangeCallback();
567 |
568 | return this;
569 |
570 | }
571 |
572 | const sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
573 | const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
574 | const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
575 | ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
576 |
577 | this._w = ( w * ratioA + this._w * ratioB );
578 | this._x = ( x * ratioA + this._x * ratioB );
579 | this._y = ( y * ratioA + this._y * ratioB );
580 | this._z = ( z * ratioA + this._z * ratioB );
581 |
582 | this._onChangeCallback();
583 |
584 | return this;
585 |
586 | }
587 |
588 | equals( quaternion ) {
589 |
590 | return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
591 |
592 | }
593 |
594 | fromArray( array, offset = 0 ) {
595 |
596 | this._x = array[ offset ];
597 | this._y = array[ offset + 1 ];
598 | this._z = array[ offset + 2 ];
599 | this._w = array[ offset + 3 ];
600 |
601 | this._onChangeCallback();
602 |
603 | return this;
604 |
605 | }
606 |
607 | toArray( array = [], offset = 0 ) {
608 |
609 | array[ offset ] = this._x;
610 | array[ offset + 1 ] = this._y;
611 | array[ offset + 2 ] = this._z;
612 | array[ offset + 3 ] = this._w;
613 |
614 | return array;
615 |
616 | }
617 |
618 | fromBufferAttribute( attribute, index ) {
619 |
620 | this._x = attribute.getX( index );
621 | this._y = attribute.getY( index );
622 | this._z = attribute.getZ( index );
623 | this._w = attribute.getW( index );
624 |
625 | return this;
626 |
627 | }
628 |
629 | _onChange( callback ) {
630 |
631 | this._onChangeCallback = callback;
632 |
633 | return this;
634 |
635 | }
636 |
637 | _onChangeCallback() {}
638 |
639 | }
--------------------------------------------------------------------------------
/src/math/V2.js:
--------------------------------------------------------------------------------
1 | export class V2 {
2 |
3 | constructor( x = 0, y = 0 ) {
4 |
5 | this.isVector2 = true;
6 | this.x = x;
7 | this.y = y;
8 |
9 | }
10 |
11 | set( x, y ){
12 |
13 | this.x = x || 0;
14 | this.y = y || 0;
15 | return this;
16 |
17 | }
18 |
19 | distanceTo( v ) {
20 |
21 | return Math.sqrt( this.distanceToSquared( v ) );
22 |
23 | }
24 |
25 | distanceToSquared( v ) {
26 |
27 | let dx = this.x - v.x, dy = this.y - v.y;
28 | return dx * dx + dy * dy;
29 |
30 | }
31 |
32 | multiplyScalar( scalar ) {
33 |
34 | this.x *= scalar;
35 | this.y *= scalar;
36 | return this;
37 |
38 | }
39 |
40 | divideScalar( scalar ) {
41 |
42 | return this.multiplyScalar( 1 / scalar );
43 |
44 | }
45 |
46 | length() {
47 |
48 | return Math.sqrt( this.x * this.x + this.y * this.y );
49 |
50 | }
51 |
52 | normalize() {
53 |
54 | return this.divideScalar( this.length() || 1 );
55 |
56 | }
57 |
58 | normalised() {
59 |
60 | return new this.constructor( this.x, this.y ).normalize();
61 |
62 | }
63 |
64 | lengthSq() {
65 |
66 | return this.x * this.x + this.y * this.y;
67 |
68 | }
69 |
70 | add( v ) {
71 |
72 | this.x += v.x;
73 | this.y += v.y;
74 | return this;
75 |
76 | }
77 |
78 | plus( v ) {
79 |
80 | return new this.constructor( this.x + v.x, this.y + v.y );
81 |
82 | }
83 |
84 | min( v ) {
85 |
86 | this.x -= v.x;
87 | this.y -= v.y;
88 | return this;
89 |
90 | }
91 |
92 | minus( v ) {
93 |
94 | return new V2( this.x - v.x, this.y - v.y );
95 |
96 | }
97 |
98 | divideBy( value ) {
99 |
100 | return new this.constructor( this.x, this.y ).divideScalar( value );
101 |
102 | }
103 |
104 | dot( a, b ) {
105 |
106 | return this.x * a.x + this.y * a.y;
107 |
108 | }
109 |
110 | negate() {
111 |
112 | this.x = -this.x;
113 | this.y = -this.y;
114 | return this;
115 |
116 | }
117 |
118 | negated() {
119 |
120 | return new this.constructor( -this.x, -this.y );
121 |
122 | }
123 |
124 | clone (){
125 |
126 | return new this.constructor( this.x, this.y );
127 |
128 | }
129 |
130 | copy( v ) {
131 |
132 | this.x = v.x;
133 | this.y = v.y;
134 | return this;
135 |
136 | }
137 |
138 | cross( v ) {
139 |
140 | return this.x * v.y - this.y * v.x;
141 |
142 | }
143 |
144 | sign( v ) {
145 |
146 | let s = this.cross( v );
147 | return s >= 0 ? 1 : -1;
148 |
149 | }
150 |
151 | approximatelyEquals( v, t ) {
152 |
153 | if ( t < 0 ) return false;
154 | let xDiff = Math.abs(this.x - v.x);
155 | let yDiff = Math.abs(this.y - v.y);
156 | return ( xDiff < t && yDiff < t );
157 |
158 | }
159 |
160 | rotate( angle ) {
161 |
162 | let cos = Math.cos( angle );
163 | let sin = Math.sin( angle );
164 | let x = this.x * cos - this.y * sin;
165 | let y = this.x * sin + this.y * cos;
166 | this.x = x;
167 | this.y = y;
168 | return this;
169 |
170 | }
171 |
172 | angleTo( v ) {
173 |
174 | let a = this.dot(v) / (Math.sqrt( this.lengthSq() * v.lengthSq() ));
175 | if(a <= -1) return Math.PI;
176 | if(a >= 1) return 0;
177 | return Math.acos( a );
178 |
179 | }
180 |
181 | getSignedAngle( v ) {
182 |
183 | let a = this.angleTo( v );
184 | let s = this.sign( v );
185 | return s === 1 ? a : -a;
186 |
187 | }
188 |
189 | constrainedUV( baselineUV, min, max ) {
190 |
191 | let angle = baselineUV.getSignedAngle( this );
192 | if( angle > max ) this.copy( baselineUV ).rotate(max);
193 | if( angle < min ) this.copy( baselineUV ).rotate(min);
194 | return this;
195 |
196 | }
197 |
198 | }
--------------------------------------------------------------------------------
/src/math/V3.js:
--------------------------------------------------------------------------------
1 | export class V3 {
2 |
3 | constructor( x = 0, y = 0, z = 0 ) {
4 |
5 | this.isVector3 = true;
6 | this.x = x;
7 | this.y = y;
8 | this.z = z;
9 |
10 | }
11 |
12 | set( x, y, z ) {
13 |
14 | this.x = x || 0;
15 | this.y = y || 0;
16 | this.z = z || 0;
17 | return this;
18 |
19 | }
20 |
21 | distanceTo( v ) {
22 |
23 | return Math.sqrt( this.distanceToSquared( v ) );
24 |
25 | }
26 |
27 | distanceToSquared( v ) {
28 |
29 | let dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
30 |
31 | return dx * dx + dy * dy + dz * dz;
32 |
33 | }
34 |
35 | abs() {
36 |
37 | return new this.constructor(
38 | this.x < 0 ? -this.x : this.x,
39 | this.y < 0 ? -this.y : this.y,
40 | this.z < 0 ? -this.z : this.z
41 | );
42 |
43 | }
44 |
45 | dot( v ) {
46 |
47 | return this.x * v.x + this.y * v.y + this.z * v.z;
48 |
49 | }
50 |
51 | length() {
52 |
53 | return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
54 |
55 | }
56 |
57 | lengthSq() {
58 |
59 | return this.x * this.x + this.y * this.y + this.z * this.z;
60 |
61 | }
62 |
63 | normalize() {
64 |
65 | return this.divideScalar( this.length() || 1 );
66 |
67 | }
68 |
69 | normalised() {
70 |
71 | return new this.constructor( this.x, this.y, this.z ).normalize();
72 |
73 | }
74 |
75 | add( v ) {
76 |
77 | this.x += v.x;
78 | this.y += v.y;
79 | this.z += v.z;
80 | return this;
81 |
82 | }
83 |
84 | min( v ) {
85 |
86 | this.x -= v.x;
87 | this.y -= v.y;
88 | this.z -= v.z;
89 | return this;
90 |
91 | }
92 |
93 | plus( v ) {
94 |
95 | return new this.constructor( this.x + v.x, this.y + v.y, this.z + v.z );
96 |
97 | }
98 |
99 | minus( v ) {
100 |
101 | return new this.constructor( this.x - v.x, this.y - v.y, this.z - v.z );
102 |
103 | }
104 |
105 | divideBy( s ) {
106 |
107 | return new this.constructor ( this.x / s, this.y / s, this.z / s );
108 |
109 | }
110 |
111 | multiply( s ) {
112 |
113 | return new this.constructor( this.x * s, this.y * s, this.z * s );
114 |
115 | }
116 |
117 |
118 | multiplyScalar( scalar ) {
119 |
120 | this.x *= scalar;
121 | this.y *= scalar;
122 | this.z *= scalar;
123 | return this;
124 |
125 | }
126 |
127 | divideScalar( scalar ) {
128 |
129 | return this.multiplyScalar( 1 / scalar );
130 |
131 | }
132 |
133 | cross( v ) {
134 |
135 | return new this.constructor( this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x );
136 |
137 | }
138 |
139 | crossVectors( a, b ) {
140 |
141 | let ax = a.x, ay = a.y, az = a.z;
142 | let bx = b.x, by = b.y, bz = b.z;
143 |
144 | this.x = ay * bz - az * by;
145 | this.y = az * bx - ax * bz;
146 | this.z = ax * by - ay * bx;
147 |
148 | return this;
149 |
150 | }
151 |
152 | negate() {
153 |
154 | this.x = -this.x;
155 | this.y = -this.y;
156 | this.z = -this.z;
157 | return this;
158 |
159 | }
160 |
161 | negated() {
162 |
163 | return new V3( -this.x, -this.y, -this.z );
164 |
165 | }
166 |
167 | clone() {
168 |
169 | return new V3( this.x, this.y, this.z );
170 |
171 | }
172 |
173 | copy( v ) {
174 |
175 | this.x = v.x;
176 | this.y = v.y;
177 | this.z = v.z;
178 | return this;
179 |
180 | }
181 |
182 | approximatelyEquals( v, t ) {
183 |
184 | if ( t < 0 ) return false;
185 | let xDiff = Math.abs(this.x - v.x);
186 | let yDiff = Math.abs(this.y - v.y);
187 | let zDiff = Math.abs(this.z - v.z);
188 | return ( xDiff < t && yDiff < t && zDiff < t );
189 |
190 | }
191 |
192 | zero() {
193 |
194 | this.x = 0;
195 | this.y = 0;
196 | this.z = 0;
197 | return this;
198 |
199 | }
200 |
201 | /*projectOnPlane_old: function ( planeNormal ) {
202 |
203 | if ( planeNormal.length() <= 0 ){ Tools.error("Plane normal cannot be a zero vector."); return; }
204 |
205 | // Projection of vector b onto plane with normal n is defined as: b - ( b.n / ( |n| squared )) * n
206 | // Note: |n| is length or magnitude of the vector n, NOT its (component-wise) absolute value
207 | let b = this.normalised();
208 | let n = planeNormal.normalised();
209 |
210 | return b.min( n.times( _Math.dotProduct( b, planeNormal ) ) ).normalize();
211 |
212 | },*/
213 |
214 | rotate( angle, axe ) {
215 |
216 | let cos = Math.cos( angle );
217 | let sin = Math.sin( angle );
218 | let x, y, z;
219 |
220 | switch ( axe ){
221 | case 'X':
222 | x = this.x;
223 | y = this.y * cos - this.z * sin;
224 | z = this.y * sin + this.z * cos;
225 | break
226 | case 'Y':
227 | x = this.z * sin + this.x * cos;
228 | y = this.y;
229 | z = this.z * cos - this.x * sin;
230 | break
231 | case 'Z':
232 | x = this.x * cos - this.y * sin;
233 | y = this.x * sin + this.y * cos;
234 | z = this.z;
235 | break
236 | }
237 |
238 | this.x = x;
239 | this.y = y;
240 | this.z = z;
241 | return this;
242 |
243 | }
244 |
245 | // added
246 |
247 | projectOnVector( vector ) {
248 |
249 | let scalar = vector.dot( this ) / vector.lengthSq();
250 | return this.copy( vector ).multiplyScalar( scalar );
251 |
252 | }
253 |
254 | projectOnPlane() {
255 |
256 | let v1 = new this.constructor();
257 |
258 | return function projectOnPlane( planeNormal ) {
259 |
260 | v1.copy( this ).normalised().projectOnVector( planeNormal.normalised() );
261 |
262 | return this.min( v1 ).normalize();
263 |
264 | }
265 |
266 | }
267 |
268 | applyM3( m ) {
269 |
270 | let x = this.x, y = this.y, z = this.z;
271 | let e = m.elements;
272 |
273 | this.x = e[ 0 ] * x + e[ 1 ] * y + e[ 2 ] * z;
274 | this.y = e[ 3 ] * x + e[ 4 ] * y + e[ 5 ] * z;
275 | this.z = e[ 6 ] * x + e[ 7 ] * y + e[ 8 ] * z;
276 |
277 | return this.normalize();
278 |
279 | }
280 |
281 | applyMatrix3( m ) {
282 |
283 | let x = this.x, y = this.y, z = this.z;
284 | let e = m.elements;
285 |
286 | this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
287 | this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
288 | this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;
289 |
290 | return this;
291 |
292 | }
293 |
294 | applyQuaternion( q ) {
295 |
296 | let x = this.x, y = this.y, z = this.z;
297 | let qx = q.x, qy = q.y, qz = q.z, qw = q.w;
298 |
299 | // calculate quat * vector
300 |
301 | let ix = qw * x + qy * z - qz * y;
302 | let iy = qw * y + qz * x - qx * z;
303 | let iz = qw * z + qx * y - qy * x;
304 | let iw = - qx * x - qy * y - qz * z;
305 |
306 | // calculate result * inverse quat
307 |
308 | this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;
309 | this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;
310 | this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;
311 |
312 | return this;
313 |
314 | }
315 |
316 | /////
317 |
318 | sign( v, normal ) {
319 |
320 | let s = this.cross( v ).dot( normal );
321 | return s >= 0 ? 1 : -1;
322 |
323 | }
324 |
325 | angleTo( v ) {
326 |
327 | let a = this.dot(v) / Math.sqrt( this.lengthSq() * v.lengthSq() );
328 | if(a <= -1) return Math.PI;
329 | if(a >= 1) return 0;
330 | return Math.acos( a );
331 |
332 | }
333 |
334 | getSignedAngle( v, normal ) {
335 |
336 | let a = this.angleTo( v );
337 | let s = this.sign( v, normal );
338 | return s === 1 ? a : -a;
339 |
340 | }
341 |
342 | constrainedUV( referenceAxis, rotationAxis, mtx, min, max ) {
343 |
344 | let angle = referenceAxis.getSignedAngle( this, rotationAxis );
345 | if( angle > max ) this.copy( mtx.rotateAboutAxis( referenceAxis, max, rotationAxis ) );
346 | if( angle < min ) this.copy( mtx.rotateAboutAxis( referenceAxis, min, rotationAxis ) );
347 | return this;
348 |
349 | }
350 |
351 | limitAngle( base, mtx, max ) {
352 |
353 | let angle = base.angleTo( this );
354 | if( angle > max ){
355 | let correctionAxis = base.normalised().cross(this).normalize();
356 | this.copy( mtx.rotateAboutAxis( base, max, correctionAxis ) );
357 | }
358 | return this;
359 |
360 | }
361 |
362 |
363 | }
--------------------------------------------------------------------------------
/src/solver/HISolver.js:
--------------------------------------------------------------------------------
1 | //import { NONE, GLOBAL_ROTOR, GLOBAL_HINGE, LOCAL_ROTOR, LOCAL_HINGE, J_BALL, J_GLOBAL, J_LOCAL } from '../constants.js';
2 | import { LOCAL_ABSOLUTE } from '../constants.js';
3 | import { math } from '../math/Math.js';
4 | import { V2 } from '../math/V2.js';
5 | import { Structure2D } from '../core/Structure2D.js';
6 | import { Chain2D } from '../core/Chain2D.js';
7 | import { Bone2D } from '../core/Bone2D.js';
8 |
9 |
10 | export class HISolver {
11 |
12 | constructor( o, THREE ) {
13 |
14 | this.THREE = THREE
15 |
16 | this.isHISolver = true;
17 | this.startBones = null;
18 | this.endBones = null;
19 |
20 | this.scene = o.scene;
21 |
22 | this.target = null;
23 | this.goal = null;
24 | this.swivelAngle = 0;
25 |
26 | this.iteration = 15;
27 |
28 | this.thresholds = { position:0.1, rotation:0.1 };
29 |
30 | this.solver = new Structure2D( this.scene, this.THREE );
31 | //this.chain = null;
32 |
33 | this.bones = [];
34 | this.numBones = 0;
35 |
36 | this.rotation = [];
37 |
38 | this.initStructure( o );
39 |
40 | }
41 |
42 |
43 | initStructure( o ) {
44 |
45 | this.startBones = o.start;
46 | this.endBones = o.end;
47 | this.angles = o.angles;
48 |
49 | let bone = this.startBones, next = bone.children[0];
50 |
51 | this.bones.push(bone);
52 |
53 | for (let i = 0; i<100; i++) {
54 |
55 | this.bones.push(next);
56 | if( next === this.endBones ) { this.createChain(); break }
57 |
58 | bone = next;
59 |
60 | next = bone.children[0];
61 |
62 | }
63 |
64 | }
65 |
66 | createChain() {
67 |
68 | this.numBones = this.bones.length;
69 | let chain = new Chain2D();
70 | //chain.embeddedTarget = new V2();
71 | //chain.useEmbeddedTarget = true;
72 | chain.setFixedBaseMode(true);
73 | chain.setBaseboneConstraintType( LOCAL_ABSOLUTE );
74 |
75 | this.fakeBone = new Bone2D( new V2(0, -1), new V2(0, 0) );
76 |
77 | this.target = new this.THREE.Vector3();
78 |
79 | let base = new this.THREE.Vector3();
80 | let p0 = new this.THREE.Vector3();
81 | let p1 = new this.THREE.Vector3();
82 | let uv = new V2();
83 | let lng = 0
84 |
85 | for (let i = 0; i 0 ){
88 | this.target.add( this.bones[i].position );
89 | lng = base.distanceTo( this.bones[i].position );
90 | this.bones[i-1].getWorldPosition( p0 );
91 | this.bones[i].getWorldPosition( p1 );
92 | p1.sub( p0 ).normalize();
93 |
94 |
95 | if(p1.z === 0 ) uv.set( p1.x, p1.y );
96 | else if(p1.x === 0 ) uv.set( p1.z, p1.y );
97 | //uvs.push( uv );
98 |
99 | //console.log( uv, lng, this.angles[i-1][0], this.angles[i-1][1])
100 |
101 | if(i===1) chain.addBone( new Bone2D( new V2(0, 0), null, uv, lng, this.angles[i-1][0], this.angles[i-1][1] ) );
102 | //else chain.addConsecutiveBone( uv, lng );//, this.angles[i-1][0], this.angles[i-1][1] );
103 | else chain.addConsecutiveBone( uv, lng, this.angles[i-1][0], this.angles[i-1][1] );
104 | }
105 |
106 | }
107 |
108 | //if(this.target.z === 0 ) chain.embeddedTarget.set( this.target.x, this.target.y );
109 | //else if(this.target.x === 0 ) chain.embeddedTarget.set( this.target.z, this.target.y );
110 | this.target.set( 10, 20, 0 )
111 |
112 | this.solver.add( chain, this.target, true );
113 |
114 | //this.solver.chains[0].embeddedTarget.set(10, 10)
115 |
116 |
117 | //console.log( lengths );
118 | //console.log( this.bones, this.target, this.solver.chains[0].bones );
119 |
120 | }
121 |
122 | update() {
123 |
124 | this.solver.update();
125 |
126 | let bones2d = this.solver.chains[0].bones;
127 | let n = this.numBones-1;
128 |
129 | let a;
130 |
131 | for(let i = 0; i < n; i++){
132 |
133 | a = i===0 ? math.findAngle( this.fakeBone, bones2d[i] ) : math.findAngle( bones2d[i-1], bones2d[i] );
134 | this.rotation[i] = a * math.toDeg;
135 | this.rotation[i] += a < 0 ? 180 : -180;
136 | this.rotation[i] *= -1;
137 |
138 | }
139 |
140 | for( let i = 0; i < n; i++ ){
141 | this.bones[i].rotation.z = this.rotation[i] * math.toRad;
142 | }
143 |
144 | //console.log(this.rotation)
145 | //let r = FIK.math.findAngle(bones[0], bones[1]);
146 |
147 | }
148 |
149 | }
--------------------------------------------------------------------------------
/src/solver/IKSolver.js:
--------------------------------------------------------------------------------
1 | //import { NONE, GLOBAL_ROTOR, GLOBAL_HINGE, LOCAL_ROTOR, LOCAL_HINGE, J_BALL, J_GLOBAL, J_LOCAL } from '../constants.js';
2 |
3 | import { V3 } from '../math/V3.js';
4 | import { V2 } from '../math/V2.js';
5 | import { Structure3D } from '../core/Structure3D.js';
6 | import { Chain3D } from '../core/Chain3D.js';
7 | import { Bone3D } from '../core/Bone3D.js';
8 |
9 | export class IKSolver {
10 |
11 | constructor( o ) {
12 |
13 | this.isIKSolver = true;
14 | this.startBones = null;
15 | this.endBones = null;
16 |
17 | this.target = null;
18 | this.goal = null;
19 | this.swivelAngle = 0;
20 |
21 | this.iteration = 40;
22 |
23 | this.thresholds = { position:0.1, rotation:0.1 };
24 |
25 | this.solver = null;
26 | this.chain = null;
27 |
28 | }
29 |
30 | }
--------------------------------------------------------------------------------