├── .eslintignore
├── .eslintrc
├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── _images
└── Epic_MegaGrants_Recipient_logo.png
├── changeLog.txt
├── examples
├── _demo
│ ├── arm.html
│ ├── char_rig.html
│ ├── index.html
│ ├── lib
│ │ ├── CharacterRig.js
│ │ ├── IKPoseVisualizer.js
│ │ ├── MixamoIKAnimatorRig.js
│ │ └── Proto.js
│ ├── ready_player_me.html
│ ├── rigs
│ │ ├── IKRig.js
│ │ ├── LigerZeroRig.js
│ │ ├── NabbaRig.js
│ │ ├── ReadyPlayerRig.js
│ │ ├── Ronin.js
│ │ ├── TRex.js
│ │ ├── TinaRig.js
│ │ └── ToruRig.js
│ └── tail_procedural_anim.html
├── _lib
│ ├── Cycle.js
│ └── propui
│ │ ├── PropGroup.js
│ │ ├── PropSelect.js
│ │ ├── PropUI.css
│ │ ├── PropUtil.js
│ │ └── index.js
├── _res
│ └── _setup.txt
├── babylonjs
│ ├── 001_skin_mtx.html
│ ├── 002_animation.html
│ ├── 005_ikrig.html
│ ├── 006_ik_retarget.html
│ ├── _000_debug.html
│ ├── _000_debug_mesh.html
│ ├── _000_template.html
│ ├── _lib
│ │ ├── BoneViewMaterial.js
│ │ ├── BoneViewMesh.js
│ │ ├── DynLineMesh.js
│ │ ├── ShapePointsMesh.js
│ │ ├── SkinMTXMaterial.js
│ │ ├── Starter.css
│ │ ├── Starter.js
│ │ ├── Util.js
│ │ ├── UtilArm.js
│ │ ├── UtilGltf2.js
│ │ └── babylon.gridMaterial.min.js
│ └── index.html
└── threejs
│ ├── 000_gltf2_mesh.html
│ ├── 001_skin_dq.html
│ ├── 001_skin_dqt.html
│ ├── 001_skin_mtx.html
│ ├── 001_skin_rts.html
│ ├── 002_animation.html
│ ├── 002_animation_bvh.html
│ ├── 003_retarget_and_springs.html
│ ├── 004_data_texture.html
│ ├── 004_data_texture_with_inst.html
│ ├── 005_ikrig.html
│ ├── 006_ik_retarget.html
│ ├── 006_ik_retarget_bvh.html
│ ├── 007_ik_solvers.html
│ ├── 007_ik_solvers_sdf.html
│ ├── 008_tentacle.html
│ ├── 999_tpose.html
│ ├── _000_template.html
│ ├── _lib
│ ├── BoneDirMesh.js
│ ├── BoneViewMaterial.js
│ ├── BoneViewMesh.js
│ ├── DynLineMesh.js
│ ├── FacedCube.js
│ ├── ShapePointsMesh.js
│ ├── SkinDQMaterial.js
│ ├── SkinDQTMaterial.js
│ ├── SkinMTXMaterial.js
│ ├── SkinRTSMaterial.js
│ ├── SkinRTS_MTXMaterial.js
│ ├── Starter.css
│ ├── Starter.js
│ ├── Util.js
│ ├── UtilArm.js
│ ├── UtilBvh.js
│ └── UtilGltf2.js
│ └── index.html
├── index.html
├── notes.txt
├── package.json
├── prototypes
├── ik_closeloop_fabrik.html
├── ik_closeloop_fabrikBAK.html
├── ik_jacobian.html
├── ik_range_fabrik.html
└── spring.js
├── src
├── animation
│ ├── Animator.ts
│ ├── Clip.ts
│ ├── Retarget.ts
│ ├── TypePool.ts
│ ├── index.ts
│ └── tracks
│ │ ├── QuatTrack.ts
│ │ ├── Vec3Track.ts
│ │ └── types.ts
├── armature
│ ├── Armature.ts
│ ├── Bone.ts
│ ├── BoneMap.ts
│ ├── BoneSlots.ts
│ ├── Pose.ts
│ ├── index.ts
│ └── skins
│ │ ├── ISkin.ts
│ │ ├── SkinDQ.ts
│ │ ├── SkinDQT.ts
│ │ ├── SkinMTX.ts
│ │ └── SkinRTS.ts
├── bonespring
│ ├── SpringChain.ts
│ ├── SpringItem.ts
│ ├── SpringPos.ts
│ ├── SpringRot.ts
│ ├── implicit_euler
│ │ ├── SpringBase.ts
│ │ ├── SpringFloat.ts
│ │ ├── SpringQuat.ts
│ │ └── SpringVec3.ts
│ └── index.ts
├── ikrig
│ ├── IKData.ts
│ ├── animation
│ │ ├── BipedIKPose.ts
│ │ ├── additives
│ │ │ ├── EffectorScale.ts
│ │ │ ├── IKPoseAdditives.ts
│ │ │ ├── PositionOffset.ts
│ │ │ └── index.ts
│ │ └── support
│ │ │ └── IIKPoseAdditive.ts
│ ├── index.ts
│ ├── rigs
│ │ ├── BipedRig.ts
│ │ ├── IKChain.ts
│ │ ├── IKRig.ts
│ │ └── QuadrupedRig.ts
│ └── solvers
│ │ ├── ArcSinSolver.ts
│ │ ├── ArcSolver.ts
│ │ ├── CatenarySolver.ts
│ │ ├── FabrikSolver.ts
│ │ ├── HipSolver.ts
│ │ ├── LimbSolver.ts
│ │ ├── NaturalCCDSolver.ts
│ │ ├── PistonSolver.ts
│ │ ├── SpringSolver.ts
│ │ ├── SwingTwistEndsSolver.ts
│ │ ├── SwingTwistSolver.ts
│ │ ├── TrapezoidSolver.ts
│ │ ├── ZSolver.ts
│ │ ├── index.ts
│ │ └── support
│ │ ├── ISolver.ts
│ │ └── SwingTwistBase.ts
├── maths
│ ├── CurveSample.ts
│ ├── DualQuatUtil.ts
│ ├── Mat4Util.ts
│ ├── Maths.ts
│ ├── QuatUtil.ts
│ ├── Transform.ts
│ ├── Vec3Util.ts
│ ├── Vec4Util.ts
│ └── index.ts
├── ossos.ts
└── parsers
│ ├── bvh
│ ├── Animation.ts
│ ├── Skin.ts
│ └── index.ts
│ └── gltf2
│ ├── Accessor.ts
│ ├── Animation.ts
│ ├── Glb.ts
│ ├── Mesh.ts
│ ├── Pose.ts
│ ├── Skin.ts
│ ├── Texture.ts
│ ├── index.ts
│ ├── structs.ts
│ └── types.ts
├── tsconfig.json
└── vite.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root" : true,
3 | "parser" : "@typescript-eslint/parser",
4 | "plugins" : [
5 | "@typescript-eslint"
6 | ],
7 | "extends" : [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/eslint-recommended",
10 | "plugin:@typescript-eslint/recommended"
11 | ],
12 | "rules" : {
13 | "@typescript-eslint/no-inferrable-types" : "off"
14 | }
15 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [sketchpunklabs, sketchpunk]
2 | patreon: sketchpunk
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Sketchpunk Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_images/Epic_MegaGrants_Recipient_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sketchpunklabs/ossos/d1325d878f4875d0eafe42f8861a853263d57081/_images/Epic_MegaGrants_Recipient_logo.png
--------------------------------------------------------------------------------
/changeLog.txt:
--------------------------------------------------------------------------------
1 | VERSION : 0.0.0.4
2 |
3 | New
4 | - Bvh parsing of Skin & Animations
5 |
6 | Changes
7 | - Animator has inPlaceScale to set which axes to zero out for inPlace Animation, before it would zero out Y Axis for Mixamo Animations
8 | - IKChain.bindAltDirections, effectorDir & poleDir are option, so can call this method to modify just one direction instead of both.
9 | - BipedIKPose has inPlace & inPlaceScale added to cancel out hip position change changes
10 |
11 | =========================================================================
12 | VERSION : 0.0.0.3
13 |
14 | New
15 | - Armature object can be cloned
16 | - New IK Solvers:
17 | - ArcSolver
18 | - ArcSinSolver
19 | - PistonSolver
20 | - SpringSolver
21 | - TrapezoidSolver
22 | - ZSolver
23 | - FabrikSolver
24 | - NaturalCCDSolver
25 | - CatenarySolver
26 | - BabylonJS Examples
27 | - ReadyPlayerMe : Loading & TPose Generation
28 | - Quadruped IK Rig ( Prototype )
29 | - Bone Slots : Attachment locations to bones
30 | - IK Animation Additives
31 |
32 | Changes
33 | - Bone.pidx is no longer nullable. -1 is used to denote there is no parent
34 | - Pose.getWorldTransform & getWorldRotation handles bIdx == -1 by returning Pose.offset value.
35 | - SwingTwistSolver doesn't apply twist if pole direction is all zeros
36 | - BoneSprings setRestPose has a parameter to not reset the current springs velocity and position.
37 |
38 | Fixes
39 | - Update the codebase to handle the Bone.pidx change.
40 | - Bug in BoneSprings where reset is called, it was zeroing out the input data which caused issues.
41 |
42 | =========================================================================
--------------------------------------------------------------------------------
/examples/_demo/arm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
152 |
--------------------------------------------------------------------------------
/examples/_demo/char_rig.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
110 |
--------------------------------------------------------------------------------
/examples/_demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | - Rig Animation
12 | - Procedural Tail Animation
13 | - Ready Player Me
14 | - Animating Arm
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/_demo/lib/IKPoseVisualizer.js:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import { vec3 } from 'gl-matrix'
3 | import Transform from '../../../src/maths/Transform';
4 | //#endregion IMPORT
5 |
6 | const V0 = vec3.create();
7 | const V1 = vec3.create();
8 | const T0 = new Transform();
9 |
10 | let debug = null;
11 |
12 | class IKPoseVisualizer{
13 | static show( debug, rig, pose, ikpose ){
14 | debug.pnt.reset();
15 | debug.ln.reset();
16 |
17 | this.limb( debug, rig.legL, pose, ikpose.legL );
18 | this.limb( debug, rig.legR, pose, ikpose.legR );
19 | this.limb( debug, rig.armR, pose, ikpose.armR );
20 | this.limb( debug, rig.armL, pose, ikpose.armL );
21 |
22 | this.swingTwist( debug, rig.footL, pose, ikpose.footL );
23 | this.swingTwist( debug, rig.footR, pose, ikpose.footR );
24 | this.swingTwist( debug, rig.handR, pose, ikpose.handR );
25 | this.swingTwist( debug, rig.handL, pose, ikpose.handL );
26 | this.swingTwist( debug, rig.head, pose, ikpose.head );
27 |
28 | this.swingTwistEnds( debug, rig.spine, pose, ikpose.spine );
29 |
30 | this.hip( debug, rig.hip, pose, ikpose.hip );
31 | }
32 |
33 | static limb( debug, chain, pose, ik ){
34 | const p0 = chain.getStartPosition( pose );
35 |
36 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | // Effector
38 | vec3.scaleAndAdd( V0, p0, ik.effectorDir, ik.lenScale * chain.length );
39 |
40 | debug.pnt.add( p0, 0x00ff00, 1.3 );
41 | debug.pnt.add( V0, 0x00ffff, 1.3 );
42 | debug.ln.add( p0, V0, 0x00ff00, 0x00ffff, true );
43 |
44 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | // Pole
46 | vec3.scaleAndAdd( V0, p0, ik.poleDir, 0.2 );
47 | debug.ln.add( p0, V0, 0x00ff00 );
48 | }
49 |
50 | static swingTwist( debug, chain, pose, ik ){
51 | const p0 = chain.getStartPosition( pose );
52 |
53 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | // Effector
55 | vec3.scaleAndAdd( V0, p0, ik.effectorDir, 0.2 );
56 | debug.ln.add( p0, V0, 0x00ffff );
57 |
58 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59 | // Pole
60 | vec3.scaleAndAdd( V0, p0, ik.poleDir, 0.2 );
61 | debug.ln.add( p0, V0, 0x00ff00 );
62 | }
63 |
64 | static swingTwistEnds( debug, chain, pose, ik ){
65 | const p0 = chain.getStartPosition( pose );
66 | const p1 = chain.getLastPosition( pose );
67 |
68 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | vec3.scaleAndAdd( V0, p0, ik.startEffectorDir, 0.12 ); // Effector
70 | debug.ln.add( p0, V0, 0x00ffff );
71 |
72 | vec3.scaleAndAdd( V0, p0, ik.startPoleDir, 0.12 ); // Pole
73 | debug.ln.add( p0, V0, 0x00ff00 );
74 |
75 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76 | vec3.scaleAndAdd( V0, p1, ik.endEffectorDir, 0.12 ); // Effector
77 | debug.ln.add( p1, V0, 0x00ffff );
78 |
79 | vec3.scaleAndAdd( V0, p1, ik.endPoleDir, 0.12 ); // Pole
80 | debug.ln.add( p1, V0, 0x00ff00 );
81 | }
82 |
83 | static hip( debug, chain, pose, ik ){
84 | const lnk = chain.first();
85 | const b = pose.bones[ lnk.idx ];
86 |
87 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88 | // Position Offset
89 | if( b.pidx == -1 ) T0.fromMul( pose.offset, lnk.bind ); // Use Offset if there is no parent
90 | else pose.getWorldTransform( lnk.pidx, T0 ).mul( lnk.bind ); // Compute Parent's WorldSpace transform, then add local bind pose to it.
91 |
92 | vec3.scaleAndAdd( V0, T0.pos, ik.pos, ik.bindHeight / T0.pos[ 1 ] );
93 |
94 | debug.pnt.add( T0.pos, 0x00ff00, 0.5 ); // Bind Position
95 | debug.pnt.add( b.world.pos, 0x00ffff, 0.5 ); // Pose Position
96 | debug.pnt.add( V0, 0x000000, 0.3 ); // Scaled Offset plus Bind Position
97 | debug.ln.add( T0.pos, V0, 0x00ff00, 0x000000 ); // Original to Animated Position
98 |
99 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100 | // IK Direction
101 | vec3.scaleAndAdd( V1, V0, ik.effectorDir, 0.1 );
102 | debug.ln.add( V0, V1, 0x00ffff );
103 |
104 | vec3.scaleAndAdd( V1, V0, ik.poleDir, 0.1 );
105 | debug.ln.add( V0, V1, 0x00ff00 );
106 | }
107 |
108 | }
109 |
110 | export default IKPoseVisualizer;
--------------------------------------------------------------------------------
/examples/_demo/lib/MixamoIKAnimatorRig.js:
--------------------------------------------------------------------------------
1 | import { BipedRig, BipedIKPose } from '../../../src/ikrig/index';
2 | import { Animator } from '../../../src/animation/index';
3 | import { Gltf2 } from '../../threejs/_lib/UtilGltf2.js';
4 | import UtilArm from '../../threejs/_lib/UtilArm.js';
5 |
6 | class MixamoIKAnimatorRig{
7 | //#region MAIN
8 | animator = new Animator();
9 | ikPose = new BipedIKPose();
10 | arm = null;
11 | rig = null;
12 | boneView = null;
13 | pose = null;
14 |
15 | onTick = null;
16 | clips = new Map();
17 |
18 | constructor(){
19 | this.animator.inPlace = true;
20 | }
21 | //#endregion
22 |
23 | //#region LOADING
24 | async loadAsync( aryUrl ){
25 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26 | const ary = [];
27 | for( let i of aryUrl ) ary.push( Gltf2.fetch( i ) );
28 |
29 | const gltf = await Promise.all( ary );
30 |
31 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 | this.setupArmature( gltf[ 0 ] );
33 | this.setupIKRig();
34 | this.setupBoneView();
35 |
36 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | for( let i=0; i < gltf.length; i++ ){
38 | this.loadClip( gltf[ i ], (i == 0) );
39 | }
40 | return this;
41 | }
42 |
43 | setupArmature( gltf ){
44 | this.arm = UtilArm.armFromGltf( gltf, 0.07 );
45 | this.pose = this.arm.newPose();
46 | this.pose
47 | .updateWorld() // Mixamo Stuff has an Offset Transform, Compute Proper WS Transforms...
48 | .updateBoneLengths( 0.01 ); // Then use it to get the correct bone lengths for use in IK
49 | }
50 |
51 | setupIKRig(){
52 | this.rig = new BipedRig();
53 | if( !this.rig.autoRig( this.arm ) ) console.log( 'AutoRig was Incomplete' );
54 |
55 | this.rig
56 | .bindPose( this.pose ) // Setup Chains & Alt Directions, Pose should be a TPose of the character
57 | .updateBoneLengths( this.pose ) // Apply BoneLengths to Rig since they're different from ARM.
58 | .useSolversForRetarget( this.pose ); // Setup Solvers
59 | }
60 |
61 | setupBoneView(){
62 | this.boneView = UtilArm.newBoneView( this.arm, this.pose, 2, 1 );
63 | }
64 |
65 | loadClip( gltf, autoLoad=false ){
66 | const clip = UtilArm.clipFromGltf( gltf );
67 | this.clips.set( clip.name, clip );
68 |
69 | // console.log( '- Load Clip : ', clip.name );
70 |
71 | if( autoLoad ) this.animator.setClip( clip );
72 | }
73 | //#endregion
74 |
75 | toScene( app ){
76 | if( this.boneView ) app.add( this.boneView );
77 | }
78 |
79 | useClip( clipName ){
80 | const clip = this.clips.get( clipName );
81 | if( clip ){
82 | this.animator.setClip( clip ).resetClock();
83 | }
84 | }
85 |
86 | tick( dt ){
87 | this.animator
88 | .update( dt ) // Move Animation Forward
89 | .applyPose( this.pose ); // Apply Animation local space transform to Pose
90 |
91 | this.pose.updateWorld(); // Update the Pose's WorldSpace Transforms
92 | this.boneView.updateFromPose( this.pose ); // Update Source's Bone View Rendering
93 |
94 | this.ikPose.computeFromRigPose( this.rig, this.pose ); // Compute IK Pose Data from Animation Pose
95 | if( this.onTick ) this.onTick( this, dt );
96 | }
97 | }
98 |
99 | export default MixamoIKAnimatorRig;
--------------------------------------------------------------------------------
/examples/_demo/lib/Proto.js:
--------------------------------------------------------------------------------
1 | import MixamoIKAnimatorRig from './MixamoIKAnimatorRig.js';
2 |
3 | class Proto{
4 | static Debug = null;
5 | static IKVisualizer = null;
6 | static mixamo = new MixamoIKAnimatorRig();
7 | static mixamoReady = false;
8 | static rigs = new Map();
9 |
10 | static tick( dt, et ){
11 | if( this.mixamoReady ){
12 | this.mixamo.tick( dt );
13 |
14 | // Visualize IK Data Over Src Bone View
15 | if( this.IKVisualizer ) this.IKVisualizer.show( this.Debug, this.mixamo.rig, this.mixamo.pose, this.mixamo.ikPose );
16 |
17 | let rig;
18 | for( rig of this.rigs.values() ){
19 | rig.applyIKPose( this.mixamo.ikPose, dt );
20 | }
21 | }
22 | }
23 |
24 | static initMixamo( app, itms=null ){
25 | const all = [
26 | '../_res/anim/Walking.gltf',
27 | '../_res/anim/Catwalk.gltf',
28 | '../_res/anim/Ready.gltf',
29 | '../_res/anim/Running.gltf',
30 | '../_res/anim/Standing.gltf',
31 | '../_res/anim/Rumba.gltf',
32 | '../_res/anim/Hiphop.gltf',
33 | ];
34 |
35 | if( !itms ) itms = all; // Load All Animations
36 | else if( typeof itms == 'number' ) itms = [ all[ itms ] ]; // Load a specific one off the list
37 | else if( typeof itms == 'string' ) itms = [ itms ]; // Load a custom url to load one animation
38 |
39 | return this.mixamo
40 | .loadAsync( itms )
41 | .then( rig=>{
42 | rig.toScene( app );
43 | Proto.mixamoReady = true;
44 | return rig;
45 | });
46 | }
47 |
48 | static async initIKVisualizer(){
49 | const mod = await import( './IKPoseVisualizer.js' );
50 | this.IKVisualizer = mod.default;
51 | }
52 |
53 | static async initDebug( app, pnt=true, ln=true ){
54 | const debug = {};
55 | const ary = [];
56 | if( pnt ) ary.push( import( '../../threejs/_lib/ShapePointsMesh.js' ).then( m=>{ debug.pnt = new m.default(); } ) ); //
57 | if( ln ) ary.push( import( '../../threejs/_lib/DynLineMesh.js' ).then( m=>{ debug.ln = new m.default(); } ) );
58 |
59 | await Promise.all( ary );
60 | if( debug.pnt ) app.add( debug.pnt );
61 | if( debug.ln ) app.add( debug.ln );
62 |
63 | this.Debug = debug;
64 | return debug;
65 | }
66 |
67 | static initKeyboardClip(){
68 | window.addEventListener( 'keypress', (e)=>{
69 | switch( e.key ){
70 | case '1': Proto.mixamo.useClip( 'Walking' ); break;
71 | case '2': Proto.mixamo.useClip( 'Running' ); break;
72 | case '3': Proto.mixamo.useClip( 'Catwalk' ); break;
73 | case '4': Proto.mixamo.useClip( 'Ready' ); break;
74 | case '5': Proto.mixamo.useClip( 'Standing' ); break;
75 | case '6': Proto.mixamo.useClip( 'Rumba' ); break;
76 | case '7': Proto.mixamo.useClip( 'Hiphop' ); break;
77 | }
78 | });
79 |
80 | return this;
81 | }
82 |
83 | static async loadRig( app, name, config=null ){
84 | const mod = await import( `../rigs/${name}.js` );
85 | const rig = new mod.default();
86 |
87 | await rig.loadAsync( config );
88 | rig.toScene( app );
89 |
90 | this.rigs.set( name, rig );
91 |
92 | return rig;
93 | }
94 | }
95 |
96 | export default Proto;
--------------------------------------------------------------------------------
/examples/_demo/rigs/NabbaRig.js:
--------------------------------------------------------------------------------
1 | import CharacterRig, { Gltf2 } from '../lib/CharacterRig.js';
2 |
3 | class NabbaRig extends CharacterRig{
4 | constructor(){ super(); }
5 |
6 | async loadAsync( config=null ){
7 | const gltf = await Gltf2.fetch( '../_res/models/nabba/nabba.gltf' );
8 | this._parseArm( gltf, false ) // Create Armature
9 | ._autoRig() // Auto BipedRig
10 |
11 | if( config?.mesh != false ) this._skinnedMesh( gltf, 'cyan', config );
12 | if( config?.boneView ) this._boneView( config );
13 | return this;
14 | }
15 | }
16 |
17 | export default NabbaRig;
--------------------------------------------------------------------------------
/examples/_demo/rigs/Ronin.js:
--------------------------------------------------------------------------------
1 | import CharacterRig, { Gltf2 } from '../lib/CharacterRig.js';
2 |
3 | import HipSolver from '../../../src/ikrig/solvers/HipSolver';
4 | import ZSolver from '../../../src/ikrig/solvers/ZSolver';
5 | import SwingTwistSolver from '../../../src/ikrig/solvers/SwingTwistSolver';
6 | import SwingTwistEndsSolver from '../../../src/ikrig/solvers/SwingTwistEndsSolver';
7 | import LimbSolver from '../../../src/ikrig/solvers/LimbSolver';
8 |
9 | import EffectorScale from '../../../src/ikrig/animation/additives/EffectorScale';
10 |
11 | class RoninRig extends CharacterRig{
12 | constructor(){ super(); }
13 |
14 | async loadAsync( config=null ){
15 | const url = '../_res/models/ronin/';
16 | const gltf = await Gltf2.fetch( url + 'ronin.gltf' );
17 | this._parseArm( gltf, false ); // Create Armature
18 | this._bipedRig();
19 | this._setupRig();
20 | this._ikAdditives();
21 |
22 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~23
23 | // Legs are too Long for human animatons, Scale down the EffectorScale
24 | // value to make the leg lengths to match up with the floor.
25 | const add_leg_scl = new EffectorScale( 0.92 );
26 | this.additives.add( 'legL', add_leg_scl );
27 | this.additives.add( 'legR', add_leg_scl );
28 |
29 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30 | if( config?.boneView ) this._boneView( config, 0.1, 1.5 );
31 |
32 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33 | if( config?.mesh != false ){
34 | let base = 'cyan';
35 | if( config?.tex != false ) base = await this._texture( url + 'WP_albedo.jpg' );
36 | this._skinnedMesh( gltf, base, config );
37 | }
38 |
39 | return this;
40 | }
41 |
42 | _setupRig(){
43 | const r = this.rig;
44 | const arm = this.arm;
45 | const pose = this.pose;
46 |
47 | const FWD = [0,0,1];
48 | const UP = [0,1,0];
49 | const DN = [0,-1,0];
50 | const R = [-1,0,0];
51 | const L = [1,0,0];
52 | const BAK = [0,0,-1];
53 |
54 | r.hip = r.add( arm, 'hip', ['def_c_hip'] );
55 | r.hip.bindAltDirections( pose, FWD, UP );
56 | r.hip.setSolver( new HipSolver().initData( pose, r.hip ) );
57 |
58 | r.spine = r.add( arm, 'spine', ['def_c_spineA', 'def_c_spineB', 'def_c_spineC'] );
59 | r.spine.bindAltDirections( pose, UP, FWD );
60 | r.spine.setSolver( new SwingTwistEndsSolver().initData( pose, r.spine ) );
61 |
62 | r.legL = r.add( arm, 'legL', ['def_l_thigh', 'def_l_thighLow', 'def_l_knee'] );
63 | r.legL.bindAltDirections( pose, DN, FWD );
64 | r.legL.setSolver( new ZSolver().initData( pose, r.legL ) );
65 |
66 | r.legR = r.add( arm, 'legR', ['def_r_thigh', 'def_r_thighLow', 'def_r_knee'] );
67 | r.legR.bindAltDirections( pose, DN, FWD );
68 | r.legR.setSolver( new ZSolver().initData( pose, r.legR ) );
69 |
70 | r.footL = r.add( arm, 'footL', ['def_l_ankle'] );
71 | r.footL.bindAltDirections( pose, FWD, UP );
72 | r.footL.setSolver( new SwingTwistSolver().initData( pose, r.footL ) );
73 |
74 | r.footR = r.add( arm, 'footR', ['def_r_ankle'] );
75 | r.footR.bindAltDirections( pose, FWD, UP );
76 | r.footR.setSolver( new SwingTwistSolver().initData( pose, r.footR ) );
77 |
78 | r.armL = r.add( arm, 'armL', ['def_l_shoulder', 'def_l_elbow'] );
79 | r.armL.bindAltDirections( pose, L, BAK );
80 | r.armL.setSolver( new LimbSolver().initData( pose, r.armL ) );
81 |
82 | r.armR = r.add( arm, 'armR', ['def_r_shoulder', 'def_r_elbow'] );
83 | r.armR.bindAltDirections( pose, R, BAK );
84 | r.armR.setSolver( new LimbSolver().initData( pose, r.armR ) );
85 |
86 | r.handL = r.add( arm, 'handL', ['def_l_wrist'] );
87 | r.handL.bindAltDirections( pose, L, BAK );
88 | r.handL.setSolver( new SwingTwistSolver().initData( pose, r.handL ) );
89 |
90 | r.handR = r.add( arm, 'handR', ['def_r_wrist'] );
91 | r.handR.bindAltDirections( pose, R, BAK );
92 | r.handR.setSolver( new SwingTwistSolver().initData( pose, r.handR ) );
93 |
94 | return this;
95 |
96 | }
97 | }
98 |
99 | export default RoninRig;
--------------------------------------------------------------------------------
/examples/_demo/rigs/TinaRig.js:
--------------------------------------------------------------------------------
1 | import CharacterRig, { Gltf2 } from '../lib/CharacterRig.js';
2 |
3 | class TinaRig extends CharacterRig{
4 | constructor(){ super(); }
5 |
6 | async loadAsync( config=null ){
7 | const url = '../_res/models/tina/';
8 | const gltf = await Gltf2.fetch( url + 'tina.gltf' );
9 | this._parseArm( gltf, true ) // Create Armature
10 | ._autoRig() // Auto BipedRig
11 |
12 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13 | if( config?.boneView ) this._boneView( config );
14 |
15 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 | if( config?.mesh != false ){
17 | let base = 'cyan';
18 | if( config?.tex != false ) base = await this._texture( url + 'initialShadingGroup_albedo.jpg' );
19 | this._skinnedMesh( gltf, base, config );
20 | }
21 |
22 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 | if( config?.springs != false ){
24 | this._boneSprings();
25 | this.springs
26 | .addRotChain( 'braidr', ["hair.L.002","hair.L.004","hair.L.003","hair.L.005"], 3, 0.8 )
27 | .addRotChain( 'braidl', ["hair.R.002","hair.R.004","hair.R.003","hair.R.005"], 3, 0.8 )
28 | .addPosChain( "boot1", [ "breast.L" ], 3, 0.2 )
29 | .addPosChain( "boot2", [ "breast.R" ], 3, 0.2 )
30 | ;
31 |
32 | this.springs.setRestPose( this.pose ); // Set the resting pose of the springs
33 | }
34 |
35 | return this;
36 | }
37 | }
38 |
39 | export default TinaRig;
--------------------------------------------------------------------------------
/examples/_demo/rigs/ToruRig.js:
--------------------------------------------------------------------------------
1 | import CharacterRig, { Gltf2, UtilArm } from '../lib/CharacterRig.js';
2 | import * as THREE from 'three';
3 |
4 | class ToruRig extends CharacterRig{
5 | constructor(){ super(); }
6 |
7 | async loadAsync( config=null ){
8 | const url = '../_res/models/toru/';
9 | const gltf = await Gltf2.fetch( url + 'toru.gltf' );
10 |
11 | this._parseArm( gltf, true ) // Create Armature
12 | ._autoRig() // Auto BipedRig
13 |
14 | console.log( this.arm );
15 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 | if( config?.boneView ) this._boneView( config );
17 |
18 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19 | if( config?.mesh != false ){
20 | let base = 'cyan';
21 |
22 | if( config?.tex != false ){
23 | if( config?.tex != false ) base = await this._texture( url + 'MKDM001_Toru.png' );
24 | }
25 |
26 | this._skinnedMesh( gltf, base, config );
27 | }
28 |
29 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30 | if( config?.springs != false ){
31 | const skirt_ops = 2.0;
32 | const skirt_damp = 0.5;
33 |
34 | this._boneSprings();
35 | this.springs
36 | .addPosChain( 'breastl', [ 'Breast.L' ], 2.5, 0.01 )
37 | .addPosChain( 'breastr', [ 'Breast.R' ], 2.5, 0.01 )
38 |
39 | .addRotChain( 'Hair_FL', [ 'Hair_FL' ], 2.5, 0.4 )
40 | .addRotChain( 'Hair_FR', [ 'Hair_FR' ], 2.5, 0.4 )
41 |
42 | .addRotChain( 'Bow_L', [ 'Bow_L' ], 2.5, 0.4 )
43 | .addRotChain( 'Bow_R', [ 'Bow_R' ], 2.5, 0.4 )
44 |
45 | .addRotChain( 'tail', ['Tail_01','Tail_02','Tail_03','Tail_04'], 2, 0.6 )
46 | .addRotChain( 'ptailr', ['Hair_R_01','Hair_R_02','Hair_R_03','Hair_R_04'], 2.5, 0.5 )
47 | .addRotChain( 'ptaill', ['Hair_L_01','Hair_L_02','Hair_L_03','Hair_L_04'], 2.5, 0.5 )
48 |
49 | .addRotChain( 'Skirt_F', ['Skirt_F_01','Skirt_F_02','Skirt_F_03'], skirt_ops, skirt_damp )
50 | .addRotChain( 'Skirt_FL', ['Skirt_FL_01','Skirt_FL_02','Skirt_FL_03'], skirt_ops, skirt_damp )
51 | .addRotChain( 'Skirt_FR', ['Skirt_FR_01','Skirt_FR_02','Skirt_FR_03'], skirt_ops, skirt_damp )
52 | .addRotChain( 'Skirt_L', ['Skirt_L_01','Skirt_L_02','Skirt_L_03'], skirt_ops, skirt_damp )
53 | .addRotChain( 'Skirt_R', ['Skirt_R_01','Skirt_R_02','Skirt_R_03'], skirt_ops, skirt_damp )
54 | .addRotChain( 'Skirt_B', ['Skirt_B_01','Skirt_B_02','Skirt_B_03'], skirt_ops, skirt_damp )
55 | .addRotChain( 'Skirt_BL', ['Skirt_BL_01','Skirt_BL_02','Skirt_BL_03'], skirt_ops, skirt_damp )
56 | .addRotChain( 'Skirt_BR', ['Skirt_BR_01','Skirt_BR_02','Skirt_BR_03'], skirt_ops, skirt_damp )
57 | ;
58 |
59 | this.springs.setRestPose( this.pose ); // Set the resting pose of the springs
60 | }
61 | // 26: {"Bow_L" => 26}
62 | // 27: {"Bow_R" => 27}
63 |
64 | // 1: {"Skirt_B_01" => 1}
65 | // 2: {"Skirt_B_02" => 2}
66 | // 3: {"Skirt_B_03" => 3}
67 | // 4: {"Skirt_BL_01" => 4}
68 | // 5: {"Skirt_BL_02" => 5}
69 | // 6: {"Skirt_BL_03" => 6}
70 | // 7: {"Skirt_BR_01" => 7}
71 | // 8: {"Skirt_BR_02" => 8}
72 | // 9: {"Skirt_BR_03" => 9}
73 |
74 | // 10: {"Skirt_F_01" => 10}
75 | // 11: {"Skirt_F_02" => 11}
76 | // 12: {"Skirt_F_03" => 12}
77 | // 13: {"Skirt_FL_01" => 13}
78 | // 14: {"Skirt_FL_02" => 14}
79 | // 15: {"Skirt_FL_03" => 15}
80 | // 16: {"Skirt_FR_01" => 16}
81 | // 17: {"Skirt_FR_02" => 17}
82 | // 18: {"Skirt_FR_03" => 18}
83 | // 19: {"Skirt_L_01" => 19}
84 | // 20: {"Skirt_L_02" => 20}
85 | // 21: {"Skirt_L_03" => 21}
86 | // 22: {"Skirt_R_01" => 22}
87 | // 23: {"Skirt_R_02" => 23}
88 | // 24: {"Skirt_R_03" => 24}
89 |
90 |
91 |
92 | return this;
93 | }
94 | }
95 |
96 | export default ToruRig;
--------------------------------------------------------------------------------
/examples/_lib/Cycle.js:
--------------------------------------------------------------------------------
1 | const PI_2 = 6.283185307179586;
2 | const PI_2_INV = 1 / 6.283185307179586;
3 |
4 | class Cycle{
5 | constructor( sec=1 ){
6 | this.value = 0; // Current Cycle Value
7 | this.cycle_inc = 0; // How much to move per millisecond
8 | this.speed_scale = 1.0; // Scale the rate of the cycle
9 | this.tickStack = new Array();
10 | this.setBySeconds( sec );
11 | }
12 |
13 | onTick( fn ){ this.tickStack.push( fn ); return this; }
14 |
15 | setBySeconds( s ){ this.cycle_inc = PI_2 / ( s * 1000 ); return this;}
16 |
17 | backwards(){ if( this.speed_scale > 0 ) this.speed_scale *= -1; return this;}
18 | forwards(){ if( this.speed_scale < 0 ) this.speed_scale *= -1; return this;}
19 |
20 | get( offset=0 ){ return (this.value + offset) % PI_2; }
21 | asSin( offset=0 ){ return Math.sin( this.value + offset ); }
22 | asSin01( offset=0 ){ return Math.sin( this.value + offset ) * 0.5 + 0.5; }
23 | asSinAbs( offset=0 ){ return Math.abs( Math.sin( this.value + offset ) ); }
24 | asCycle01( offset=0 ){ return (this.value + offset) * PI_2_INV; }
25 | asCycle010( offset=0 ){
26 | var n = (this.value + offset) * PI_2_INV * 2;
27 | return ( n > 1 )? 1 - (n - 1) : n;
28 | }
29 |
30 | tick( dt ){
31 | this.value = ( this.value + ( dt * 1000 * this.speed_scale) * this.cycle_inc ) % PI_2;
32 |
33 | if( this.tickStack.length > 0 ){
34 | for( let t of this.tickStack ) t( this );
35 | }
36 |
37 | return this;
38 | }
39 | }
40 |
41 | export default Cycle;
--------------------------------------------------------------------------------
/examples/_lib/propui/PropGroup.js:
--------------------------------------------------------------------------------
1 | import { CollapseContent } from './PropUtil.js';
2 |
3 | class PropGroup extends HTMLElement{
4 | // #region MAIN
5 | _isOpen = true;
6 | _contentCont = null;
7 | _contentArea = null;
8 | _header = null;
9 | _lblHeading = null;
10 | _btnToggle = null;
11 | constructor(){
12 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13 | super();
14 | this.attachShadow( { mode: 'open' } );
15 |
16 | this.shadowRoot.appendChild( PropGroup.Template.content.cloneNode( true ) );
17 | const sroot = this.shadowRoot;
18 |
19 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20 | this._contentCont = sroot.querySelector( 'main' );
21 | this._contentArea = sroot.querySelector( 'main > div' );
22 | this._header = sroot.querySelector( 'header' );
23 | this._lblHeading = sroot.querySelector( 'header > span' );
24 | this._btnToggle = sroot.querySelector( 'header > button' );
25 |
26 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 | this._header.addEventListener( 'click', this.toggle.bind( this ) );
28 | }
29 | // #endregion
30 |
31 | // #region METHODS
32 | close(){
33 | CollapseContent.close( this._contentCont, this._contentArea );
34 | this._isOpen = false;
35 | this.classList.remove( 'open' );
36 | }
37 |
38 | open(){
39 | CollapseContent.open( this._contentCont, this._contentArea );
40 | this._isOpen = true;
41 | this.classList.add( 'open' );
42 | }
43 |
44 | toggle(){
45 | if( this._isOpen ) this.close();
46 | else this.open();
47 | }
48 |
49 | fixedTopRight( x=10, y=10 ){
50 | this.style.position = 'fixed';
51 | this.style.top = y + 'px';
52 | this.style.right = x + 'px';
53 | return this;
54 | }
55 |
56 | setWidth( v ){ this.style.width = v+'px'; return this }
57 | setHeading( v ){ this._lblHeading.innerText = v; return this; }
58 | // #endregion
59 |
60 | // #region WEB COMPONENT
61 | connectedCallback(){
62 | if( !this.hasAttribute( 'open' ) ){
63 | this.classList.add( 'open' );
64 | this._isOpen = true;
65 | }
66 | }
67 | // #endregion
68 |
69 | // #region ATTRIBUTES
70 | static get observedAttributes(){
71 | return [ 'open', 'heading' ];
72 | }
73 |
74 | attributeChangedCallback( name, oldval, newval ){
75 | //console.log( name, 'old', oldval, 'new', newval );
76 | switch( name ){
77 | case 'open':
78 | if( newval == true || newval == 'true') this.open();
79 | else this.close();
80 | break;
81 | case 'heading': this.setHeading( newval ); break;
82 | }
83 | }
84 | // #endregion
85 | }
86 |
87 | // #region TEMPLATE
88 | PropGroup.Template = document.createElement( 'template' );
89 | PropGroup.Template.innerHTML = `
90 |
134 |
135 | Heading
136 |
141 |
142 |
143 |
144 |
145 |
146 | `;
147 |
148 | window.customElements.define( "prop-group", PropGroup );
149 | // #endregion
150 |
151 |
152 | export default PropGroup;
--------------------------------------------------------------------------------
/examples/_lib/propui/PropUtil.js:
--------------------------------------------------------------------------------
1 | export class GlobalMove{
2 | static init_x = 0;
3 | static init_y = 0;
4 | static callback = 0;
5 |
6 | static begin( e, cb ){
7 | this.init_x = e.clientX;
8 | this.init_y = e.clientY;
9 | this.callback = cb;
10 | document.addEventListener( 'mousemove', this.mousemove );
11 | document.addEventListener( 'mouseup', this.mouseup );
12 | }
13 |
14 | static mousemove( e ){
15 | const x = e.clientX;
16 | const y = e.clientY;
17 | const dx = x - GlobalMove.init_x;
18 | const dy = y - GlobalMove.init_y;
19 |
20 | if( GlobalMove.callback ) GlobalMove.callback( [x,y], [dx,dy] );
21 | }
22 |
23 | static mouseup( e ){
24 | document.removeEventListener( 'mousemove', this.mousemove );
25 | document.removeEventListener( 'mouseup', this.mouseup );
26 | GlobalMove.callback = null;
27 | }
28 | }
29 |
30 | export class CollapseContent{
31 | static open( container, content ){
32 | // When in an open state, remove style properties since they'll get in the way
33 | // if elements are added dynamically into the content area.
34 | container.addEventListener( 'transitionend',()=>{
35 | container.style.removeProperty( 'height' );
36 | container.style.removeProperty( 'overflow' );
37 | //this._contentArea.scrollIntoView( { behavior: 'smooth', block: 'nearest' } );
38 | }, { once: true } );
39 |
40 | const box = content.getBoundingClientRect();
41 | container.style.height = box.height + 'px';
42 | }
43 |
44 | static close( container, content ){
45 | const box = content.getBoundingClientRect();
46 | container.style.overflow = 'hidden';
47 | container.style.height = box.height + 'px'; // Set Height as a starting point for transition
48 |
49 | // Then set the true height on a delay
50 | window.setTimeout( ()=>( container.style.height = '0px' ), 50 );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/_lib/propui/index.js:
--------------------------------------------------------------------------------
1 | import PropGroup from './PropGroup.js';
2 |
3 | import PropSelect from './PropSelect.js';
4 |
5 |
6 | // #region STARTUP
7 | const mod_path = import.meta.url.substring( 0, import.meta.url.lastIndexOf("/") + 1 );
8 | const css_path = mod_path + "PropUI.css";
9 |
10 | (function(){
11 | let link = document.createElement( "link" );
12 | link.rel = "stylesheet";
13 | link.type = "text/css";
14 | link.media = "all";
15 | link.href = css_path;
16 | document.getElementsByTagName( "head" )[0].appendChild( link );
17 | })();
18 | // #endregion /////////////////////////////////////////////////////////////////////////
19 |
20 |
21 | export {
22 | PropGroup,
23 | PropSelect
24 | };
--------------------------------------------------------------------------------
/examples/_res/_setup.txt:
--------------------------------------------------------------------------------
1 | All testing Resources currently at
2 | https://github.com/sketchpunk/res
3 |
4 | Clone the repo into this folder for examples for function correctly.
--------------------------------------------------------------------------------
/examples/babylonjs/001_skin_mtx.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
94 |
--------------------------------------------------------------------------------
/examples/babylonjs/002_animation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
96 |
--------------------------------------------------------------------------------
/examples/babylonjs/005_ikrig.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
92 |
--------------------------------------------------------------------------------
/examples/babylonjs/_000_debug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
43 |
--------------------------------------------------------------------------------
/examples/babylonjs/_000_debug_mesh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
53 |
--------------------------------------------------------------------------------
/examples/babylonjs/_000_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
32 |
--------------------------------------------------------------------------------
/examples/babylonjs/_lib/BoneViewMaterial.js:
--------------------------------------------------------------------------------
1 | import * as BABYLON from 'babylonjs';
2 |
3 | export default function BoneViewMaterial( app ){
4 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 | // Shader
6 | const mat = new BABYLON.ShaderMaterial( "shader", app.scene,
7 | { vertexSource: VERT_SRC, fragmentSource: FRAG_SRC },
8 | { attributes : [ 'position', 'inst', 'boneRot', 'bonePos', 'boneScl' ],
9 | uniforms : [ 'world', 'view', 'projection', 'meshScl', 'dirScl', 'color' ] }
10 | );
11 |
12 | mat.setFloat( 'meshScl', 0.02 );
13 | mat.setFloat( 'dirScl', 1.0 );
14 | mat.setColor3( 'color', BABYLON.Color3.FromHexString( '#f0f0f0' ) );
15 |
16 | // Meshes from GTLF have triangles CCW winding, but need to
17 | // set to CW on the shader to render correctly. A babylonJS thing?
18 | mat.sideOrientation = BABYLON.Material.ClockWiseSideOrientation;
19 | return mat;
20 | }
21 |
22 | const VERT_SRC = `
23 | precision highp float;
24 | in vec3 position;
25 | in vec2 inst;
26 | in vec4 boneRot;
27 | in vec3 bonePos;
28 | in vec3 boneScl;
29 |
30 | uniform float meshScl;
31 | uniform float dirScl;
32 |
33 | // Babylon Matrices
34 | uniform mat4 world;
35 | uniform mat4 view;
36 | uniform mat4 projection;
37 |
38 | // Fragment Output
39 | out vec3 frag_wpos; // Fragment World Space Position
40 |
41 | ////////////////////////////////////////////////////////////////////////
42 |
43 | vec3 transform( vec3 v ){
44 | vec4 q = boneRot;
45 | v *= boneScl;
46 | v += 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );
47 | v += bonePos;
48 | return v;
49 | }
50 |
51 | ////////////////////////////////////////////////////////////////////////
52 |
53 | void main(void){
54 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 | vec3 pos = position * meshScl; // Apply Bone Scale
56 |
57 | if( gl_VertexID < 4 ) pos.y = inst.y; // Move Top Face to Bone's Length in Local Space
58 | if( gl_VertexID > 7 ) pos.z *= dirScl; // Scale the Direction Pointer face
59 |
60 | pos = transform( pos ); // Apply WorldSpace Transform on the mesh
61 |
62 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63 | vec4 wpos = world * vec4( pos, 1.0 );
64 | frag_wpos = wpos.xyz;
65 | gl_Position = projection * view * wpos;
66 | }`;
67 |
68 | // Babylon Adds
69 | // -- #version 300 es
70 | // precision highp float;
71 | // out vec3 glFragColor;
72 | const FRAG_SRC = `
73 | ////////////////////////////////////////////////////////////////////////
74 |
75 | in vec3 frag_wpos;
76 | in vec3 frag_norm;
77 |
78 | uniform vec3 color;
79 |
80 | ////////////////////////////////////////////////////////////////////////
81 |
82 | #define LITCNT 2
83 | const vec3[] light_pos = vec3[](
84 | vec3( 0.0, 2.5, -1.5 ),
85 | vec3( 1.0, 0.0, 1.0 )
86 | );
87 |
88 | float computePointLights( vec3[LITCNT] lights, vec3 norm ){
89 | vec3 light_vec;
90 | vec3 light_dir;
91 |
92 | float dist;
93 | float attenuation;
94 | float diffuse = 0.25;
95 | float constant = 0.5;
96 | float linear = 0.5;
97 | float quadratic = 0.5;
98 |
99 | for( int i=0; i < LITCNT; i++ ){
100 | light_vec = lights[i].xyz - frag_wpos;
101 | light_dir = normalize( light_vec );
102 | dist = length( light_vec );
103 | attenuation = 1.0 / ( constant + linear * dist + quadratic * (dist * dist) );
104 | diffuse += max( dot( norm, light_dir ), 0.0 ) * attenuation;
105 | }
106 |
107 | return diffuse;
108 | }
109 |
110 | ////////////////////////////////////////////////////////////////////////
111 |
112 | void main(void) {
113 | vec3 norm = normalize( cross( dFdx(frag_wpos), dFdy(frag_wpos) ) ); // Low Poly Normals
114 | float diffuse = computePointLights( light_pos, norm );
115 | glFragColor = vec4( color * diffuse, 1.0 );
116 |
117 | //glFragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
118 | }`;
119 | //#endregion
120 |
--------------------------------------------------------------------------------
/examples/babylonjs/_lib/Starter.css:
--------------------------------------------------------------------------------
1 | html, body{ margin: 0px; padding:0px; width:100%; height:100%; }
2 | body {
3 | margin: 0;
4 | background-color: #000;
5 | color: #fff;
6 | font-family: monospace;
7 | font-size: 12px;
8 | overscroll-behavior: none;
9 | }
10 | canvas{ display: block; width:100%; height:100%; }
--------------------------------------------------------------------------------
/examples/babylonjs/_lib/Starter.js:
--------------------------------------------------------------------------------
1 | import * as BABYLON from 'babylonjs';
2 | import * as MATERIAL from 'babylonjs-materials';
3 |
4 | // #region STARTUP
5 | const mod_path = import.meta.url.substring( 0, import.meta.url.lastIndexOf("/") + 1 );
6 | const css_path = mod_path + "Starter.css";
7 |
8 | (function(){
9 | let link = document.createElement( "link" );
10 | link.rel = "stylesheet";
11 | link.type = "text/css";
12 | link.media = "all";
13 | link.href = css_path;
14 | document.getElementsByTagName( "head" )[0].appendChild( link );
15 | })();
16 | // #endregion /////////////////////////////////////////////////////////////////////////
17 |
18 | // https://github.com/BabylonJS/Babylon.js/tree/master/materialsLibrary/src/grid
19 | // https://github.com/BabylonJS/Babylon.js/tree/master/dist/materialsLibrary
20 |
21 | // Boiler Plate Starter for Babylon JS
22 | class Starter{
23 | // #region MAIN
24 | scene = null;
25 | engine = null;
26 | canvas = null;
27 | camera = null;
28 |
29 | orbit = null;
30 | render_bind = this.render.bind( this );
31 | onRender = null;
32 | deltaTime = 0;
33 | elapsedTime = 0;
34 |
35 | constructor(){
36 | this.initCore();
37 | this.initEnv();
38 | this.initUtil();
39 | }
40 |
41 | initCore(){
42 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43 | this.canvas = document.createElement( "canvas" );
44 | document.body.appendChild( this.canvas );
45 |
46 | this.engine = new BABYLON.Engine( this.canvas, true );
47 |
48 | this.scene = new BABYLON.Scene( this.engine );
49 | this.scene.clearColor = BABYLON.Color3.FromHexString( '#202020' );
50 |
51 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52 | this.camera = new BABYLON.ArcRotateCamera( 'Camera', Math.PI/2, Math.PI/3, 4, new BABYLON.Vector3(0, 0.5, 0), this.scene );
53 | this.camera.attachControl( this.canvas, true );
54 | //this.camera.inertialRadiusOffset = 10;
55 |
56 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 | }
58 |
59 | initEnv(){
60 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61 | const light = new BABYLON.HemisphericLight( 'MainLight', new BABYLON.Vector3(1, 1, 0) );
62 |
63 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64 | const matGround = new MATERIAL.GridMaterial( "groundMaterial", this.scene );
65 | matGround.majorUnitFrequency = 5;
66 | matGround.minorUnitVisibility = 0.1;
67 | matGround.gridRatio = 0.2;
68 | matGround.backFaceCulling = false;
69 | matGround.mainColor = new BABYLON.Color3(1, 1, 1);
70 | matGround.lineColor = new BABYLON.Color3(0.4, 0.4, 0.4);
71 | matGround.opacity = 0.98;
72 |
73 | const ground = BABYLON.Mesh.CreateGround( 'Ground', 10, 10, 1, this.scene, false );
74 | ground.material = matGround;
75 | }
76 |
77 | initUtil(){
78 | window.addEventListener( 'resize', ()=>{ this.engine.resize(); });
79 | this.engine.runRenderLoop( this.render_bind );
80 | }
81 |
82 | render(){
83 | if( this.onRender ){
84 | const dt = this.engine.getDeltaTime() * 0.001;
85 | this.onRender( dt );
86 | }
87 | this.scene.render();
88 | }
89 | // #endregion ////////////////////////////////////////////////////////////////////////////////////////
90 |
91 | // #region METHODS
92 | add( o ){ this.scene.add( o ); return this; }
93 | remove( o ){ this.scene.remove( o ); return this; }
94 |
95 | setCamera( lon, lat, radius, target ){
96 |
97 | //this.camera.setTarget(BABYLON.Vector3.Zero());
98 |
99 | /*
100 | let phi = ( 90 - lat ) * Math.PI / 180,
101 | theta = ( lon + 180 ) * Math.PI / 180;
102 |
103 | this.camera.position.set(
104 | -(radius * Math.sin( phi ) * Math.sin(theta)),
105 | radius * Math.cos( phi ),
106 | -(radius * Math.sin( phi ) * Math.cos(theta))
107 | );
108 |
109 | if( target ) this.orbit.target.fromArray( target );
110 |
111 | this.orbit.update();
112 | */
113 |
114 | return this;
115 | }
116 |
117 | // #endregion ////////////////////////////////////////////////////////////////////////////////////////
118 |
119 | // #region EVENTS
120 | // onResize( e ){
121 | // this.setSize( window.innerWidth, window.innerHeight );
122 | // }
123 | //#endregion
124 |
125 | }
126 |
127 | export default Starter;
128 | export { BABYLON };
--------------------------------------------------------------------------------
/examples/babylonjs/_lib/Util.js:
--------------------------------------------------------------------------------
1 | import * as BABYLON from 'babylonjs';
2 |
3 | export default class Util{
4 |
5 | static matColor( hex='#00ffff', name='colorMat' ){
6 | const mat = new BABYLON.StandardMaterial( name );
7 | mat.diffuseColor = BABYLON.Color3.FromHexString( hex );
8 | mat.sideOrientation = BABYLON.Material.ClockWiseSideOrientation;
9 | //mat.backFaceCulling = true;
10 | return mat;
11 | }
12 |
13 | static mesh( name, verts, idx=null, normal=null, uv=null, mat=null ){
14 | const mesh = new BABYLON.Mesh( name );
15 | const data = new BABYLON.VertexData();
16 |
17 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18 | data.positions = verts;
19 | if( idx ) data.indices = idx;
20 | if( normal ) data.normals = normal;
21 | if( uv ) data.uvs = uv;
22 | data.applyToMesh( mesh );
23 |
24 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25 | if( mat ) mesh.material = mat;
26 | return mesh;
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/examples/babylonjs/_lib/UtilArm.js:
--------------------------------------------------------------------------------
1 | import { Armature, SkinMTX } from '../../../src/armature/index';
2 | import Clip from '../../../src/animation/Clip';
3 |
4 | import BoneViewMesh from './BoneViewMesh.js';
5 | import SkinMTXMaterial from './SkinMTXMaterial.js';
6 | import { UtilGltf2 } from './UtilGltf2.js';
7 |
8 | class UtilArm{
9 |
10 | static newBoneView( app, arm, pose=null, meshScl, dirScl ){
11 | const boneView = new BoneViewMesh( app, arm );
12 |
13 | // Because of the transform on the Armature itself, need to scale up the bones
14 | // to offset the massive scale down of the model
15 | if( meshScl && dirScl ) boneView.setBoneScale( meshScl, dirScl );
16 |
17 | // Set Initial Data So it Renders
18 | boneView.updateFromPose( pose || arm ); // arm.newPose().updateWorld( true )
19 |
20 | return boneView;
21 | }
22 |
23 | static skinMtxMesh( app, gltf, arm, base='#00ffff', meshName=null ){
24 | const mat = SkinMTXMaterial( app, base, arm.getSkinOffsets()[0] ); // 3JS Example of Matrix Skinning GLSL Code
25 | return UtilGltf2.loadMesh( gltf, meshName, mat ); // Pull Skinned Mesh from GLTF
26 | }
27 |
28 | static clipFromGltf( gltf ){ return Clip.fromGLTF2( gltf.getAnimation() ); }
29 |
30 | static armFromGltf( gltf, defaultBoneLen = 0.07 ){
31 | const skin = gltf.getSkin();
32 | const arm = new Armature();
33 |
34 | // Create Armature
35 | for( let j of skin.joints ){
36 | arm.addBone( j.name, j.parentIndex, j.rotation, j.position, j.scale );
37 | }
38 |
39 | // Bind
40 | arm.bind( SkinMTX, defaultBoneLen );
41 |
42 | // Save Offsets if available
43 | arm.offset.set( skin.rotation, skin.position, skin.scale );
44 | return arm;
45 | }
46 |
47 | }
48 |
49 | export default UtilArm;
--------------------------------------------------------------------------------
/examples/babylonjs/_lib/UtilGltf2.js:
--------------------------------------------------------------------------------
1 | import * as BABYLON from 'babylonjs';
2 | import Gltf2 from '../../../src/parsers/gltf2/index';
3 |
4 | class UtilGltf2{
5 |
6 | static loadMesh( gltf, name=null, mat=null ){
7 | const o = gltf.getMesh( name );
8 | let prim;
9 |
10 | if( o.primitives.length == 1 ){
11 | prim = o.primitives[ 0 ];
12 | return this.primitiveMesh( o.name, prim, mat );
13 | }else{
14 | console.error( 'Multi-Primitive GLTF Mesh conversion not implemented.' );
15 | }
16 | }
17 |
18 | static primitiveMesh( name, prim, mat=null ){
19 | const mesh = new BABYLON.Mesh( name );
20 | const data = new BABYLON.VertexData();
21 |
22 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 | data.positions = prim.position.data;
24 | if( prim.indices ) data.indices = prim.indices.data;
25 | if( prim.normal ) data.normals = prim.normal.data;
26 | if( prim.texcoord_0 ) data.uvs = prim.texcoord_0.data;
27 |
28 | if( prim.joints_0 && prim.weights_0 ){
29 | // Why Matrices? What if skinning with dual quaternions? I'm Triggered :)
30 | data.matricesIndices = prim.joints_0.data;
31 | data.matricesWeights = prim.weights_0.data;
32 | }
33 |
34 | if( prim.indices && !prim.normal ){
35 | const norm = [];
36 | BABYLON.VertexData.ComputeNormals(
37 | prim.position.data,
38 | prim.indices.data,
39 | norm
40 | );
41 | data.normals = norm;
42 | }
43 |
44 | data.applyToMesh( mesh );
45 |
46 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47 | if( mat ) mesh.material = mat;
48 | return mesh;
49 | }
50 |
51 | }
52 |
53 | export { UtilGltf2, Gltf2 };
--------------------------------------------------------------------------------
/examples/babylonjs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/threejs/000_gltf2_mesh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
--------------------------------------------------------------------------------
/examples/threejs/001_skin_dq.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/examples/threejs/001_skin_dqt.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/examples/threejs/001_skin_mtx.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
92 |
--------------------------------------------------------------------------------
/examples/threejs/001_skin_rts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/examples/threejs/002_animation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/examples/threejs/002_animation_bvh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
96 |
--------------------------------------------------------------------------------
/examples/threejs/005_ikrig.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
84 |
--------------------------------------------------------------------------------
/examples/threejs/_000_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
44 |
--------------------------------------------------------------------------------
/examples/threejs/_lib/BoneViewMaterial.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | function BoneViewMaterial( color='white', useDepthTest=true ){
4 | let mat = new THREE.RawShaderMaterial({
5 | //side : THREE.DoubleSide,
6 | depthTest : useDepthTest,
7 | uniforms : {
8 | color : { type :'vec3', value:new THREE.Color( color ) },
9 | meshScl : { value: 0.02 },
10 | dirScl : { value: 2.0 },
11 | boneRot : { value: null },
12 | bonePos : { value: null },
13 | boneScl : { value: null },
14 | },
15 |
16 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | vertexShader : `#version 300 es
18 | in vec3 position; // Vertex Position
19 | in vec2 inst; // Instanced Data : Bone Index, Bone Length
20 |
21 | const int MAXBONE = 100;
22 | uniform vec4 boneRot[ MAXBONE ];
23 | uniform vec3 bonePos[ MAXBONE ];
24 | uniform vec3 boneScl[ MAXBONE ];
25 |
26 | uniform float meshScl;
27 | uniform float dirScl;
28 |
29 | uniform mat4 modelMatrix; // Matrices should be filled in by THREE.JS Automatically.
30 | uniform mat4 viewMatrix;
31 | uniform mat4 projectionMatrix;
32 |
33 | out vec3 frag_wpos; // Fragment World Space Position
34 |
35 | ////////////////////////////////////////////////////////////////////////
36 |
37 | vec3 transform( int i, vec3 v ){
38 | vec4 q = boneRot[ i ];
39 | v *= boneScl[ i ];
40 | v += 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );
41 | v += bonePos[ i ];
42 | return v;
43 | }
44 |
45 | ////////////////////////////////////////////////////////////////////////
46 |
47 | void main(){
48 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49 | int bIdx = int( inst.x ); // Get Bone Index this instance is for
50 | vec3 pos = position * meshScl; // Apply Bone Scale
51 |
52 | if( gl_VertexID < 4 ) pos.y = inst.y; // Move Top Face to Bone's Length in Local Space
53 | if( gl_VertexID > 7 ) pos.z *= dirScl; // Scale the Direction Pointer face
54 |
55 | pos = transform( bIdx, pos ); // Apply WorldSpace Transform on the mesh
56 |
57 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58 | vec4 wpos = modelMatrix * vec4( pos, 1.0 );
59 | frag_wpos = wpos.xyz;
60 | gl_Position = projectionMatrix * viewMatrix * wpos;
61 | }`,
62 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63 | fragmentShader : `#version 300 es
64 | precision mediump float;
65 |
66 | ////////////////////////////////////////////////////////////////////////
67 |
68 | out vec4 out_color;
69 | in vec3 frag_wpos;
70 | in vec3 frag_norm;
71 |
72 | uniform vec3 color;
73 |
74 | ////////////////////////////////////////////////////////////////////////
75 |
76 | #define LITCNT 2
77 | const vec3[] light_pos = vec3[](
78 | vec3( 0.0, 2.5, 1.0 ),
79 | vec3( -1.0, 0.0, 1.0 )
80 | );
81 |
82 | float computePointLights( vec3[LITCNT] lights, vec3 norm ){
83 | vec3 light_vec;
84 | vec3 light_dir;
85 |
86 | float dist;
87 | float attenuation;
88 | float diffuse = 0.0;
89 | float constant = 0.5;
90 | float linear = 0.5;
91 | float quadratic = 0.5;
92 |
93 | for( int i=0; i < LITCNT; i++ ){
94 | light_vec = lights[i].xyz - frag_wpos;
95 | light_dir = normalize( light_vec );
96 | dist = length( light_vec );
97 | attenuation = 1.0 / ( constant + linear * dist + quadratic * (dist * dist) );
98 | diffuse += max( dot( norm, light_dir ), 0.0 ) * attenuation;
99 | }
100 |
101 | return diffuse;
102 | }
103 |
104 | void main(){
105 | vec3 norm = normalize( cross( dFdx(frag_wpos), dFdy(frag_wpos) ) ); // Low Poly Normals
106 | //vec3 norm = normalize( frag_norm ); // Model's Normals
107 | float diffuse = computePointLights( light_pos, norm );
108 | out_color = vec4( color * diffuse, 1.0 );
109 | //out_color = vec4( 1.0, 0.0, 0.0, 1.0 );
110 | }`,
111 | });
112 |
113 | // If not using WebGL2.0 and Want to use dfdx or fwidth, Need to load extension
114 | mat.extensions = { derivatives : true };
115 | return mat;
116 | }
117 |
118 | export default BoneViewMaterial;
--------------------------------------------------------------------------------
/examples/threejs/_lib/BoneViewMesh.js:
--------------------------------------------------------------------------------
1 |
2 | //#region IMPORTS
3 | import * as THREE from 'three';
4 | import BoneViewMaterial from './BoneViewMaterial.js';
5 | import Vec3Util from '../../../src/maths/Vec3Util';
6 | import QuatUtil from '../../../src/maths/QuatUtil';
7 | //#endregion
8 |
9 | class BoneViewMesh extends THREE.Mesh{
10 | constructor( arm, color='white', useDepthTest=true ){
11 | const shape = baseShape();
12 | const inst = instanceData( arm );
13 | const bCnt = arm.bones.length;
14 |
15 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 | const mat = BoneViewMaterial( color, useDepthTest );
17 | mat.uniforms.boneRot.value = new Float32Array( 4 * bCnt );
18 | mat.uniforms.bonePos.value = new Float32Array( 3 * bCnt );
19 | mat.uniforms.boneScl.value = new Float32Array( 3 * bCnt );
20 | mat.side = THREE.DoubleSide;
21 |
22 | const geo = new THREE.InstancedBufferGeometry();
23 | geo.setIndex( new THREE.BufferAttribute( new Uint16Array(shape.indices), 1 ) );
24 | geo.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array(shape.vertices), 3 ) );
25 | geo.setAttribute( 'inst', new THREE.InstancedBufferAttribute( inst, 2 ) );
26 |
27 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28 | super( geo, mat );
29 | }
30 |
31 | setScales( meshScl = 0.02, dirScl=2.0 ){
32 | this.material.uniforms.meshScl.value = meshScl;
33 | this.material.uniforms.dirScl.value = dirScl;
34 | return this;
35 | }
36 |
37 | updateFromPose( pose ){
38 | const rot = this.material.uniforms.boneRot.value;
39 | const pos = this.material.uniforms.bonePos.value;
40 | const scl = this.material.uniforms.boneScl.value;
41 |
42 | let b, i, ii;
43 | for( b of pose.bones ){
44 | i = b.idx * 3;
45 | ii = b.idx * 4;
46 |
47 | QuatUtil.toBuf( b.world.rot, rot, ii );
48 | Vec3Util.toBuf( b.world.pos, pos, i );
49 | Vec3Util.toBuf( b.world.scl, scl, i );
50 | }
51 |
52 | return this;
53 | }
54 | }
55 |
56 | //#region HELPERS
57 | function baseShape(){
58 | //const a = new Vec3( -0.5, 0, -0.5 );//.scale( 0.5 );
59 | //const b = new Vec3( 0.5, 1, 0.5 );//.scale( 0.5 );
60 | const a = Vec3Util.toStruct( [-0.5, 0, -0.5] );
61 | const b = Vec3Util.toStruct( [ 0.5, 1, 0.5] );
62 | const geo = {
63 | vertices : [
64 | a.x, b.y*2, a.z, // 0 Up
65 | a.x, b.y*2, b.z, // 1
66 | b.x, b.y*2, b.z, // 2
67 | b.x, b.y*2, a.z, // 3
68 |
69 | a.x, a.y, a.z, // 4 Bend
70 | a.x, b.y, b.z, // 5
71 | b.x, b.y, b.z, // 6
72 | b.x, a.y, a.z, // 7
73 |
74 | a.x, a.y, b.z*3, // 8 Fwd
75 | a.x, b.y, b.z*3, // 9
76 | b.x, b.y, b.z*3, // 10
77 | b.x, a.y, b.z*3, // 11
78 | ],
79 |
80 | indices : [
81 | 0,1,2, 2,3,0, // Top Face
82 |
83 | 0,4,5, 5,1,0, // Top Left
84 | 1,5,6, 6,2,1, // Top Fwd
85 | 2,6,7, 7,3,2, // Top Right
86 | 3,7,4, 4,0,3, // Top Back
87 |
88 | 10,9,8, 8,11,10, // Fwd Face
89 | 4,8,5, 8,9,5, // Fwd Left
90 | 5,9,6, 9,10,6, // Fwd Up
91 | 6,10,7, 10,11,7, // Fwd Right
92 | 7,11,8, 4,7,8 // Fwd Bot
93 | ],
94 | }
95 | return geo;
96 | }
97 |
98 | function instanceData( arm ){
99 | const bCnt = arm.bones.length;
100 | const rtn = new Float32Array( 2 * bCnt );
101 |
102 | let b, ii;
103 | for( b of arm.bones ){
104 | ii = b.idx * 2;
105 | rtn[ ii ] = b.idx;
106 | rtn[ ii+1 ] = b.len;
107 | }
108 |
109 | return rtn;
110 | }
111 | //#endregion
112 |
113 | export default BoneViewMesh;
--------------------------------------------------------------------------------
/examples/threejs/_lib/FacedCube.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | export default facedCube( pos=null, scl=null ){
3 | const geo = new THREE.BoxGeometry( 1, 1, 1 );
4 | const mat = [
5 | new THREE.MeshBasicMaterial( { color: 0x00ff00 } ), // Left
6 | new THREE.MeshBasicMaterial( { color: 0x777777 } ), // Right
7 | new THREE.MeshBasicMaterial( { color: 0x0000ff } ), // Top
8 | new THREE.MeshBasicMaterial( { color: 0x222222 } ), // Bottom
9 | new THREE.MeshBasicMaterial( { color: 0xff0000 } ), // Forward
10 | new THREE.MeshBasicMaterial( { color: 0xffffff } ), // Back
11 | ];
12 |
13 | const mesh = new THREE.Mesh( geo, mat );
14 |
15 | if( pos ) mesh.position.fromArray( pos );
16 | if( scl != null ) mesh.scale.set( scl, scl, scl );
17 |
18 | return mesh;
19 | }
20 |
--------------------------------------------------------------------------------
/examples/threejs/_lib/Starter.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | background-color: #000;
4 | color: #fff;
5 | font-family: monospace;
6 | font-size: 12px;
7 | overscroll-behavior: none;
8 | }
9 | canvas{ display: block; }
--------------------------------------------------------------------------------
/examples/threejs/_lib/Util.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | class Util{
4 | static loadTexture( url, flipY=false ){
5 | return new Promise( (resolve, reject) => {
6 | const loader = new THREE.TextureLoader()
7 | .load(
8 | url,
9 | tex =>{
10 | tex.flipY = flipY;
11 | tex.wrapT = THREE.RepeatWrapping;
12 | tex.wrapS = THREE.RepeatWrapping;
13 | resolve( tex );
14 | },
15 | undefined,
16 | err => reject( err )
17 | );
18 | });
19 | }
20 | }
21 |
22 | export default Util;
--------------------------------------------------------------------------------
/examples/threejs/_lib/UtilArm.js:
--------------------------------------------------------------------------------
1 | import { Armature, SkinMTX } from '../../../src/armature/index';
2 | import Clip from '../../../src/animation/Clip';
3 |
4 | import BoneViewMesh from './BoneViewMesh.js';
5 | import SkinMTXMaterial from './SkinMTXMaterial.js';
6 | import { UtilGltf2 } from './UtilGltf2.js';
7 |
8 |
9 |
10 | class UtilArm{
11 |
12 | static newBoneView( arm, pose=null, meshScl, dirScl ){
13 | const boneView = new BoneViewMesh( arm );
14 |
15 | // Because of the transform on the Armature itself, need to scale up the bones
16 | // to offset the massive scale down of the model
17 | if( meshScl ) boneView.material.uniforms.meshScl.value = meshScl;
18 | if( dirScl ) boneView.material.uniforms.dirScl.value = dirScl;
19 |
20 | // Set Initial Data So it Renders
21 | boneView.updateFromPose( pose || arm ); // arm.newPose().updateWorld( true )
22 |
23 | return boneView;
24 | }
25 |
26 | static skinMtxMesh( gltf, arm, base='cyan', meshName=null ){
27 | const mat = SkinMTXMaterial( base, arm.getSkinOffsets()[0] ); // 3JS Example of Matrix Skinning GLSL Code
28 | return UtilGltf2.loadMesh( gltf, meshName, mat ); // Pull Skinned Mesh from GLTF
29 | }
30 |
31 | static clipFromGltf( gltf ){ return Clip.fromGLTF2( gltf.getAnimation() ); }
32 |
33 | static armFromGltf( gltf, defaultBoneLen = 0.07 ){
34 | const skin = gltf.getSkin();
35 | const arm = new Armature();
36 |
37 | // Create Armature
38 | for( let j of skin.joints ){
39 | arm.addBone( j.name, j.parentIndex, j.rotation, j.position, j.scale );
40 | }
41 |
42 | // Bind
43 | arm.bind( SkinMTX, defaultBoneLen );
44 |
45 | // Save Offsets if available
46 | arm.offset.set( skin.rotation, skin.position, skin.scale );
47 | //if( skin.rotation ) arm.offset.rot.copy( skin.rotation );
48 | //if( skin.position ) arm.offset.pos.copy( skin.position );
49 | //if( skin.scale ) arm.offset.scl.copy( skin.scale );
50 |
51 | return arm;
52 | }
53 |
54 | }
55 |
56 | export default UtilArm;
--------------------------------------------------------------------------------
/examples/threejs/_lib/UtilBvh.js:
--------------------------------------------------------------------------------
1 | import { Armature, SkinMTX } from '../../../src/armature/index';
2 | import { Clip } from '../../../src/animation/index';
3 | import Bvh from '../../../src/parsers/bvh/index';
4 |
5 | import BoneDirMesh from './BoneDirMesh.js';
6 |
7 | class UtilBvh{
8 | static async fetchArmClip( url ){
9 | const bvh = await Bvh.fetch( url );
10 | bvh.ignoreRoot = true;
11 |
12 | const clip = this.getClip( bvh );
13 | const arm = this.getArmature( bvh );
14 |
15 | return [ arm, clip ];
16 | }
17 |
18 | static getClip( bvh ){
19 | const anim = bvh.getAnimation();
20 | return Clip.fromBvh( anim, [0] );
21 | }
22 |
23 | static getArmature( bvh, defaultBoneLen = 0.07 ){
24 | const arm = new Armature();
25 | const skin = bvh.getSkin();
26 |
27 | for( let j of skin.joints ){
28 | arm.addBone( j.name, j.parentIndex, j.rotation, j.position );
29 | }
30 |
31 | // Create Bind Pose
32 | arm.bind( SkinMTX, defaultBoneLen );
33 | return arm;
34 | }
35 |
36 | static getBoneView( pose, boneScl=0.1 ){
37 | const boneView = new BoneDirMesh( pose );
38 | boneView.setBoneScale( boneScl );
39 | boneView.updateFromPose( pose );
40 | return boneView;
41 | }
42 | }
43 |
44 | export { UtilBvh, Bvh };
--------------------------------------------------------------------------------
/examples/threejs/_lib/UtilGltf2.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import Gltf2 from '../../../src/parsers/gltf2/index';
3 |
4 | class UtilGltf2{
5 |
6 | static primitiveGeo( prim ){
7 | const geo = new THREE.BufferGeometry();
8 | geo.setAttribute( 'position', new THREE.BufferAttribute( prim.position.data, prim.position.componentLen ) );
9 |
10 | //console.log( prim );
11 |
12 | if( prim.indices ) geo.setIndex( new THREE.BufferAttribute( prim.indices.data, 1 ) );
13 | if( prim.normal ) geo.setAttribute( 'normal', new THREE.BufferAttribute( prim.normal.data, prim.normal.componentLen ) );
14 | if( prim.texcoord_0 ) geo.setAttribute( 'uv', new THREE.BufferAttribute( prim.texcoord_0.data, prim.texcoord_0.componentLen ) );
15 |
16 | if( prim.joints_0 && prim.weights_0 ){
17 | geo.setAttribute( 'skinWeight', new THREE.BufferAttribute( prim.weights_0.data, prim.weights_0.componentLen ) );
18 | geo.setAttribute( 'skinIndex', new THREE.BufferAttribute( prim.joints_0.data, prim.joints_0.componentLen ) );
19 | }
20 |
21 | return geo;
22 | }
23 |
24 | static loadMesh( gltf, name=null, mat=null ){
25 | const o = gltf.getMesh( name );
26 | let geo, prim, pmat;
27 |
28 | if( o.primitives.length == 1 ){
29 | prim = o.primitives[ 0 ];
30 |
31 | if( mat ) pmat = mat;
32 | else if( prim.materialIdx != null ) pmat = this.loadMaterial( gltf, prim.materialIdx );
33 |
34 | geo = this.primitiveGeo( prim );
35 | return new THREE.Mesh( geo, pmat );
36 | }else{
37 | let mesh, m, c ;
38 | const grp = new THREE.Group();
39 | for( prim of o.primitives ){
40 |
41 | if( mat ){
42 | pmat = mat;
43 | }else if( prim.materialIdx != null ){
44 | pmat = this.loadMaterial( gltf, prim.materialIdx );
45 | }
46 |
47 | geo = this.primitiveGeo( prim );
48 | mesh = new THREE.Mesh( geo, pmat );
49 |
50 | grp.add( mesh );
51 | }
52 | return grp;
53 | }
54 | }
55 |
56 | static loadMaterial( gltf, id ){
57 | const config = {};
58 | const m = gltf.getMaterial( id );
59 |
60 | if( m ){
61 | if( m.baseColorFactor ){
62 | config.color = new THREE.Color( m.baseColorFactor[0], m.baseColorFactor[1], m.baseColorFactor[2] );
63 | }
64 | }
65 |
66 | return new THREE.MeshPhongMaterial( config );
67 | }
68 |
69 | }
70 |
71 | export { UtilGltf2, Gltf2 };
--------------------------------------------------------------------------------
/examples/threejs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Index
6 |
7 | {%DIRECTORY%}
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "ossos",
3 | "version" : "0.0.3",
4 | "author" : "Pedro Sousa ( Vor @ SketchPunk Labs )",
5 | "description" : "Character Animation System",
6 | "keywords" : [ "animation", "skeleton", "inverse kinematrics", "armature", "ikrig" ],
7 | "license" : "MIT",
8 |
9 | "homepage" : "https://github.com/sketchpunklabs/ossos#readme",
10 | "repository" : { "type": "git", "url": "git+https://github.com/sketchpunklabs/ossos.git" },
11 | "bugs" : { "url": "https://github.com/sketchpunklabs/ossos/issues" },
12 |
13 | "files" : [ "dist" ],
14 | "main" : "./dist/ossos.cjs.js",
15 | "module" : "./dist/ossos.es.js",
16 | "types" : "./dist/ossos.d.ts",
17 | "exports": {
18 | ".": {
19 | "import" : "./dist/ossos.es.js",
20 | "require" : "./dist/ossos.cjs.js"
21 | }
22 | },
23 |
24 | "scripts" : {
25 | "dev" : "vite",
26 | "build" : "vite build",
27 | "build:types" : "tsc --declaration --noEmit false --emitDeclarationOnly --strict false --outDir ./dist",
28 | "build:site" : "vite build --mode site",
29 | "pack" : "npm pack",
30 | "preview-site" : "vite preview",
31 | "lint" : "eslint . --ext .ts"
32 | },
33 |
34 | "peerDependencies": {
35 | "gl-matrix" : "^3.4.3"
36 | },
37 |
38 | "devDependencies": {
39 | "vite" : "^3.0.9",
40 | "vite-plugin-list-directory-contents": "^1.0.1",
41 |
42 | "typescript" : "^4.5.2",
43 | "three" : "^0.138.3",
44 | "manipulator3d" : "^0.0.8",
45 |
46 | "babylonjs" : "^5.0.0-beta.9",
47 | "babylonjs-materials" : "^5.0.0-beta.9",
48 |
49 | "eslint" : "^8.8.0",
50 | "@typescript-eslint/eslint-plugin" : "^5.10.1",
51 | "@typescript-eslint/parser" : "^5.10.1"
52 | }
53 | }
--------------------------------------------------------------------------------
/src/animation/TypePool.ts:
--------------------------------------------------------------------------------
1 | import { vec3, quat } from 'gl-matrix'
2 |
3 | class TypePool{
4 | static _vec3Pool : vec3[] = [];
5 | static _quatPool : quat[] = [];
6 |
7 | static vec3() : vec3{
8 | let v: vec3 | undefined = this._vec3Pool.pop();
9 | if( !v ) v = vec3.create();
10 | return v;
11 | }
12 |
13 | static quat() : quat{
14 | let v: quat | undefined = this._quatPool.pop();
15 | if( !v ) v = quat.create()
16 | return v;
17 | }
18 |
19 | static recycle_vec3( ...ary: vec3[] ): TypePool{
20 | let v: vec3;
21 | for( v of ary ) this._vec3Pool.push( vec3.set( v, 0, 0, 0 ) );
22 | return this;
23 | }
24 |
25 | static recycle_quat( ...ary: quat[] ): TypePool{
26 | let v: quat;
27 | for( v of ary ) this._quatPool.push( quat.set( v, 0, 0, 0, 1 ) );
28 | return this;
29 | }
30 | }
31 |
32 | export default TypePool;
--------------------------------------------------------------------------------
/src/animation/index.ts:
--------------------------------------------------------------------------------
1 | import Animator from './Animator';
2 | import Clip from './Clip';
3 | import Retarget from './Retarget';
4 |
5 | export { Animator, Clip, Retarget };
--------------------------------------------------------------------------------
/src/animation/tracks/QuatTrack.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type { ITrack, fnInterp, Lerp } from './types';
3 | import type { FrameInfo } from '../Animator';
4 | import type Pose from '../../armature/Pose'
5 |
6 | import { ELerp } from './types'
7 | import TypePool from '../TypePool';
8 | import { quat } from 'gl-matrix'
9 | import QuatUtil from '../../maths/QuatUtil';
10 | //#endregion
11 |
12 |
13 | //#region ELERP FUNCTIONS
14 | function quat_step( track: ITrack, fi: FrameInfo, out: quat ) : quat{
15 | return QuatUtil.fromBuf( out, track.values, fi.k0 * 4 );
16 | }
17 |
18 | function quat_linear( track: ITrack, fi: FrameInfo, out: quat ) : quat{
19 | const v0 = TypePool.quat();
20 | const v1 = TypePool.quat();
21 |
22 | QuatUtil.fromBuf( v0, track.values, fi.k0 * 4 );
23 | QuatUtil.fromBuf( v1, track.values, fi.k1 * 4 );
24 | QuatUtil.nblend( out, v0, v1, fi.t ); // TODO : Maybe Slerp in the future?
25 |
26 | TypePool.recycle_quat( v0, v1 );
27 | return out;
28 | }
29 | //#endregion
30 |
31 |
32 | export default class QuatTrack implements ITrack{
33 | name : string = 'QuatTrack';
34 | values !: Float32Array;
35 | boneIndex = -1;
36 | timeStampIndex = -1;
37 | fnLerp : fnInterp = quat_linear;
38 |
39 | setInterpolation( i: Lerp ): this {
40 | switch( i ){
41 | case ELerp.Step : this.fnLerp = quat_step; break;
42 | case ELerp.Linear : this.fnLerp = quat_linear; break;
43 | case ELerp.Cubic : console.warn( 'Quat Cubic Lerp Not Implemented' ); break;
44 | }
45 | return this;
46 | }
47 |
48 | apply( pose: Pose, fi: FrameInfo ): this{
49 | const q = TypePool.quat();
50 | pose.setLocalRot( this.boneIndex, this.fnLerp( this, fi, q ) );
51 | TypePool.recycle_quat( q );
52 | return this;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/animation/tracks/Vec3Track.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type { ITrack, fnInterp, Lerp } from './types';
3 | import type { FrameInfo } from '../Animator';
4 | import type Pose from '../../armature/Pose'
5 |
6 | import { ELerp } from './types'
7 | import TypePool from '../TypePool';
8 | import { vec3 } from 'gl-matrix'
9 | import { Vec3Util } from '../../maths';
10 | //#endregion
11 |
12 | //#regeion ELERP FUNCTIONS
13 | function vec3_step( track: ITrack, fi: FrameInfo, out: vec3 ) : vec3{
14 | return Vec3Util.fromBuf( out, track.values, fi.k0 * 3 );
15 | }
16 |
17 | function vec3_linear( track: ITrack, fi: FrameInfo, out: vec3 ) : vec3{
18 | const v0 = TypePool.vec3();
19 | const v1 = TypePool.vec3();
20 |
21 | Vec3Util.fromBuf( v0, track.values, fi.k0 * 3 );
22 | Vec3Util.fromBuf( v1, track.values, fi.k1 * 3 );
23 | vec3.lerp( out, v0, v1, fi.t );
24 |
25 | TypePool.recycle_vec3( v0, v1 );
26 | return out;
27 | }
28 | //#endregion
29 |
30 | export default class Vec3Track implements ITrack{
31 | name : string = 'Vec3Track';
32 | values !: Float32Array;
33 | boneIndex = -1;
34 | timeStampIndex = -1;
35 | fnLerp : fnInterp = vec3_linear;
36 |
37 | setInterpolation( i: Lerp ): this {
38 | switch( i ){
39 | case ELerp.Step : this.fnLerp = vec3_step; break;
40 | case ELerp.Linear : this.fnLerp = vec3_linear; break;
41 | case ELerp.Cubic : console.warn( 'Vec3 Cubic Lerp Not Implemented' ); break;
42 | }
43 | return this;
44 | }
45 |
46 | apply( pose: Pose, fi: FrameInfo ): this{
47 | const v = TypePool.vec3();
48 | pose.setLocalPos( this.boneIndex, this.fnLerp( this, fi, v ) );
49 | TypePool.recycle_vec3( v );
50 | return this;
51 | }
52 | }
--------------------------------------------------------------------------------
/src/animation/tracks/types.ts:
--------------------------------------------------------------------------------
1 | import type { FrameInfo } from '../Animator';
2 | import type Pose from '../../armature/Pose';
3 |
4 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 | export type Lerp = typeof ELerp[ keyof typeof ELerp ];
6 | export const ELerp = {
7 | Step : 0,
8 | Linear : 1,
9 | Cubic : 2,
10 | } as const;
11 |
12 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13 | export interface ITrack{
14 | name : string;
15 | timeStampIndex : number;
16 | values : Float32Array;
17 | boneIndex : number;
18 | fnLerp : fnInterp
19 |
20 | apply( pose: Pose, fi: FrameInfo ): this;
21 |
22 | setInterpolation( i: Lerp ): this;
23 | }
24 |
25 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26 | export type fnInterp = ( track: ITrack, fi: FrameInfo, out: T ) => T;
--------------------------------------------------------------------------------
/src/armature/Bone.ts:
--------------------------------------------------------------------------------
1 | import { vec3, quat } from 'gl-matrix';
2 | import Transform from '../maths/Transform';
3 |
4 | class Bone{
5 | name : string; // Name of Bone
6 | idx : number; // Bone Index
7 | pidx : number; // Index to Parent Bone if not root. -1 means no parent
8 | len : number; // Length of the Bone
9 | dir : vec3 = [0,1,0]; // Direction the bone points with no rotation
10 | local = new Transform(); // Local Transform of Resting Pose
11 | world = new Transform(); // World Transform of Resting Pose
12 |
13 | constructor( name: string, idx: number, len=0 ){
14 | this.name = name;
15 | this.idx = idx;
16 | this.pidx = -1;
17 | this.len = len;
18 | }
19 |
20 | setLocal( rot ?: quat, pos ?: vec3, scl ?: vec3 ): this{
21 | if( rot ) quat.copy( this.local.rot, rot ); // this.local.rot.copy( rot );
22 | if( pos ) vec3.copy( this.local.pos, pos ); // this.local.pos.copy( pos );
23 | if( scl ) vec3.copy( this.local.scl, scl ); // this.local.scl.copy( scl );
24 | return this;
25 | }
26 |
27 | clone(): Bone{
28 | const b = new Bone( this.name, this.idx, this.len );
29 |
30 | b.pidx = this.pidx;
31 | b.local.copy( this.local );
32 | b.world.copy( this.world );
33 | vec3.copy( b.dir, this.dir );
34 | return b;
35 | }
36 | }
37 |
38 | export default Bone;
--------------------------------------------------------------------------------
/src/armature/BoneMap.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import Bone from "./Bone";
3 | import Armature from "./Armature";
4 | //#endregion
5 |
6 | //#region BONE PARSING
7 | class BoneParse{
8 | name : string;
9 | isLR : boolean;
10 | isChain : boolean;
11 | reFind : RegExp;
12 | reExclude ?: RegExp;
13 |
14 | constructor( name: string, isLR: boolean, reFind :string, reExclude?: string, isChain=false ){
15 | this.name = name;
16 | this.isLR = isLR;
17 | this.isChain = isChain;
18 | this.reFind = new RegExp( reFind, "i" );
19 | if( reExclude ) this.reExclude = new RegExp( reExclude, "i" );
20 | }
21 |
22 | test( bname: string ){
23 | if( !this.reFind.test( bname ) ) return null;
24 | if( this.reExclude && this.reExclude.test( bname ) ) return null;
25 |
26 | if( this.isLR && reLeft.test( bname ) ) return this.name + "_l";
27 | if( this.isLR && reRight.test( bname ) ) return this.name + "_r";
28 |
29 | return this.name;
30 | }
31 | }
32 |
33 | const reLeft = new RegExp( "\\.l|left|_l", "i" );
34 | const reRight = new RegExp( "\\.r|right|_r", "i" );
35 | const Parsers = [
36 | new BoneParse( "thigh", true, "thigh|up.*leg", "twist" ), //upleg | upperleg
37 | new BoneParse( "shin", true, "shin|leg|calf", "up|twist" ),
38 | new BoneParse( "foot", true, "foot" ),
39 | new BoneParse( "shoulder", true, "clavicle|shoulder" ),
40 | new BoneParse( "upperarm", true, "(upper.*arm|arm)", "fore|twist|lower" ),
41 | new BoneParse( "forearm", true, "forearm|arm", "up|twist" ),
42 | new BoneParse( "hand", true, "hand", "thumb|index|middle|ring|pinky" ),
43 | new BoneParse( "head", false, "head" ),
44 | new BoneParse( "neck", false, "neck" ),
45 | new BoneParse( "hip", false, "hips*|pelvis" ),
46 | new BoneParse( "spine", false, "spine.*\d*|chest", undefined, true ),
47 | ];
48 |
49 | //console.log( 'TEST', Parsers[ 0 ].test( 'mixamorig:LeftUpLeg' ) );
50 | //#endregion
51 |
52 | //#region DATA STRUCTS
53 | class BoneChain{
54 | items : BoneInfo[] = [];
55 | }
56 |
57 | class BoneInfo{
58 | index : number;
59 | name : string;
60 | constructor( idx: number, name: string ){
61 | this.index = idx;
62 | this.name = name;
63 | }
64 | }
65 | //#endregion
66 |
67 | class BoneMap{
68 | bones : Map< string, BoneInfo | BoneChain > = new Map();
69 | constructor( arm: Armature ){
70 | let i : number;
71 | let b : Bone;
72 | let bp : BoneParse;
73 | let key : string | null;
74 |
75 | for( i=0; i < arm.bones.length; i++ ){
76 | b = arm.bones[ i ];
77 | for( bp of Parsers ){
78 | if( !(key = bp.test( b.name )) ) continue; // Didn't pass test, Move to next parser.
79 |
80 | if( !this.bones.has( key ) ){
81 | if( bp.isChain ){
82 | const ch = new BoneChain();
83 | ch.items.push( new BoneInfo( i, b.name ) );
84 | this.bones.set( key, ch );
85 | }else{
86 | this.bones.set( key, new BoneInfo( i, b.name ) );
87 | }
88 | }else{
89 | if( bp.isChain ){
90 | const ch = this.bones.get( bp.name )
91 | if( ch && ch instanceof BoneChain ) ch.items.push( new BoneInfo( i, b.name ) );
92 | }
93 | }
94 |
95 | break;
96 | }
97 | }
98 | }
99 | }
100 |
101 | export default BoneMap;
102 | export { BoneInfo, BoneChain };
--------------------------------------------------------------------------------
/src/armature/index.ts:
--------------------------------------------------------------------------------
1 | import Armature from './Armature';
2 | import Bone from './Bone';
3 | import Pose from './Pose';
4 | import { ISkin, TTextureInfo } from './skins/ISkin';
5 | import SkinMTX from './skins/SkinMTX';
6 | import SkinDQ from './skins/SkinDQ';
7 | import SkinDQT from './skins/SkinDQT';
8 | import SkinRTS from './skins/SkinRTS';
9 |
10 | export { Armature, Bone, Pose, SkinMTX, SkinDQ, SkinDQT, SkinRTS };
11 | export type { ISkin, TTextureInfo };
--------------------------------------------------------------------------------
/src/armature/skins/ISkin.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Armature from '../Armature';
3 | import type Pose from '../Pose';
4 | //#endregion
5 |
6 | export interface ISkin{
7 | init( arm: Armature ): this;
8 | updateFromPose( pose: Pose ): this;
9 | getOffsets(): Array< unknown >;
10 | getTextureInfo( frameCount: number ): TTextureInfo;
11 | clone():ISkin;
12 | }
13 |
14 | export type TTextureInfo = {
15 | boneCount : number,
16 | strideFloatLength : number,
17 | strideByteLength : number,
18 | pixelsPerStride : number,
19 | floatRowSize : number,
20 | bufferFloatSize : number,
21 | bufferByteSize : number,
22 | pixelWidth : number,
23 | pixelHeight : number,
24 | };
25 |
26 | // Matrix (MTX)
27 | // -- Bind Pose as Matrix
28 |
29 | // Dual Quaternions (DQ)
30 | // -- Bind Pose as DQ
31 |
32 | // DQ Transform (DQT)
33 | // -- Bind Pose as Transform
34 |
--------------------------------------------------------------------------------
/src/bonespring/SpringChain.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Armature from '../armature/Armature';
3 | import type Pose from '../armature/Pose';
4 | import type Bone from '../armature/Bone';
5 | import type { ISpringType } from './index'
6 |
7 | import SpringItem from './SpringItem';
8 | import SpringRot from './SpringRot';
9 | import SpringPos from './SpringPos';
10 | //#endregion
11 |
12 | class SpringChain{
13 | static ROT = 0;
14 | static POS = 1;
15 |
16 | //#region MAIN
17 | items : SpringItem[] = [];
18 | name : string;
19 | spring : ISpringType;
20 | constructor( name: string, type=0 ){
21 | this.name = name;
22 | this.spring = ( type == 1 )? new SpringPos() : new SpringRot();
23 | }
24 | //#endregion
25 |
26 | //#region SETTERS
27 |
28 | setBones( aryName: string[], arm: Armature, osc=5.0, damp=0.5 ): void{
29 | let bn : string;
30 | let b : Bone | null;
31 | let spr : SpringItem;
32 |
33 | for( bn of aryName ){
34 | b = arm.getBone( bn );
35 | if( b == null ){ console.log( 'Bone not found for spring: ', bn ); continue; }
36 |
37 | spr = new SpringItem( b.name, b.idx );
38 | spr.spring.setDamp( damp );
39 | spr.spring.setOscPerSec( osc );
40 |
41 | this.items.push( spr );
42 | }
43 | }
44 |
45 | setRestPose( pose: Pose, resetSpring=false, debug ?: any ): void{ this.spring.setRestPose( this, pose, resetSpring, debug ); }
46 | updatePose( dt: number, pose: Pose, debug ?: any ): void{ this.spring.updatePose( this, pose, dt, debug ); }
47 |
48 | //#endregion
49 | }
50 |
51 | export default SpringChain;
--------------------------------------------------------------------------------
/src/bonespring/SpringItem.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import Transform from '../maths/Transform';
3 | import SpringVec3 from './implicit_euler/SpringVec3';
4 | //#endregion
5 |
6 | class SpringItem{
7 | index : number;
8 | name : string;
9 | spring = new SpringVec3();
10 | bind = new Transform(); // Bind Transform in Local Space
11 |
12 | constructor( name: string, idx: number ){
13 | this.name = name;
14 | this.index = idx;
15 | }
16 | }
17 |
18 | export default SpringItem;
--------------------------------------------------------------------------------
/src/bonespring/SpringPos.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Bone from '../armature/Bone';
3 | import type Pose from '../armature/Pose';
4 | import type { ISpringType } from './index'
5 | import type SpringChain from './SpringChain';
6 |
7 | import Transform from '../maths/Transform';
8 | import SpringItem from './SpringItem';
9 | //#endregion
10 |
11 | class SpringPos implements ISpringType{
12 |
13 | setRestPose( chain: SpringChain, pose: Pose, resetSpring=true, debug ?: any ): void{
14 | let si : SpringItem;
15 | let b : Bone;
16 |
17 | for( si of chain.items ){
18 | b = pose.bones[ si.index ]; // Get Pose Bone
19 | si.spring.reset( b.world.pos ); // Set Spring to Start at this Position.
20 | si.bind.copy( b.local ); // Copy LS Transform as this will be the Actual Rest Pose of the bone.
21 | }
22 | }
23 |
24 | updatePose( chain: SpringChain, pose: Pose, dt: number, debug ?: any ): void{
25 | let si : SpringItem;
26 | let b : Bone;
27 | let pTran = new Transform();
28 | let cTran = new Transform();
29 | let iTran = new Transform();
30 |
31 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 | // Find the Starting WorldSpace Transform
33 | si = chain.items[ 0 ]; // First Chain Link
34 | b = pose.bones[ si.index ]; // Its Pose Bone
35 | if( b.pidx != -1 ) pTran.copy( pose.bones[ b.pidx ].world ); // Use Parent's WS Transform
36 | else pTran.copy( pose.offset ); // Use Pose's Offset if there is no parent.
37 |
38 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | // Start Processing Chain
40 | for( si of chain.items ){
41 | b = pose.bones[ si.index ]; // Get Pose Bone
42 |
43 | //----------------------------------------------
44 | cTran.fromMul( pTran, si.bind ); // Compute the Bone's Resting WS Transform
45 | si.spring.setTarget( cTran.pos ) // Set new Target
46 |
47 | // If no spring movement, save WS transform and move to next item
48 | if( !si.spring.update( dt ) ){
49 | pTran.copy( cTran );
50 | continue;
51 | }
52 |
53 | //----------------------------------------------
54 | iTran
55 | .fromInvert( pTran ) // Need Parent WS Transform inverted...
56 | .transformVec3( si.spring.val, b.local.pos ); // to move spring position to Local Space,
57 |
58 | pTran.mul( si.bind.rot, b.local.pos, si.bind.scl ); // Using new Position, Move Parent WS Transform for the next item
59 | }
60 | }
61 |
62 | }
63 |
64 | export default SpringPos;
--------------------------------------------------------------------------------
/src/bonespring/SpringRot.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Bone from '../armature/Bone';
3 | import type Pose from '../armature/Pose';
4 | import type { ISpringType } from './index'
5 | import type SpringChain from './SpringChain';
6 |
7 | import { vec3, quat } from 'gl-matrix';
8 | import Transform from '../maths/Transform';
9 | import SpringItem from './SpringItem';
10 | import QuatUtil from '../maths/QuatUtil';
11 | //#endregion
12 |
13 | class SpringRot implements ISpringType{
14 |
15 | setRestPose( chain: SpringChain, pose: Pose, resetSpring=true, debug ?: any ): void{
16 | const tail = vec3.create();
17 | let si : SpringItem;
18 | let b : Bone;
19 |
20 | for( si of chain.items ){
21 | b = pose.bones[ si.index ]; // Get Pose Bone
22 |
23 | if( resetSpring ){
24 | vec3.set( tail, 0, b.len, 0 ); // Tail's LocalSpace Position.
25 | b.world.transformVec3( tail ); // Move Tail to WorldSpace
26 | si.spring.reset( tail ); // Set Spring to Start at this Position.
27 | }
28 |
29 | si.bind.copy( b.local ); // Copy LS Transform as this will be the Actual Rest Pose of the bone.
30 | }
31 | }
32 |
33 | updatePose( chain: SpringChain, pose: Pose, dt: number, debug ?: any ): void{
34 | let si : SpringItem;
35 | let b : Bone;
36 | let tail = vec3.create();
37 | let pTran = new Transform();
38 | let cTran = new Transform();
39 | let va = vec3.create();
40 | let vb = vec3.create();
41 | let rot = quat.create();
42 |
43 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44 | // Find the Starting WorldSpace Transform
45 | si = chain.items[ 0 ]; // First Chain Link
46 | b = pose.bones[ si.index ]; // Its Pose Bone
47 |
48 | pose.getWorldTransform( b.pidx, pTran );
49 |
50 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 | // Start Processing Chain
52 | for( si of chain.items ){
53 | b = pose.bones[ si.index ]; // Get Pose Bone
54 |
55 | //----------------------------------------------
56 | // Compute the Tail's Position as if this bone had never rotated
57 | // The idea is to find its resting location which will be our spring target.
58 |
59 | cTran.fromMul( pTran, si.bind ); // Compute the Bone's Resting WS Transform
60 | vec3.set( tail, 0, b.len, 0 ); // Tail's LocalSpace Position.
61 | cTran.transformVec3( tail ); // Move Tail to WorldSpace
62 |
63 | si.spring
64 | .setTarget( tail ) // Set new Target
65 | .update( dt ); // Update Spring with new Target & DeltaTime
66 |
67 | //----------------------------------------------
68 | // Compute the rotation based on two direction, one is our bone's position toward
69 | // its resting tail position with the other toward our spring tail position.
70 | vec3.sub( va, tail, cTran.pos ); // Resting Ray
71 | vec3.normalize( va, va );
72 |
73 | vec3.sub( vb, si.spring.val, cTran.pos ); // Spring Ray
74 | vec3.normalize( vb, vb );
75 |
76 | quat.rotationTo( rot, va, vb ); // Resting to Spring
77 | QuatUtil.dotNegate( rot, rot, cTran.rot ); // Prevent any Artifacts
78 | quat.mul( rot, rot, cTran.rot ); // Apply spring rotation to our resting rotation
79 | QuatUtil.pmulInvert( rot, rot, pTran.rot ); // Use parent to convert to Local Space
80 | // TODO : Normalize as a possible fix if artifacts creeping up
81 |
82 | //----------------------------------------------
83 | quat.copy( b.local.rot, rot ) // Save Result back to pose bone
84 | pTran.mul( rot, si.bind.pos, si.bind.scl ); // Using new Rotation, Move Parent WS Transform for the next item
85 | }
86 | }
87 | }
88 |
89 | export default SpringRot;
--------------------------------------------------------------------------------
/src/bonespring/implicit_euler/SpringBase.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | // http://allenchou.net/2014/04/game-math-interpolating-quaternions-with-circular-blending/
4 | // https://gafferongames.com/post/spring_physics/
5 | // http://allenchou.net/2015/04/game-math-more-on-numeric-springing/
6 | // http://allenchou.net/2015/04/game-math-precise-control-over-numeric-springing/
7 |
8 | /** implicit euler spring */
9 | class SpringBase{
10 | // #region MAIN
11 | oscPerSec = Math.PI * 2; // Oscillation per Second : How many Cycles (Pi*2) per second.
12 | damping = 1; // How much to slow down : Value between 0 and 1. 1 creates critical damping.
13 | epsilon = 0.01;
14 | // #endregion ///////////////////////////////////////////////////////////////////
15 |
16 | // #region SETTERS / GETTERS
17 | setTarget( v: any ): this{ console.log( "SET_TARGET NOT IMPLEMENTED"); return this; }
18 | setOscPerSec( sec: number ): this{ this.oscPerSec = Math.PI * 2 * sec; return this; }
19 | setDamp( damping: number ): this{ this.damping = damping; return this; }
20 |
21 | /** Damp Time, in seconds to damp. So damp 0.5 for every 2 seconds.
22 | With the idea that for every 2 seconds, about 0.5 damping has been applied */
23 | setDampRatio( damping: number, dampTime: number ): this{
24 | this.damping = Math.log( damping ) / ( -this.oscPerSec * dampTime );
25 | return this;
26 | }
27 |
28 | /** Reduce oscillation by half in X amount of seconds */
29 | setDampHalfLife( dampTime: number ){
30 | // float zeta = -ln(0.5f) / ( omega * lambda );
31 | this.damping = 0.6931472 / ( this.oscPerSec * dampTime );
32 | return this;
33 | }
34 |
35 | // Critical Damping with a speed control of how fast the cycle to run
36 | setDampExpo( dampTime: number ){
37 | this.oscPerSec = 0.6931472 / dampTime; // -Log(0.5) but in terms of OCS its 39.7 degrees over time
38 | this.damping = 1;
39 | return this
40 | }
41 |
42 | reset( v:any ): this{
43 | return this;
44 | }
45 | // #endregion ///////////////////////////////////////////////////////////////////
46 |
47 | update( dt: number ): boolean{ console.log( "UPDATE NOT IMPLEMENTED"); return false; }
48 | }
49 |
50 | export default SpringBase;
--------------------------------------------------------------------------------
/src/bonespring/implicit_euler/SpringFloat.ts:
--------------------------------------------------------------------------------
1 | import SpringBase from './SpringBase';
2 |
3 | // implicit euler spring
4 | class SpringFloat extends SpringBase {
5 | // #region MAIN
6 | vel = 0; // Velocity
7 | val = 0; // Currvent Value
8 | tar = 0; // Target Value
9 | // #endregion ///////////////////////////////////////////////////////////////////
10 |
11 | // #region SETTERS / GETTERS
12 | setTarget( v: number ){ this.tar = v; return this; }
13 |
14 | reset( v ?: number ): this{
15 | this.vel = 0;
16 |
17 | if( v != undefined ){
18 | this.val = v;
19 | this.tar = v;
20 | }else{
21 | this.val = 0
22 | this.tar = 0;
23 | }
24 | return this;
25 | }
26 | // #endregion ///////////////////////////////////////////////////////////////////
27 |
28 | update( dt: number ): boolean{
29 | if( this.vel == 0 && this.tar == this.val ) return false;
30 |
31 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 | if ( Math.abs( this.vel ) < this.epsilon && Math.abs( this.tar - this.val ) < this.epsilon ) {
33 | this.vel = 0;
34 | this.val = this.tar;
35 | return true;
36 | }
37 |
38 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | let friction = 1.0 + 2.0 * dt * this.damping * this.oscPerSec,
40 | dt_osc = dt * this.oscPerSec**2,
41 | dt2_osc = dt * dt_osc,
42 | det_inv = 1.0 / ( friction + dt2_osc );
43 |
44 | this.vel = ( this.vel + dt_osc * ( this.tar - this.val ) ) * det_inv;
45 | this.val = ( friction * this.val + dt * this.vel + dt2_osc * this.tar ) * det_inv;
46 |
47 | return true;
48 | }
49 | }
50 |
51 | export default SpringFloat;
--------------------------------------------------------------------------------
/src/bonespring/implicit_euler/SpringQuat.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import { quat } from 'gl-matrix';
3 | import SpringBase from './SpringBase';
4 | import QuatUtil from '../../maths/QuatUtil';
5 | //#endregion
6 |
7 | // implicit euler spring
8 | class SpringQuat extends SpringBase {
9 | // #region MAIN
10 | vel = quat.create(); // Velocity
11 | val = quat.create(); // Current Value
12 | tar = quat.create(); // Target Value
13 | epsilon = 0.00001;
14 | // #endregion ///////////////////////////////////////////////////////////////////
15 |
16 | //#region SETTERS / GETTERS
17 | setTarget( v: quat, doNorm=false ): this{
18 | quat.copy( this.tar, v );
19 | if( doNorm ) quat.normalize( this.tar, this.tar );
20 | return this;
21 | }
22 |
23 | reset( v ?: quat ){
24 | quat.identity( this.vel );
25 |
26 | if( v ){
27 | quat.copy( this.val, v );
28 | quat.copy( this.tar, v );
29 | }else{
30 | quat.identity( this.val );
31 | quat.identity( this.tar );
32 | }
33 |
34 | return this;
35 | }
36 | //#endregion ///////////////////////////////////////////////////////////////////
37 |
38 | update( dt: number ): boolean{
39 | if( QuatUtil.isZero( this.vel ) && QuatUtil.lenSqr( this.tar, this.val ) == 0 ) return false;
40 |
41 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42 | if ( quat.sqrLen( this.vel ) < this.epsilon && QuatUtil.lenSqr( this.tar, this.val ) < this.epsilon ) {
43 | quat.set( this.vel, 0, 0, 0, 0 );
44 | quat.copy( this.val, this.tar );
45 | return true;
46 | }
47 |
48 | if( quat.dot( this.tar, this.val ) < 0 ) QuatUtil.negate( this.tar ); // Can screw up skinning if axis not in same hemisphere
49 |
50 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 | let friction = 1.0 + 2.0 * dt * this.damping * this.oscPerSec,
52 | dt_osc = dt * this.oscPerSec**2,
53 | dt2_osc = dt * dt_osc,
54 | det_inv = 1.0 / ( friction + dt2_osc );
55 |
56 | this.vel[0] = ( this.vel[0] + dt_osc * ( this.tar[0] - this.val[0] ) ) * det_inv;
57 | this.vel[1] = ( this.vel[1] + dt_osc * ( this.tar[1] - this.val[1] ) ) * det_inv;
58 | this.vel[2] = ( this.vel[2] + dt_osc * ( this.tar[2] - this.val[2] ) ) * det_inv;
59 | this.vel[3] = ( this.vel[3] + dt_osc * ( this.tar[3] - this.val[3] ) ) * det_inv;
60 |
61 | this.val[0] = ( friction * this.val[0] + dt * this.vel[0] + dt2_osc * this.tar[0] ) * det_inv;
62 | this.val[1] = ( friction * this.val[1] + dt * this.vel[1] + dt2_osc * this.tar[1] ) * det_inv;
63 | this.val[2] = ( friction * this.val[2] + dt * this.vel[2] + dt2_osc * this.tar[2] ) * det_inv;
64 | this.val[3] = ( friction * this.val[3] + dt * this.vel[3] + dt2_osc * this.tar[3] ) * det_inv;
65 |
66 | quat.normalize( this.val, this.val );
67 | return true;
68 | }
69 | }
70 |
71 | export default SpringQuat;
--------------------------------------------------------------------------------
/src/bonespring/implicit_euler/SpringVec3.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import { vec3 } from 'gl-matrix';
3 | import SpringBase from './SpringBase';
4 | import Vec3Util from '../../maths/Vec3Util';
5 | //#endregion
6 |
7 |
8 | // implicit euler spring
9 | class SpringVec3 extends SpringBase {
10 | //#region MAIN
11 | vel = vec3.create(); // Velocity
12 | val = vec3.create(); // Current Value
13 | tar = vec3.create(); // Target Value
14 | epsilon = 0.000001;
15 | //#endregion ///////////////////////////////////////////////////////////////////
16 |
17 | // #region SETTERS / GETTERS
18 | setTarget( v: vec3 ){ vec3.copy( this.tar, v ); return this; }
19 |
20 | reset( v: vec3 ){
21 | if( v ){
22 | vec3.copy( this.val, v );
23 | vec3.copy( this.tar, v );
24 | }else{
25 | vec3.set( this.val, 0, 0, 0 );
26 | vec3.set( this.tar, 0, 0, 0 );
27 | }
28 |
29 | return this;
30 | }
31 | //#endregion ///////////////////////////////////////////////////////////////////
32 |
33 | update( dt: number ): boolean{
34 | if( Vec3Util.isZero( this.vel ) && Vec3Util.lenSqr( this.tar, this.val ) == 0 ) return false;
35 |
36 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | if ( vec3.sqrLen( this.vel ) < this.epsilon && Vec3Util.lenSqr( this.tar, this.val ) < this.epsilon ) {
38 | vec3.set( this.vel, 0, 0, 0 );
39 | vec3.copy( this.val, this.tar );
40 | return true;
41 | }
42 |
43 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44 | let friction = 1.0 + 2.0 * dt * this.damping * this.oscPerSec,
45 | dt_osc = dt * this.oscPerSec**2,
46 | dt2_osc = dt * dt_osc,
47 | det_inv = 1.0 / ( friction + dt2_osc );
48 |
49 | this.vel[0] = ( this.vel[0] + dt_osc * ( this.tar[0] - this.val[0] ) ) * det_inv;
50 | this.vel[1] = ( this.vel[1] + dt_osc * ( this.tar[1] - this.val[1] ) ) * det_inv;
51 | this.vel[2] = ( this.vel[2] + dt_osc * ( this.tar[2] - this.val[2] ) ) * det_inv;
52 |
53 | this.val[0] = ( friction * this.val[0] + dt * this.vel[0] + dt2_osc * this.tar[0] ) * det_inv;
54 | this.val[1] = ( friction * this.val[1] + dt * this.vel[1] + dt2_osc * this.tar[1] ) * det_inv;
55 | this.val[2] = ( friction * this.val[2] + dt * this.vel[2] + dt2_osc * this.tar[2] ) * det_inv;
56 |
57 | return true;
58 | }
59 | }
60 |
61 | export default SpringVec3;
--------------------------------------------------------------------------------
/src/bonespring/index.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Armature from '../armature/Armature';
3 | import type Pose from '../armature/Pose';
4 | import type SpringItem from './SpringItem';
5 | import SpringChain from './SpringChain';
6 | //#endregion
7 |
8 | interface ISpringType{
9 | setRestPose( chain: SpringChain, pose: Pose, resetSpring: boolean, debug ?: any ): void;
10 | updatePose( chain: SpringChain, pose: Pose, dt: number, debug ?: any ): void;
11 | }
12 |
13 | class BoneSpring{
14 | arm : Armature;
15 | items : Map< string, SpringChain > = new Map();
16 | constructor( arm: Armature ){
17 | this.arm = arm;
18 | }
19 |
20 | addRotChain( chName: string, bNames: string[], osc=5.0, damp=0.5 ): this{
21 | const chain = new SpringChain( chName, 0 ); // Rotation Spring Chain
22 | chain.setBones( bNames, this.arm, osc, damp ); // Setup Chain
23 | this.items.set( chName, chain ); // Save
24 | return this;
25 | }
26 |
27 | addPosChain( chName: string, bNames: string[], osc=5.0, damp=0.5 ): this{
28 | const chain = new SpringChain( chName, 1 ); // Position Spring Chain
29 | chain.setBones( bNames, this.arm, osc, damp ); // Setup Chain
30 | this.items.set( chName, chain ); // Save
31 | return this;
32 | }
33 |
34 | setRestPose( pose: Pose, resetSpring=true, debug ?: any ): this{
35 | let ch: SpringChain;
36 | for( ch of this.items.values() ){
37 | ch.setRestPose( pose, resetSpring, debug );
38 | }
39 | return this;
40 | }
41 |
42 | updatePose( dt: number, pose: Pose, doWorldUpdate:false, debug ?: any ): this{
43 | let ch: SpringChain;
44 | for( ch of this.items.values() ){
45 | ch.updatePose( dt, pose, debug );
46 | }
47 |
48 | if( doWorldUpdate ) pose.updateWorld( true );
49 | return this;
50 | }
51 |
52 | //#region SPRING SETTERS
53 |
54 | /** Set Oscillation Per Section for all Chain Items */
55 | setOsc( chName: string, osc: number ): this{
56 | const ch = this.items.get( chName );
57 | if( !ch ){ console.error( 'Spring Chain name not found', chName ); return this }
58 |
59 | let si: SpringItem;
60 | for( si of ch.items ) si.spring.setOscPerSec( osc );
61 |
62 | return this;
63 | }
64 |
65 | /** Spread a Oscillation range on the chain */
66 | setOscRange( chName: string, a: number, b:number ): this{
67 | const ch = this.items.get( chName );
68 | if( !ch ){ console.error( 'Spring Chain name not found', chName ); return this }
69 |
70 | const len = ch.items.length - 1;
71 | let t : number;
72 | for( let i=0; i <= len; i++ ){
73 | t = i / len;
74 | ch.items[ i ].spring.setOscPerSec( a*(1-t) + b*t );
75 | }
76 |
77 | return this;
78 | }
79 |
80 | setDamp( chName: string, damp: number ): this{
81 | const ch = this.items.get( chName );
82 | if( !ch ){ console.error( 'Spring Chain name not found', chName ); return this }
83 |
84 | let si: SpringItem;
85 | for( si of ch.items ) si.spring.setDamp( damp );
86 |
87 | return this;
88 | }
89 |
90 | setDampRange( chName: string, a: number, b:number ): this{
91 | const ch = this.items.get( chName );
92 | if( !ch ){ console.error( 'Spring Chain name not found', chName ); return this }
93 |
94 | const len = ch.items.length - 1;
95 | let t : number;
96 | for( let i=0; i <= len; i++ ){
97 | t = i / len;
98 | ch.items[ i ].spring.setDamp( a*(1-t) + b*t );
99 | }
100 |
101 | return this;
102 | }
103 | //#endregion
104 |
105 | }
106 |
107 | export default BoneSpring;
108 | export type { ISpringType };
--------------------------------------------------------------------------------
/src/ikrig/IKData.ts:
--------------------------------------------------------------------------------
1 | import { vec3 } from "gl-matrix";
2 |
3 | export class DirScale{
4 | lenScale : number = 1;
5 | effectorDir : vec3 = [0,0,0];
6 | poleDir : vec3 = [0,0,0];
7 |
8 | copy( v: DirScale ): void{
9 | this.lenScale = v.lenScale;
10 | vec3.copy( this.effectorDir, v.effectorDir );
11 | vec3.copy( this.poleDir, v.poleDir );
12 | }
13 |
14 | clone(): DirScale{
15 | const c = new DirScale();
16 | c.copy( this );
17 | return c;
18 | }
19 | }
20 |
21 | export class Dir{
22 | effectorDir : vec3 = [0,0,0];
23 | poleDir : vec3 = [0,0,0];
24 |
25 | copy( v: Dir ): void{
26 | vec3.copy( this.effectorDir, v.effectorDir );
27 | vec3.copy( this.poleDir, v.poleDir );
28 | }
29 | }
30 |
31 | export class DirEnds{
32 | startEffectorDir : vec3 = [0,0,0];
33 | startPoleDir : vec3 = [0,0,0];
34 | endEffectorDir : vec3 = [0,0,0];
35 | endPoleDir : vec3 = [0,0,0];
36 |
37 | copy( v: DirEnds ): void{
38 | vec3.copy( this.startEffectorDir, v.startEffectorDir );
39 | vec3.copy( this.startPoleDir, v.startPoleDir );
40 | vec3.copy( this.endEffectorDir, v.endEffectorDir );
41 | vec3.copy( this.endPoleDir, v.endPoleDir );
42 | }
43 | }
44 |
45 | export class Hip{
46 | effectorDir : vec3 = [0,0,0];
47 | poleDir : vec3 = [0,0,0];
48 | pos : vec3 = [0,0,0];
49 | bindHeight : number = 1;
50 | isAbsolute : boolean = false;
51 |
52 | copy( v: Hip ): void{
53 | this.bindHeight = v.bindHeight;
54 | this.isAbsolute = v.isAbsolute;
55 | vec3.copy( this.effectorDir, v.effectorDir );
56 | vec3.copy( this.poleDir, v.poleDir );
57 | vec3.copy( this.pos, v.pos );
58 | }
59 | }
--------------------------------------------------------------------------------
/src/ikrig/animation/BipedIKPose.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORT
2 | import type BipedRig from '../rigs/BipedRig';
3 | import type { Pose } from '../../armature/index'
4 |
5 | import * as IKData from '../IKData';
6 | //#endregion
7 |
8 | class BipedIKPose{
9 | //#region MAIN
10 | hip = new IKData.Hip();
11 | spine = new IKData.DirEnds();
12 | head = new IKData.Dir();
13 |
14 | armL = new IKData.DirScale();
15 | armR = new IKData.DirScale();
16 | legL = new IKData.DirScale();
17 | legR = new IKData.DirScale();
18 |
19 | handL = new IKData.Dir();
20 | handR = new IKData.Dir();
21 | footL = new IKData.Dir();
22 | footR = new IKData.Dir();
23 |
24 | inPlace = false;
25 | inPlaceScale = [1,1,1];
26 |
27 | constructor(){}
28 | //#endregion
29 |
30 | /** Compute the IK Data from a Rig and Pose */
31 | computeFromRigPose( r: BipedRig, pose: Pose ): void{
32 | r.legL?.solver.ikDataFromPose( r.legL, pose, this.legL );
33 | r.legR?.solver.ikDataFromPose( r.legR, pose, this.legR );
34 | r.armR?.solver.ikDataFromPose( r.armR, pose, this.armR );
35 | r.armL?.solver.ikDataFromPose( r.armL, pose, this.armL );
36 |
37 | r.footL?.solver.ikDataFromPose( r.footL, pose, this.footL );
38 | r.footR?.solver.ikDataFromPose( r.footR, pose, this.footR );
39 | r.handR?.solver.ikDataFromPose( r.handR, pose, this.handR );
40 | r.handR?.solver.ikDataFromPose( r.handL, pose, this.handL );
41 |
42 | r.head?.solver.ikDataFromPose( r.head, pose, this.head );
43 | r.spine?.solver.ikDataFromPose( r.spine, pose, this.spine );
44 | r.hip?.solver.ikDataFromPose( r.hip, pose, this.hip );
45 |
46 | if( this.inPlace && r.hip ){
47 | this.hip.pos[ 0 ] *= this.inPlaceScale[ 0 ];
48 | this.hip.pos[ 1 ] *= this.inPlaceScale[ 1 ];
49 | this.hip.pos[ 2 ] *= this.inPlaceScale[ 2 ];
50 | }
51 | }
52 |
53 | applyToRig( r: BipedRig ): void{
54 | r.legL?.solver.setTargetDir( this.legL.effectorDir, this.legL.poleDir, this.legL.lenScale );
55 | r.legR?.solver.setTargetDir( this.legR.effectorDir, this.legR.poleDir, this.legR.lenScale );
56 | r.armL?.solver.setTargetDir( this.armL.effectorDir, this.armL.poleDir, this.armL.lenScale );
57 | r.armR?.solver.setTargetDir( this.armR.effectorDir, this.armR.poleDir, this.armR.lenScale );
58 |
59 | r.footL?.solver.setTargetDir( this.footL.effectorDir, this.footL.poleDir );
60 | r.footR?.solver.setTargetDir( this.footR.effectorDir, this.footR.poleDir );
61 | r.handL?.solver.setTargetDir( this.handL.effectorDir, this.handL.poleDir );
62 | r.handR?.solver.setTargetDir( this.handR.effectorDir, this.handR.poleDir );
63 | r.head?.solver.setTargetDir( this.head.effectorDir, this.head.poleDir );
64 |
65 | r.hip?.solver
66 | .setTargetDir( this.hip.effectorDir, this.hip.poleDir )
67 | .setMovePos( this.hip.pos, this.hip.isAbsolute, this.hip.bindHeight );
68 |
69 | r.spine?.solver
70 | .setStartDir( this.spine.startEffectorDir, this.spine.startPoleDir )
71 | .setEndDir( this.spine.endEffectorDir, this.spine.endPoleDir );
72 | }
73 |
74 | copy( r: BipedIKPose ): this{
75 | this.hip.copy( r.hip );
76 | this.spine.copy( r.spine );
77 | this.head.copy( r.head );
78 |
79 | this.armL.copy( r.armL );
80 | this.armR.copy( r.armR );
81 | this.legL.copy( r.legL );
82 | this.legR.copy( r.legR );
83 |
84 | this.handL.copy( r.handL );
85 | this.handR.copy( r.handR );
86 | this.footL.copy( r.footL );
87 | this.footR.copy( r.footR );
88 | return this;
89 | }
90 | }
91 |
92 | export default BipedIKPose;
--------------------------------------------------------------------------------
/src/ikrig/animation/additives/EffectorScale.ts:
--------------------------------------------------------------------------------
1 | import type BipedIKPose from '../BipedIKPose';
2 | import type IIKPoseAdditive from '../support/IIKPoseAdditive';
3 |
4 | export default class EffectorScale implements IIKPoseAdditive{
5 | scale: number = 1.0;
6 | constructor( s: number ){
7 | this.scale = s;
8 | }
9 |
10 | apply( key:string, src: BipedIKPose ): void{
11 | const o : any = (src as any)[ key ];
12 | o.lenScale *= this.scale;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/ikrig/animation/additives/IKPoseAdditives.ts:
--------------------------------------------------------------------------------
1 | import BipedIKPose from "../BipedIKPose";
2 | import IIKPoseAdditive from "../support/IIKPoseAdditive";
3 |
4 | class Additive{
5 | key : string;
6 | add : IIKPoseAdditive;
7 | constructor( k: string, add: IIKPoseAdditive ){
8 | this.key = k
9 | this.add = add;
10 | }
11 | }
12 |
13 | class IKPoseAddtives{
14 | items : Array< Additive > = [];
15 |
16 | add( key: string, add: IIKPoseAdditive ): this{
17 | this.items.push( new Additive( key, add ) );
18 | return this
19 | }
20 |
21 | apply( src: BipedIKPose ): void{
22 | let a: Additive;
23 | for( a of this.items ) a.add.apply( a.key, src );
24 | }
25 | }
26 |
27 | export default IKPoseAddtives;
--------------------------------------------------------------------------------
/src/ikrig/animation/additives/PositionOffset.ts:
--------------------------------------------------------------------------------
1 | import { vec3 } from 'gl-matrix';
2 | import type BipedIKPose from '../BipedIKPose';
3 | import type IIKPoseAdditive from '../support/IIKPoseAdditive';
4 |
5 | export default class PositionOffset implements IIKPoseAdditive{
6 | pos: vec3 = [0,0,0];
7 | constructor( p: vec3 ){
8 | vec3.copy( this.pos, p );
9 | }
10 |
11 | apply( key:string, src: BipedIKPose ): void{
12 | const o : any = (src as any)[ key ];
13 | vec3.add( o.pos, o.pos, this.pos );
14 | }
15 | }
--------------------------------------------------------------------------------
/src/ikrig/animation/additives/index.ts:
--------------------------------------------------------------------------------
1 | import EffectorScale from './EffectorScale';
2 | import IKPoseAdditives from './IKPoseAdditives';
3 | import PositionOffset from './PositionOffset';
4 |
5 | export{
6 | EffectorScale,
7 | IKPoseAdditives,
8 | PositionOffset,
9 | };
--------------------------------------------------------------------------------
/src/ikrig/animation/support/IIKPoseAdditive.ts:
--------------------------------------------------------------------------------
1 | import type BipedIKPose from '../BipedIKPose';
2 |
3 | export default interface IIKPoseAdditive{
4 | apply( key: string, src: BipedIKPose): void;
5 | }
--------------------------------------------------------------------------------
/src/ikrig/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import IKRig from './rigs/IKRig';
3 | import BipedRig from './rigs/BipedRig';
4 | import { IKChain, IKLink } from './rigs/IKChain';
5 |
6 | import BipedIKPose from './animation/BipedIKPose';
7 |
8 | import * as IKData from './IKData';
9 |
10 | import {
11 | SwingTwistEndsSolver,
12 | SwingTwistSolver,
13 | LimbSolver,
14 | HipSolver,
15 | } from './solvers/index'
16 |
17 | export {
18 | IKData, BipedIKPose,
19 | IKRig, BipedRig, IKChain, IKLink,
20 |
21 | SwingTwistEndsSolver,
22 | SwingTwistSolver,
23 | LimbSolver,
24 | HipSolver,
25 | };
--------------------------------------------------------------------------------
/src/ikrig/rigs/IKRig.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type { Armature, Pose } from '../../armature/index'
3 | import { IKChain } from './IKChain';
4 | //#endregion
5 |
6 | class IKRig{
7 | //#region MAIN
8 | items: Map< string, IKChain > = new Map();
9 | constructor(){}
10 | //#endregion
11 |
12 | //#region METHODS
13 |
14 | // Change the Bind Transform for all the chains
15 | // Mostly used for late binding a TPose when armature isn't naturally in a TPose
16 | bindPose( pose: Pose ): this{
17 | let ch: IKChain;
18 | for( ch of this.items.values() ) ch.bindToPose( pose );
19 | return this;
20 | }
21 |
22 | updateBoneLengths( pose: Pose ): this{
23 | let ch: IKChain;
24 |
25 | for( ch of this.items.values() ){
26 | ch.resetLengths( pose );
27 | }
28 |
29 | return this;
30 | }
31 |
32 | get( name: string ): IKChain | undefined{
33 | return this.items.get( name );
34 | }
35 |
36 | add( arm: Armature, name:string, bNames: string[] ): IKChain{
37 | const chain = new IKChain( bNames, arm );
38 | this.items.set( name, chain );
39 | return chain;
40 | }
41 | //#endregion
42 | }
43 |
44 | export default IKRig;
--------------------------------------------------------------------------------
/src/ikrig/solvers/LimbSolver.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Pose from '../../armature/Pose';
3 | import type { IKChain } from '../rigs/IKChain';
4 |
5 | import QuatUtil from '../../maths/QuatUtil';
6 | import Vec3Util from '../../maths/Vec3Util';
7 | import { quat } from 'gl-matrix';
8 |
9 | import SwingTwistBase from './support/SwingTwistBase';
10 | //#endregion
11 |
12 | function lawcos_sss( aLen: number, bLen: number, cLen: number ): number{
13 | // Law of Cosines - SSS : cos(C) = (a^2 + b^2 - c^2) / 2ab
14 | // The Angle between A and B with C being the opposite length of the angle.
15 | let v = ( aLen*aLen + bLen*bLen - cLen*cLen ) / ( 2 * aLen * bLen );
16 | if( v < -1 ) v = -1; // Clamp to prevent NaN Errors
17 | else if( v > 1 ) v = 1;
18 | return Math.acos( v );
19 | }
20 |
21 | class LimbSolver extends SwingTwistBase{
22 | bendDir : number = 1; // Switching to Negative will flip the rotation arc
23 | invertBend(): this{ this.bendDir = -this.bendDir; return this; }
24 |
25 | resolve( chain: IKChain, pose: Pose, debug?:any ): void{
26 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 | // Start by Using SwingTwist to target the bone toward the EndEffector
28 | const ST = this._swingTwist
29 | const [ rot, pt ] = ST.getWorldRot( chain, pose, debug );
30 |
31 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 | let b0 = chain.links[ 0 ],
33 | b1 = chain.links[ 1 ],
34 | alen = b0.len,
35 | blen = b1.len,
36 | clen = Vec3Util.len( ST.effectorPos, ST.originPos ),
37 | prot : quat = [0,0,0,0],
38 | rad : number;
39 |
40 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 | // FIRST BONE
42 | rad = lawcos_sss( alen, clen, blen ); // Get the Angle between First Bone and Target.
43 |
44 | QuatUtil.pmulAxisAngle( rot, ST.orthoDir, -rad * this.bendDir, rot ); // Use the Axis X to rotate by Radian Angle
45 | quat.copy( prot, rot ); // Save For Next Bone as Starting Point.
46 | QuatUtil.pmulInvert( rot, rot, pt.rot ); // To Local
47 |
48 | pose.setLocalRot( b0.idx, rot ); // Save to Pose
49 |
50 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 | // SECOND BONE
52 | // Need to rotate from Right to Left, So take the angle and subtract it from 180 to rotate from
53 | // the other direction. Ex. L->R 70 degrees == R->L 110 degrees
54 | rad = Math.PI - lawcos_sss( alen, blen, clen );
55 |
56 | quat.mul( rot, prot, b1.bind.rot ); // Get the Bind WS Rotation for this bone
57 | QuatUtil.pmulAxisAngle( rot, ST.orthoDir, rad * this.bendDir, rot ); // Rotation that needs to be applied to bone.
58 | QuatUtil.pmulInvert( rot, rot, prot ); // To Local Space
59 |
60 | pose.setLocalRot( b1.idx, rot ); // Save to Pose
61 | }
62 |
63 | }
64 |
65 | export default LimbSolver;
--------------------------------------------------------------------------------
/src/ikrig/solvers/PistonSolver.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Pose from '../../armature/Pose';
3 | import type { IKChain, IKLink } from '../rigs/IKChain';
4 |
5 | import QuatUtil from '../../maths/QuatUtil';
6 | import Vec3Util from '../../maths/Vec3Util';
7 | import { vec3 } from 'gl-matrix';
8 |
9 | import SwingTwistBase from './support/SwingTwistBase';
10 | //#endregion
11 |
12 | class PistonSolver extends SwingTwistBase{
13 |
14 | resolve( chain: IKChain, pose: Pose, debug?:any ): void{
15 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 | // Start by Using SwingTwist to target the bone toward the EndEffector
17 | const ST = this._swingTwist
18 | const [ rot, pt ] = ST.getWorldRot( chain, pose, debug );
19 | const effLen = Vec3Util.len( ST.effectorPos, ST.originPos );
20 | const v : vec3 = [0,0,0];
21 | let lnk : IKLink = chain.first();
22 | let i : number;
23 |
24 | // Apply SwingTwist Rotation
25 | QuatUtil.pmulInvert( rot, rot, pt.rot );
26 | pose.setLocalRot( lnk.idx, rot );
27 |
28 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29 | // Check if target length is less then any bone, then compress all bones down to zero
30 | for( lnk of chain.links ){
31 | if( lnk.len >= effLen ){
32 | for( i=1; i < chain.count; i++ )
33 | pose.setLocalPos( chain.links[ i ].idx, [0,0,0] );
34 | return;
35 | }
36 | }
37 |
38 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | // Bones can only shift into their parent bone. So the final bone's length in the chain isn't needed.
40 | // So we get the acount of space we need to retract, then divide it evenly based on the ratio of bone
41 | // lengths. So if Bone0 = 2 and Bone1 = 8, that means Bone0 only needs to travel 20% of the total retraction
42 | // length where bone1 does 80%.
43 | // Keep in mind, we travel based on parent length BUT apply change to child.
44 |
45 | const endIdx = chain.count - 1;
46 | const deltaLen = chain.length - effLen; // How Much distance needing to move
47 | const incInv = 1 / ( chain.length - chain.links[ endIdx ].len ); // Get Total Available Space of Movement, Inverted to remove division later
48 |
49 | for( i=0; i < endIdx; i++ ){
50 | lnk = chain.links[ i ];
51 |
52 | // Normalize Bone Length In relation to Total, Use that as a scale of total delta movement
53 | // then subtract from the bone's length, apply that length to the next bone's Position.
54 | v[ 1 ] = lnk.len - deltaLen * ( lnk.len * incInv );
55 |
56 | pose.setLocalPos( chain.links[ i+1 ].idx, v );
57 | }
58 |
59 | }
60 |
61 | }
62 |
63 | export default PistonSolver;
--------------------------------------------------------------------------------
/src/ikrig/solvers/ZSolver.ts:
--------------------------------------------------------------------------------
1 |
2 | //#region IMPORTS
3 | import type Pose from '../../armature/Pose';
4 | import type { IKChain } from '../rigs/IKChain';
5 |
6 | import QuatUtil from '../../maths/QuatUtil';
7 | import Vec3Util from '../../maths/Vec3Util';
8 | import { quat } from 'gl-matrix';
9 |
10 | import SwingTwistBase from './support/SwingTwistBase';
11 | //#endregion
12 |
13 | /* [[ NOTES ]]
14 | Get the length of the bones, the calculate the ratio length for the bones based on the chain length
15 | The 3 bones when placed in a zig-zag pattern creates a Parallelogram shape. We can break the shape down into two triangles
16 | By using the ratio of the Target length divided between the 2 triangles, then using the first bone + half of the second bound
17 | to solve for the top 2 joiints, then using the half of the second bone + 3rd bone to solve for the bottom joint.
18 | If all bones are equal length, then we only need to use half of the target length and only test one triangle and use that for
19 | both triangles, but if bones are uneven, then we need to solve an angle for each triangle which this function does.
20 | */
21 |
22 | function lawcos_sss( aLen: number, bLen: number, cLen: number ): number{
23 | // Law of Cosines - SSS : cos(C) = (a^2 + b^2 - c^2) / 2ab
24 | // The Angle between A and B with C being the opposite length of the angle.
25 | let v = ( aLen*aLen + bLen*bLen - cLen*cLen ) / ( 2 * aLen * bLen );
26 | if( v < -1 ) v = -1; // Clamp to prevent NaN Errors
27 | else if( v > 1 ) v = 1;
28 | return Math.acos( v );
29 | }
30 |
31 | class ZSolver extends SwingTwistBase{
32 |
33 | resolve( chain: IKChain, pose: Pose, debug?:any ): void{
34 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 | // Start by Using SwingTwist to target the bone toward the EndEffector
36 | const ST = this._swingTwist
37 | const [ rot, pt ] = ST.getWorldRot( chain, pose, debug );
38 |
39 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
40 | const b0 = chain.links[ 0 ];
41 | const b1 = chain.links[ 1 ];
42 | const b2 = chain.links[ 2 ];
43 | const a_len = b0.len; // Length of First 3 Bones of Chain
44 | const b_len = b1.len;
45 | const c_len = b2.len;
46 | const mh_len = b1.len * 0.5; // Half the length of the middle bone.
47 |
48 | // How much to subdivide the Target length between the two triangles
49 | const eff_len = Vec3Util.len( ST.effectorPos, ST.originPos );
50 | const t_ratio = ( a_len + mh_len ) / ( a_len + b_len + c_len );
51 | const ta_len = eff_len * t_ratio; // Long Side Length for 1st Triangle : 0 & 1
52 | const tb_len = eff_len - ta_len; // Long Side Length for 2nd Triangle : 1 & 2
53 |
54 | const prot : quat = [0,0,0,0];
55 | const prot2 : quat = [0,0,0,0];
56 | let rad : number;
57 |
58 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59 | // 1ST BONE a_len, ta_len, bh_len
60 | rad = lawcos_sss( a_len, ta_len, mh_len ); // Get the Angle between First Bone and Target.
61 | QuatUtil.pmulAxisAngle( rot, ST.orthoDir, -rad, rot ); // Use the Axis X to rotate by Radian Angle
62 | quat.copy( prot, rot ); // Save For Next Bone as its WorldSpace Parent
63 | QuatUtil.pmulInvert( rot, rot, pt.rot ); // To Local
64 | pose.setLocalRot( b0.idx, rot ); // Save to Pose
65 |
66 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67 | // 2ND BONE
68 | rad = Math.PI - lawcos_sss( a_len, mh_len, ta_len );
69 | quat.mul( rot, prot, b1.bind.rot ); // Move local bind rot to World Space
70 | QuatUtil.pmulAxisAngle( rot, ST.orthoDir, rad, rot ); // Rotation that needs to be applied to bone, same as prev bone
71 | quat.copy( prot2, rot ); // Save for next bone
72 | QuatUtil.pmulInvert( rot, rot, prot ); // To Local
73 | pose.setLocalRot( b1.idx, rot ); // Save to Pose
74 |
75 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76 | // 3RD BONE
77 | rad = Math.PI - lawcos_sss( c_len, mh_len, tb_len );
78 | quat.mul( rot, prot2, b2.bind.rot ); // Move local bind rot to World Space
79 | QuatUtil.pmulAxisAngle( rot, ST.orthoDir, -rad, rot ); // Rotation that needs to be applied to bone, same as prev bone
80 | QuatUtil.pmulInvert( rot, rot, prot2 ); // To Local
81 | pose.setLocalRot( b2.idx, rot ); // Save to Pose
82 | }
83 |
84 | }
85 |
86 | export default ZSolver;
--------------------------------------------------------------------------------
/src/ikrig/solvers/index.ts:
--------------------------------------------------------------------------------
1 | //import SwingTwistChainSolver from './SwingTwistChainSolver';
2 | import SwingTwistEndsSolver from './SwingTwistEndsSolver';
3 | import SwingTwistSolver from './SwingTwistSolver';
4 | import HipSolver from './HipSolver';
5 |
6 | import LimbSolver from './LimbSolver';
7 | import ZSolver from './ZSolver';
8 | import ArcSolver from './ArcSolver';
9 | import ArcSinSolver from './ArcSinSolver';
10 | import PistonSolver from './PistonSolver';
11 | import SpringSolver from './SpringSolver';
12 | import TrapezoidSolver from './TrapezoidSolver';
13 | import FabrikSolver from './FabrikSolver';
14 | import CatenarySolver from './CatenarySolver';
15 | import NaturalCCDSolver from './NaturalCCDSolver';
16 |
17 | export {
18 | //SwingTwistChainSolver,
19 | SwingTwistEndsSolver,
20 | SwingTwistSolver,
21 | HipSolver,
22 | LimbSolver,
23 | ZSolver,
24 | ArcSolver,
25 | ArcSinSolver,
26 | PistonSolver,
27 | SpringSolver,
28 | TrapezoidSolver,
29 | FabrikSolver,
30 | CatenarySolver,
31 | NaturalCCDSolver,
32 | };
--------------------------------------------------------------------------------
/src/ikrig/solvers/support/ISolver.ts:
--------------------------------------------------------------------------------
1 | import type Pose from '../../../armature/Pose';
2 | import type { IKChain } from '../..';
3 |
4 | export interface ISolver{
5 | resolve( chain: IKChain, pose: Pose, debug?:any ): void;
6 | }
--------------------------------------------------------------------------------
/src/ikrig/solvers/support/SwingTwistBase.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import type Pose from '../../../armature/Pose';
3 | import type { IKChain } from '../../rigs/IKChain';
4 | import type { IKData } from '../..';
5 | import type { ISolver } from './ISolver';
6 |
7 | import { vec3 } from 'gl-matrix';
8 |
9 | import SwingTwistSolver from '../SwingTwistSolver';
10 | //#endregion
11 |
12 |
13 | class SwingTwistBase implements ISolver{
14 | //#region MAIN
15 | _swingTwist = new SwingTwistSolver();
16 |
17 | initData( pose?: Pose, chain?: IKChain ): this{
18 | if( pose && chain ) this._swingTwist.initData( pose, chain );
19 | return this;
20 | }
21 | //#endregion
22 |
23 | //#region SETTING TARGET DATA
24 | setTargetDir( e: vec3, pole ?: vec3, effectorScale ?: number ): this{ this._swingTwist.setTargetDir( e, pole, effectorScale ); return this; }
25 | setTargetPos( v: vec3, pole ?: vec3 ): this{ this._swingTwist.setTargetPos( v, pole ); return this; }
26 | setTargetPole( v: vec3 ): this{ this._swingTwist.setTargetPole( v ); return this; }
27 | //#endregion
28 |
29 | resolve( chain: IKChain, pose: Pose, debug?:any ): void{}
30 |
31 | ikDataFromPose( chain: IKChain, pose: Pose, out: IKData.DirScale ): void{
32 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33 | // Length Scaled & Effector Direction
34 | const p0 : vec3 = chain.getStartPosition( pose );
35 | const p1 : vec3 = chain.getTailPosition( pose, true );
36 | const dir : vec3 = vec3.sub( [0,0,0], p1, p0 );
37 |
38 | out.lenScale = vec3.len( dir ) / chain.length;
39 | vec3.normalize( out.effectorDir, dir );
40 |
41 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42 | // Pole Direction
43 | const lnk = chain.first(); // Chain Link : Pole is based on the first Bone's Rotation
44 | const bp = pose.bones[ lnk.idx ]; // Bone ref from Pose
45 |
46 | vec3.transformQuat( dir, lnk.poleDir, bp.world.rot ); // Get Alt Pole Direction from Pose
47 | vec3.cross( dir, dir, out.effectorDir ); // Get orthogonal Direction...
48 | vec3.cross( dir, out.effectorDir, dir ); // to Align Pole to Effector
49 | vec3.normalize( out.poleDir, dir );
50 | }
51 | }
52 |
53 | export default SwingTwistBase;
--------------------------------------------------------------------------------
/src/maths/DualQuatUtil.ts:
--------------------------------------------------------------------------------
1 | import type { quat2 } from 'gl-matrix'
2 |
3 | class DualQuatUtil{
4 |
5 | /** Used to get data from a flat buffer of matrices */
6 | static fromBuf( out: quat2, ary : Array | Float32Array, idx: number ) : quat2 {
7 | out[ 0 ] = ary[ idx ];
8 | out[ 1 ] = ary[ idx + 1 ];
9 | out[ 2 ] = ary[ idx + 2 ];
10 | out[ 3 ] = ary[ idx + 3 ];
11 | out[ 4 ] = ary[ idx + 4 ];
12 | out[ 5 ] = ary[ idx + 5 ];
13 | out[ 6 ] = ary[ idx + 6 ];
14 | out[ 7 ] = ary[ idx + 7 ];
15 | return out;
16 | }
17 |
18 | /** Put data into a flat buffer of matrices */
19 | static toBuf( m: quat2, ary : Array | Float32Array, idx: number ) : DualQuatUtil {
20 | ary[ idx ] = m[ 0 ];
21 | ary[ idx + 1 ] = m[ 1 ];
22 | ary[ idx + 2 ] = m[ 2 ];
23 | ary[ idx + 3 ] = m[ 3 ];
24 | ary[ idx + 4 ] = m[ 4 ];
25 | ary[ idx + 5 ] = m[ 5 ];
26 | ary[ idx + 6 ] = m[ 6 ];
27 | ary[ idx + 7 ] = m[ 7 ];
28 | return this;
29 | }
30 | }
31 |
32 | export default DualQuatUtil;
--------------------------------------------------------------------------------
/src/maths/Mat4Util.ts:
--------------------------------------------------------------------------------
1 | import type { mat4 } from 'gl-matrix'
2 |
3 | class Mat4Util{
4 |
5 | /** Used to get data from a flat buffer of matrices */
6 | static fromBuf( out: mat4, ary : Array | Float32Array, idx: number ) : mat4 {
7 | out[ 0 ] = ary[ idx ];
8 | out[ 1 ] = ary[ idx + 1 ];
9 | out[ 2 ] = ary[ idx + 2 ];
10 | out[ 3 ] = ary[ idx + 3 ];
11 | out[ 4 ] = ary[ idx + 4 ];
12 | out[ 5 ] = ary[ idx + 5 ];
13 | out[ 6 ] = ary[ idx + 6 ];
14 | out[ 7 ] = ary[ idx + 7 ];
15 | out[ 8 ] = ary[ idx + 8 ];
16 | out[ 9 ] = ary[ idx + 9 ];
17 | out[ 10 ] = ary[ idx + 10 ];
18 | out[ 11 ] = ary[ idx + 11 ];
19 | out[ 12 ] = ary[ idx + 12 ];
20 | out[ 13 ] = ary[ idx + 13 ];
21 | out[ 14 ] = ary[ idx + 14 ];
22 | out[ 15 ] = ary[ idx + 15 ];
23 | return out;
24 | }
25 |
26 | /** Put data into a flat buffer of matrices */
27 | static toBuf( m: mat4, ary : Array | Float32Array, idx: number ) : Mat4Util {
28 | ary[ idx ] = m[ 0 ];
29 | ary[ idx + 1 ] = m[ 1 ];
30 | ary[ idx + 2 ] = m[ 2 ];
31 | ary[ idx + 3 ] = m[ 3 ];
32 | ary[ idx + 4 ] = m[ 4 ];
33 | ary[ idx + 5 ] = m[ 5 ];
34 | ary[ idx + 6 ] = m[ 6 ];
35 | ary[ idx + 7 ] = m[ 7 ];
36 | ary[ idx + 8 ] = m[ 8 ];
37 | ary[ idx + 9 ] = m[ 9 ];
38 | ary[ idx + 10 ] = m[ 10 ];
39 | ary[ idx + 11 ] = m[ 11 ];
40 | ary[ idx + 12 ] = m[ 12 ];
41 | ary[ idx + 13 ] = m[ 13 ];
42 | ary[ idx + 14 ] = m[ 14 ];
43 | ary[ idx + 15 ] = m[ 15 ];
44 | return this;
45 | }
46 | }
47 |
48 | export default Mat4Util;
--------------------------------------------------------------------------------
/src/maths/Maths.ts:
--------------------------------------------------------------------------------
1 |
2 | class Maths{
3 |
4 | // Special Modulus that can take in Negative Number
5 | // and Loop Around as the result
6 | static mod( a: number, b: number ) : number{
7 | const v = a % b;
8 | return ( v < 0 )? b + v : v;
9 | }
10 |
11 | }
12 |
13 | export default Maths;
--------------------------------------------------------------------------------
/src/maths/QuatUtil.ts:
--------------------------------------------------------------------------------
1 | import { quat, vec3 } from 'gl-matrix'
2 |
3 | class QuatUtil{
4 |
5 | /** Used to get data from a flat buffer */
6 | static fromBuf( out: quat, ary : Array | Float32Array, idx: number ) : quat {
7 | out[ 0 ] = ary[ idx ];
8 | out[ 1 ] = ary[ idx + 1 ];
9 | out[ 2 ] = ary[ idx + 2 ];
10 | out[ 3 ] = ary[ idx + 3 ];
11 | return out;
12 | }
13 |
14 | /** Put data into a flat buffer */
15 | static toBuf( q: quat, ary : Array | Float32Array, idx: number ) : QuatUtil {
16 | ary[ idx ] = q[ 0 ];
17 | ary[ idx + 1 ] = q[ 1 ];
18 | ary[ idx + 2 ] = q[ 2 ];
19 | ary[ idx + 3 ] = q[ 3 ];
20 | return this;
21 | }
22 |
23 |
24 | static lenSqr( a: quat, b: quat ): number{
25 | return (a[ 0 ]-b[ 0 ]) ** 2 +
26 | (a[ 1 ]-b[ 1 ]) ** 2 +
27 | (a[ 2 ]-b[ 2 ]) ** 2 +
28 | (a[ 3 ]-b[ 3 ]) ** 2 ;
29 | }
30 |
31 | static isZero( q: quat ): boolean { return ( q[0] == 0 && q[1] == 0 && q[2] == 0 && q[3] == 0 ); }
32 |
33 | static negate( out: quat, q?: quat ): quat{
34 | if( !q ) q = out;
35 | out[ 0 ] = -q[ 0 ];
36 | out[ 1 ] = -q[ 1 ];
37 | out[ 2 ] = -q[ 2 ];
38 | out[ 3 ] = -q[ 3 ];
39 | return out;
40 | }
41 |
42 | /** Checks if on opposite hemisphere, if so, negate change quat */
43 | static dotNegate( out: quat, chg: quat, chk: quat ): quat{
44 | if( quat.dot( chg, chk ) < 0 ) this.negate( out, chg );
45 | return out;
46 | }
47 |
48 | /** PreMultiple an Axis Angle to this quaternions */
49 | static pmulAxisAngle( out:quat, axis: vec3, angle: number, q:quat ) : quat{
50 | const half = angle * .5,
51 | s = Math.sin( half ),
52 | ax = axis[0] * s, // A Quat based on Axis Angle
53 | ay = axis[1] * s,
54 | az = axis[2] * s,
55 | aw = Math.cos( half ),
56 |
57 | bx = q[0], // B of mul
58 | by = q[1],
59 | bz = q[2],
60 | bw = q[3];
61 | // Quat.mul( a, b );
62 | out[ 0 ] = ax * bw + aw * bx + ay * bz - az * by;
63 | out[ 1 ] = ay * bw + aw * by + az * bx - ax * bz;
64 | out[ 2 ] = az * bw + aw * bz + ax * by - ay * bx;
65 | out[ 3 ] = aw * bw - ax * bx - ay * by - az * bz;
66 | return out;
67 | }
68 |
69 | /** Inverts the quaternion passed in, then pre multiplies to this quaternion. */
70 | static pmulInvert( out: quat, q: quat, qinv: quat ): quat{
71 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
72 | // q.invert()
73 | let ax = qinv[ 0 ],
74 | ay = qinv[ 1 ],
75 | az = qinv[ 2 ],
76 | aw = qinv[ 3 ];
77 |
78 | const dot = ax*ax + ay*ay + az*az + aw*aw;
79 |
80 | if( dot == 0 ){
81 | ax = ay = az = aw = 0;
82 | }else{
83 | const dot_inv = 1.0 / dot;
84 | ax = -ax * dot_inv;
85 | ay = -ay * dot_inv;
86 | az = -az * dot_inv;
87 | aw = aw * dot_inv;
88 | }
89 |
90 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
91 | // Quat.mul( a, b );
92 | const bx = q[ 0 ],
93 | by = q[ 1 ],
94 | bz = q[ 2 ],
95 | bw = q[ 3 ];
96 | out[ 0 ] = ax * bw + aw * bx + ay * bz - az * by;
97 | out[ 1 ] = ay * bw + aw * by + az * bx - ax * bz;
98 | out[ 2 ] = az * bw + aw * bz + ax * by - ay * bx;
99 | out[ 3 ] = aw * bw - ax * bx - ay * by - az * bz;
100 |
101 | return out;
102 | }
103 |
104 | static nblend( out: quat, a: quat, b: quat, t: number ) : quat{
105 | // https://physicsforgames.blogspot.com/2010/02/quaternions.html
106 | const a_x = a[ 0 ], // Quaternion From
107 | a_y = a[ 1 ],
108 | a_z = a[ 2 ],
109 | a_w = a[ 3 ],
110 | b_x = b[ 0 ], // Quaternion To
111 | b_y = b[ 1 ],
112 | b_z = b[ 2 ],
113 | b_w = b[ +3 ],
114 | dot = a_x*b_x + a_y*b_y + a_z*b_z + a_w*b_w,
115 | ti = 1 - t;
116 | let s = 1;
117 |
118 | // if Rotations with a dot less then 0 causes artifacts when lerping,
119 | // Can fix this by switching the sign of the To Quaternion.
120 | if( dot < 0 ) s = -1;
121 |
122 | out[ 0 ] = ti * a_x + t * b_x * s;
123 | out[ 1 ] = ti * a_y + t * b_y * s;
124 | out[ 2 ] = ti * a_z + t * b_z * s;
125 | out[ 3 ] = ti * a_w + t * b_w * s;
126 |
127 | return quat.normalize( out, out );
128 | }
129 | }
130 |
131 | export default QuatUtil;
--------------------------------------------------------------------------------
/src/maths/Vec3Util.ts:
--------------------------------------------------------------------------------
1 | import type { vec3 } from 'gl-matrix'
2 |
3 | export type TVec3Struct = { x: number, y: number, z: number }; // Handle Data form ThreeJS
4 |
5 | class Vec3Util{
6 |
7 | static len( a: vec3, b: vec3 ): number{
8 | return Math.sqrt(
9 | (a[ 0 ]-b[ 0 ]) ** 2 +
10 | (a[ 1 ]-b[ 1 ]) ** 2 +
11 | (a[ 2 ]-b[ 2 ]) ** 2
12 | );
13 | }
14 |
15 | static lenSqr( a: vec3, b: vec3 ): number{
16 | return (a[ 0 ]-b[ 0 ]) ** 2 +
17 | (a[ 1 ]-b[ 1 ]) ** 2 +
18 | (a[ 2 ]-b[ 2 ]) ** 2 ;
19 | }
20 |
21 | static isZero( v: vec3 ): boolean { return ( v[0] == 0 && v[1] == 0 && v[2] == 0 ); }
22 |
23 | /** When values are very small, like less then 0.000001, just make it zero */
24 | static nearZero( out: vec3, v: vec3 ) : vec3{
25 | out[ 0 ] = ( Math.abs( v[ 0 ] ) <= 1e-6 )? 0 : v[ 0 ];
26 | out[ 1 ] = ( Math.abs( v[ 1 ] ) <= 1e-6 )? 0 : v[ 1 ];
27 | out[ 2 ] = ( Math.abs( v[ 2 ] ) <= 1e-6 )? 0 : v[ 2 ];
28 | return out;
29 | }
30 |
31 | //#region LOADING / CONVERSION
32 | /** Used to get data from a flat buffer */
33 | static fromBuf( out: vec3, ary : Array | Float32Array, idx: number ) : vec3 {
34 | out[ 0 ] = ary[ idx ];
35 | out[ 1 ] = ary[ idx + 1 ];
36 | out[ 2 ] = ary[ idx + 2 ];
37 | return out;
38 | }
39 |
40 | /** Put data into a flat buffer */
41 | static toBuf( v: vec3, ary : Array | Float32Array, idx: number ) : Vec3Util {
42 | ary[ idx ] = v[ 0 ];
43 | ary[ idx + 1 ] = v[ 1 ];
44 | ary[ idx + 2 ] = v[ 2 ];
45 | return this;
46 | }
47 |
48 | static toStruct( v: vec3, o ?: TVec3Struct ): TVec3Struct{
49 | o ??= { x:0, y:0, z:0 };
50 | o.x = v[ 0 ];
51 | o.y = v[ 1 ];
52 | o.z = v[ 2 ];
53 | return o;
54 | }
55 |
56 | static fromStruct( v: vec3, o: TVec3Struct ): vec3{
57 | v[ 0 ] = o.x;
58 | v[ 1 ] = o.y;
59 | v[ 2 ] = o.z;
60 | return v;
61 | }
62 |
63 | static toArray( v: vec3 ): number[]{ return [ v[0], v[1], v[2] ]; }
64 | //#endregion
65 |
66 | }
67 |
68 | export default Vec3Util;
--------------------------------------------------------------------------------
/src/maths/Vec4Util.ts:
--------------------------------------------------------------------------------
1 | import type { vec4 } from 'gl-matrix'
2 |
3 | class Vec4Util{
4 |
5 | /** Used to get data from a flat buffer */
6 | static fromBuf( out: vec4, ary : Array | Float32Array, idx: number ) : vec4 {
7 | out[ 0 ] = ary[ idx ];
8 | out[ 1 ] = ary[ idx + 1 ];
9 | out[ 2 ] = ary[ idx + 2 ];
10 | return out;
11 | }
12 |
13 | /** Put data into a flat buffer */
14 | static toBuf( v: vec4, ary : Array | Float32Array, idx: number ) : Vec4Util {
15 | ary[ idx ] = v[ 0 ];
16 | ary[ idx + 1 ] = v[ 1 ];
17 | ary[ idx + 2 ] = v[ 2 ];
18 | return this;
19 | }
20 | }
21 |
22 | export default Vec4Util;
--------------------------------------------------------------------------------
/src/maths/index.ts:
--------------------------------------------------------------------------------
1 | import Maths from './Maths';
2 | import Transform from './Transform';
3 |
4 | import DualQuatUtil from './DualQuatUtil';
5 | import Mat4Util from './Mat4Util';
6 | import QuatUtil from './QuatUtil';
7 | import Vec3Util from './Vec3Util';
8 | import Vec4Util from './Vec4Util';
9 |
10 | export {
11 | Maths, Transform,
12 | DualQuatUtil, Mat4Util, QuatUtil, Vec3Util, Vec4Util,
13 | };
--------------------------------------------------------------------------------
/src/ossos.ts:
--------------------------------------------------------------------------------
1 | //#region IMPORTS
2 | import { Animator, Clip, Retarget } from './animation/index';
3 |
4 | import { Armature, Bone, Pose, SkinMTX, SkinDQ, SkinDQT, SkinRTS } from './armature/index';
5 | import type { ISkin, TTextureInfo } from './armature/index';
6 |
7 | import BoneSpring from './bonespring/index';
8 | import type { ISpringType } from './bonespring/index';
9 |
10 | import {
11 | Maths, Transform,
12 | DualQuatUtil, Mat4Util, QuatUtil, Vec3Util, Vec4Util,
13 | } from './maths/index';
14 |
15 | import Gltf2, { Accessor } from './parsers/gltf2/index';
16 |
17 | import {
18 | IKData, BipedIKPose, IKRig, BipedRig, IKChain, IKLink,
19 | SwingTwistEndsSolver, SwingTwistSolver, LimbSolver, HipSolver,
20 | } from './ikrig/index';
21 |
22 | //#endregion
23 |
24 | //#region EXPORTS
25 | export {
26 | Animator, Clip, Retarget,
27 | Armature, Bone, Pose, SkinMTX, SkinDQ, SkinDQT, SkinRTS,
28 | BoneSpring,
29 | Maths, Transform, DualQuatUtil, Mat4Util, QuatUtil, Vec3Util, Vec4Util,
30 | Gltf2, Accessor,
31 | IKData, BipedIKPose, IKRig, BipedRig, IKChain, IKLink,
32 | SwingTwistEndsSolver, SwingTwistSolver, LimbSolver, HipSolver,
33 | };
34 |
35 | export type{
36 | ISkin, TTextureInfo,
37 | ISpringType,
38 | };
39 | //#endregion
--------------------------------------------------------------------------------
/src/parsers/bvh/Animation.ts:
--------------------------------------------------------------------------------
1 | import { vec3 } from "gl-matrix";
2 |
3 | type Transform = typeof ETransform[ keyof typeof ETransform ];
4 | export const ETransform = {
5 | Rot : 0,
6 | Pos : 1,
7 | } as const;
8 |
9 | export class Track{
10 | //#region ENUMS
11 | static Transform = ETransform;
12 | //#endregion
13 |
14 | //#region MAIN
15 | transform : Transform;
16 | jointIndex : number;
17 | keyframes !: Float32Array;
18 |
19 | constructor( jointIndex:number = 0, transformType:Transform = ETransform.Pos ){
20 | this.transform = transformType;
21 | this.jointIndex = jointIndex;
22 | }
23 | //#endregion
24 |
25 | // #region METHODS
26 | buildKeyframeData( frameCount:number ): this{
27 | const comSize = ( this.transform == ETransform.Pos )? 3 : 4;
28 | this.keyframes = new Float32Array( frameCount * comSize );
29 | return this;
30 | }
31 |
32 | setFrameData( frameIndex: number, data: Array ): void{
33 | const idx = ( this.transform == ETransform.Pos )? 3 * frameIndex : 4 * frameIndex;
34 | this.keyframes.set( data, idx );
35 | }
36 | // #endregion
37 |
38 | // #region STATIC
39 | static newPosition( jointIndex:number ): Track{ return new Track( jointIndex, ETransform.Pos ); }
40 | static newRotation( jointIndex:number ): Track{ return new Track( jointIndex, ETransform.Rot ); }
41 | // #endregion
42 | }
43 |
44 | export class Animation{
45 | name : string = '';
46 | duration : number = 0;
47 | timestamp : Float32Array;
48 | joints : Array< { rot:Track, pos:Track } > = [];
49 | frameCount : number;
50 |
51 | constructor( frameCount:number ){
52 | this.timestamp = new Float32Array( frameCount );
53 | this.frameCount = frameCount;
54 | }
55 |
56 | addJoint(): number{
57 | const idx = this.joints.length;
58 | const tRot = Track.newRotation( idx ).buildKeyframeData( this.frameCount );
59 | const tPos = Track.newPosition( idx ).buildKeyframeData( this.frameCount );
60 | this.joints.push({ rot: tRot, pos: tPos });
61 | return idx;
62 | }
63 | }
--------------------------------------------------------------------------------
/src/parsers/bvh/Skin.ts:
--------------------------------------------------------------------------------
1 | export class Skin{
2 | joints : Array = []; // Collection of Joints
3 | }
4 |
5 | export class SkinJoint{
6 | name : string | null = null; // Name of Joint
7 | index : number = -1; // Joint Index
8 | parentIndex : number = -1 ; // Parent Joint Index, Null if its a Root Joint
9 |
10 | position : number[] | null = null; // Local Space Position
11 | rotation : number[] | null = null; // Local Space Rotation
12 | }
--------------------------------------------------------------------------------
/src/parsers/gltf2/Accessor.ts:
--------------------------------------------------------------------------------
1 | import { TTypeArray } from "./types";
2 | import { ComponentTypeMap, ComponentVarMap } from "./structs";
3 |
4 | type TBufferView = { byteOffset ?: number };
5 | type TAccessor = {
6 | componentType : number,
7 | type : number,
8 | count : number,
9 | byteOffset ?: number,
10 | min ?: Array,
11 | max ?: Array,
12 | };
13 |
14 | class Accessor{
15 | componentLen = 0;
16 | elementCnt = 0;
17 | byteOffset = 0;
18 | byteSize = 0;
19 | boundMin : Array | null = null;
20 | boundMax : Array | null = null;
21 | type : string | null = null;
22 | data : TTypeArray | null = null;
23 |
24 | constructor( accessor: TAccessor, bufView: TBufferView, bin: ArrayBuffer ){
25 | const [ compByte, // Type Byte Size
26 | compType,
27 | typeName ] = ComponentTypeMap[ accessor.componentType ]; // Ref to TypeArray
28 |
29 | if( !compType ){ console.error( "Unknown Component Type for Accessor", accessor.componentType ); return; }
30 |
31 | this.componentLen = ComponentVarMap[ accessor.type ]; // How many Components in Value
32 | this.elementCnt = accessor.count; // Like, How many Vector3s exist?
33 | this.byteOffset = ( accessor.byteOffset || 0 ) + ( bufView.byteOffset || 0 );
34 | this.byteSize = this.elementCnt * this.componentLen * compByte;
35 | this.boundMin = ( accessor.min )? accessor.min.slice( 0 ) : null;
36 | this.boundMax = ( accessor.max )? accessor.max.slice( 0 ) : null;
37 | this.type = typeName; //accessor.type;
38 |
39 | if( bin ){
40 | const size = this.elementCnt * this.componentLen;
41 | this.data = new compType( bin, this.byteOffset, size );
42 | }
43 | }
44 | }
45 |
46 | export default Accessor;
--------------------------------------------------------------------------------
/src/parsers/gltf2/Animation.ts:
--------------------------------------------------------------------------------
1 | import type Accessor from './Accessor';
2 |
3 | type Transform = typeof ETransform[ keyof typeof ETransform ];
4 | export const ETransform = {
5 | Rot : 0,
6 | Pos : 1,
7 | Scl : 2,
8 | } as const;
9 |
10 | type Lerp = typeof ELerp[ keyof typeof ELerp ];
11 | export const ELerp = {
12 | Step : 0,
13 | Linear : 1,
14 | Cubic : 2,
15 | } as const;
16 |
17 | export class Track{
18 | //#region ENUMS
19 | static Transform = ETransform;
20 | static Lerp = ELerp;
21 | //#endregion
22 |
23 | //#region MAIN
24 | transform : Transform = ETransform.Pos;
25 | interpolation : Lerp = ELerp.Step;
26 | jointIndex : number = 0;
27 | timeStampIndex : number = 0;
28 | keyframes !: Accessor;
29 |
30 | static fromGltf( jointIdx:number, target:string, inter:string ): Track{
31 | const t = new Track();
32 | t.jointIndex = jointIdx;
33 |
34 | switch( target ){
35 | case 'translation' : t.transform = ETransform.Pos; break;
36 | case 'rotation' : t.transform = ETransform.Rot; break;
37 | case 'scale' : t.transform = ETransform.Scl; break;
38 | }
39 |
40 | switch( inter ){
41 | case 'LINEAR' : t.interpolation = ELerp.Linear; break;
42 | case 'STEP' : t.interpolation = ELerp.Step; break;
43 | case 'CUBICSPLINE' : t.interpolation = ELerp.Cubic; break;
44 | }
45 |
46 | return t;
47 | }
48 | //#endregion
49 | }
50 |
51 | export class Animation{
52 | name : string = '';
53 | timestamps : Array< Accessor > = [];
54 | tracks : Array< Track > = [];
55 |
56 | constructor( name ?: string ){
57 | if( name ) this.name = name;
58 | }
59 | }
--------------------------------------------------------------------------------
/src/parsers/gltf2/Glb.ts:
--------------------------------------------------------------------------------
1 | // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#glb-file-format-specification-general
2 |
3 | const GLB_MAGIC = 0x46546C67; // Simple number test to see if its a GLB
4 | const GLB_JSON = 0x4E4F534A; // Chunk Type for JSON
5 | const GLB_BIN = 0x004E4942; // Chunk Type for Binary
6 | const GLB_VER = 2; // Version Number
7 | const GLB_MAGIC_BIDX = 0; // Byte Index for magic Uint32 magic value
8 | const GLB_VERSION_BIDX = 4; // Byte Index for version Uint32 Value
9 | const GLB_JSON_TYPE_BIDX = 16; // Byte Index for Chunk0 Type
10 | const GLB_JSON_LEN_BIDX = 12; // Byte Index for Chunk0 ByteLength ( Start of Header )
11 | const GLB_JSON_BIDX = 20; // Byte Index for the start of Chunk0
12 |
13 | async function parseGLB( res: Response ): Promise<[JSON, ArrayBuffer] | null> {
14 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15 | const arybuf = await res.arrayBuffer();
16 | const dv = new DataView( arybuf );
17 |
18 | if( dv.getUint32( GLB_MAGIC_BIDX, true ) != GLB_MAGIC ){ console.error( 'GLB magic number does not match.' ); return null; }
19 | if( dv.getUint32( GLB_VERSION_BIDX, true ) != GLB_VER ){ console.error( 'Can only accept GLB of version 2.' ); return null; }
20 | if( dv.getUint32( GLB_JSON_TYPE_BIDX, true ) != GLB_JSON ){ console.error( 'GLB Chunk 0 is not the type: JSON '); return null; }
21 |
22 | const json_len = dv.getUint32( GLB_JSON_LEN_BIDX, true ); // Byte Length of Chunk0-JSON
23 | const chk1_bidx = GLB_JSON_BIDX + json_len; // Byte Index for Chunk1's Header ( Also Chunk1's ByteLength )
24 |
25 | // TODO: This isn't actually required, can have GLTF without Binary Chunk
26 | if( dv.getUint32( chk1_bidx + 4, true ) != GLB_BIN ){ console.error( 'GLB Chunk 1 is not the type: BIN ' );return null; }
27 |
28 | const bin_len = dv.getUint32( chk1_bidx, true ); // Get Length of Binary Chunk
29 | const bin_idx = chk1_bidx + 8; // Skip the 2 INT header values to get the byte index start of BIN
30 |
31 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 | // PARSE JSON
33 | const txt_decoder = new TextDecoder( 'utf8' ); // JSON is encoded with uf8
34 | const json_bytes = new Uint8Array( arybuf, GLB_JSON_BIDX, json_len ); // Slice the Byte Array to just have the JSON Chunk
35 | const json_text = txt_decoder.decode( json_bytes ); // Decode Byte Array Slice
36 | const json = JSON.parse( json_text ); // Parse Text to JSON Objects
37 |
38 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | // PARSE BIN - TODO, Not efficent to slice the array buffer
40 | // Ideally better to save start index as a starting offset
41 | // & fix the parser to tack that value onto every accessor call
42 |
43 | const bin = arybuf.slice( bin_idx );
44 | if( bin.byteLength != bin_len ){ console.error( 'GLB Bin length does not match value in header.' ); return null; }
45 |
46 | return [ json, bin ];
47 | }
48 |
49 | export default parseGLB;
--------------------------------------------------------------------------------
/src/parsers/gltf2/Mesh.ts:
--------------------------------------------------------------------------------
1 | import Accessor from "./Accessor";
2 |
3 | export class Mesh{
4 | index : number | null = null; // Index in Mesh Collection
5 | name : string | null = null; // Mesh Name
6 | primitives : Array = []; // Mesh is made up of more then one Primative
7 |
8 | position : number[] | null = null; // Node's Position
9 | rotation : number[] | null = null; // Node's Rotation
10 | scale : number[] | null = null; // Node's Scale
11 | }
12 |
13 | export class Primitive{
14 | materialName : string | null = null;
15 | materialIdx : number | null = null;
16 |
17 | indices : Accessor | null = null;
18 | position : Accessor | null = null;
19 | normal : Accessor | null = null;
20 | tangent : Accessor | null = null;
21 | texcoord_0 : Accessor | null = null;
22 | texcoord_1 : Accessor | null = null;
23 | color_0 : Accessor | null = null;
24 | joints_0 : Accessor | null = null;
25 | weights_0 : Accessor | null = null;
26 | }
27 |
--------------------------------------------------------------------------------
/src/parsers/gltf2/Pose.ts:
--------------------------------------------------------------------------------
1 |
2 | class PoseJoint{
3 | //#region MAIN
4 | index : number;
5 | rot ?: number[];
6 | pos ?: number[];
7 | scl ?: number[];
8 |
9 | constructor( idx:number, rot ?: number[], pos ?: number[], scl ?: number[] ){
10 | this.index = idx;
11 | this.rot = rot;
12 | this.pos = pos;
13 | this.scl = scl;
14 | }
15 | //#endregion
16 | }
17 |
18 | class Pose{
19 | name : string = '';
20 | joints : Array< PoseJoint > = [];
21 |
22 | constructor( name ?: string ){
23 | if( name ) this.name = name;
24 | }
25 |
26 | add( idx:number, rot ?: number[], pos ?: number[], scl ?: number[] ): void{
27 | this.joints.push( new PoseJoint( idx, rot, pos, scl ) );
28 | }
29 | }
30 |
31 | export { Pose, PoseJoint };
--------------------------------------------------------------------------------
/src/parsers/gltf2/Skin.ts:
--------------------------------------------------------------------------------
1 | export class Skin{
2 | index : number | null = null; // Index in Mesh Collection
3 | name : string | null = null; // Skin Name
4 | joints : Array = []; // Collection of Joints
5 |
6 | // Sometimes Skin Objects will have their own transform in nodes
7 | // Tends to come from FBX to GLTF conversion in blender.
8 | position : number[] | null = null; // Local Space Position
9 | rotation : number[] | null = null; // Local Space Rotation
10 | scale : number[] | null = null; // Local Space Scale
11 | }
12 |
13 | export class SkinJoint{
14 | name : string | null = null; // Name of Joint
15 | index : number | null = null; // Joint Index
16 | parentIndex : number | null = null; // Parent Joint Index, Null if its a Root Joint
17 |
18 | bindMatrix : number[] | null = null; // Inverted WorldSpace Transform
19 | position : number[] | null = null; // Local Space Position
20 | rotation : number[] | null = null; // Local Space Rotation
21 | scale : number[] | null = null; // Local Space Scale
22 | }
--------------------------------------------------------------------------------
/src/parsers/gltf2/Texture.ts:
--------------------------------------------------------------------------------
1 |
2 | export class Texture{
3 | index : number | null = null; // Index in Texture Collection
4 | name : string | null = null; // Texture Name
5 | mime : string | null = null; // Texture Mime
6 | blob : Blob | null = null; // Image Binary
7 | }
--------------------------------------------------------------------------------
/src/parsers/gltf2/structs.ts:
--------------------------------------------------------------------------------
1 | import { TTypeArrayCon } from "./types";
2 |
3 | // ByteSize : TypeArray : JS Type name, Gltf Type name
4 | export const ComponentTypeMap : Record= {
5 | 5120: [ 1, Int8Array, "int8", "BYTE" ],
6 | 5121: [ 1, Uint8Array, "uint8", "UNSIGNED_BYTE" ],
7 | 5122: [ 2, Int16Array, "int16", "SHORT" ],
8 | 5123: [ 2, Uint16Array, "uint16", "UNSIGNED_SHORT" ],
9 | 5125: [ 4, Uint32Array, "uint32", "UNSIGNED_INT" ],
10 | 5126: [ 4, Float32Array, "float", "FLOAT" ],
11 | };
12 |
13 | export const ComponentVarMap : Record = { // Component Length of Each Var Type
14 | SCALAR : 1,
15 | VEC2 : 2,
16 | VEC3 : 3,
17 | VEC4 : 4,
18 | MAT2 : 4,
19 | MAT3 : 9,
20 | MAT4 : 16,
21 | };
--------------------------------------------------------------------------------
/src/parsers/gltf2/types.ts:
--------------------------------------------------------------------------------
1 | export type TTypeArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Uint32Array | Float32Array;
2 | export type TTypeArrayCon = Int8ArrayConstructor | Uint8ArrayConstructor | Int16ArrayConstructor |
3 | Uint16ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor;
4 |
5 |
6 | export type JSONValue =
7 | | string
8 | | number
9 | | boolean
10 | | { [x: string]: JSONValue }
11 | | Array;
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions" : {
3 | "target" : "ESNext",
4 | "lib" : [ "DOM", "DOM.Iterable", "ESNext" ],
5 | "types" : [ "vite/client" ],
6 | "allowJs" : false,
7 | "skipLibCheck" : false,
8 | "esModuleInterop" : false,
9 | "allowSyntheticDefaultImports" : true,
10 | "strict" : true,
11 | "forceConsistentCasingInFileNames" : true,
12 | "module" : "ESNext",
13 | "moduleResolution" : "Node",
14 | "resolveJsonModule" : true,
15 | "isolatedModules" : true,
16 | "noEmit" : true
17 | },
18 |
19 | "include" : [ "./src" ]
20 | }
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "fs";
3 | import { directoryPlugin } from 'vite-plugin-list-directory-contents';
4 |
5 | const ignorePaths = [ ".git", "node_modules", "dist", "site" ];
6 |
7 | function getHtmlPaths( dirPath = __dirname, htmlPaths = {} ){
8 | const files = fs.readdirSync(dirPath);
9 |
10 | for( const file of files ){
11 | if( ignorePaths.includes( file ) ) continue;
12 |
13 | const absPath = path.join( dirPath, file );
14 |
15 | if( fs.statSync(absPath).isDirectory() ){
16 | htmlPaths = getHtmlPaths( absPath, htmlPaths );
17 |
18 | }else if( path.extname(file) === ".html" ){
19 | const relPath = path.relative( __dirname, absPath );
20 | htmlPaths[relPath] = absPath;
21 | }
22 | }
23 |
24 | return htmlPaths;
25 | }
26 |
27 | export default ( { command, mode } ) => {
28 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29 | if( mode === "site" || command === "serve" ){
30 |
31 | const repo = process.env.GITHUB_REPOSITORY;
32 | const base = ( repo )? `/${repo.split("/")[1]}/` : '/';
33 |
34 | return {
35 | base,
36 |
37 | build : {
38 | outDir : path.resolve( __dirname, "site" ),
39 | minify : false,
40 | rollupOptions : { input: getHtmlPaths() },
41 | },
42 |
43 | publicDir : path.join( __dirname, "examples", "public" ),
44 |
45 | plugins : [
46 | directoryPlugin( {
47 | baseDir : __dirname,
48 | filterList : [ 'node_modules', '.git', '.github', '_store', '_images', 'dist', 'src', '.*' ],
49 | })
50 | ],
51 |
52 | server : {
53 | port : 3009,
54 | open : '/',
55 | },
56 | };
57 |
58 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59 | } else {
60 | return {
61 | build: {
62 | minify : false,
63 |
64 | lib : {
65 | entry : path.resolve( __dirname, "src/ossos.ts" ),
66 | name : "ossos",
67 | formats : [ "es", "cjs" ],
68 | },
69 |
70 | rollupOptions : {
71 | external : [ "three", /^three\// ],
72 | }
73 | },
74 | };
75 | }
76 | };
--------------------------------------------------------------------------------