├── .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 |
  1. Rig Animation
  2. 12 |
  3. Procedural Tail Animation
  4. 13 |
  5. Ready Player Me
  6. 14 |
  7. Animating Arm
  8. 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 | }; --------------------------------------------------------------------------------