Skip to content

Commit 12e3b32

Browse files
author
Avaer Kazmer
committed
Add avatar proto implementation source files
1 parent 7c2c837 commit 12e3b32

30 files changed

+4225
-0
lines changed

proto/Armature.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Armature.js -- base skeleton abstraction across a well-known joint topology (independent of mesh)
2+
3+
import { _decoupledSkeletonClone } from './armature.utils.js';
4+
import remapJointNames from './remapJointNames.js';
5+
import { NamedJointWrappers } from './NamedJointWrappers.js';
6+
import { AutoIKChain, walkBoneChain } from './AutoIKChain.js';
7+
import SkeletonMetrics from './SkeletonMetrics.js';
8+
9+
class Armature {
10+
static get version() { return '0.0.0a'; }
11+
constructor(skeleton, options) {
12+
this.options = options;
13+
this.skeleton = skeleton;
14+
this.originalSkeleton = _decoupledSkeletonClone(skeleton, new THREE.Group());
15+
this.bones = remapJointNames(this.skeleton, options.remapper);
16+
this.rootBone = this.bones.Hips;
17+
this.armatureObject = this.rootBone.parent;
18+
this.bones.Armature = this.bones.Armature || this.armatureObject;
19+
//this.bones.Armature.rotation.order = 'YXZ';
20+
this.bones.Hips.rotation.order = 'YXZ';
21+
this.bones.Head.rotation.order = 'YXZ';
22+
23+
//this.armatureBone =
24+
this.boneNames = Object.keys(this.bones);
25+
this.metrics = new SkeletonMetrics(skeleton);
26+
this.backup = _decoupledSkeletonClone(skeleton, new THREE.Group());
27+
this.backup.metrics = new SkeletonMetrics(this.backup);
28+
29+
this.virtual = new THREE.Object3D();
30+
this.virtual.rotation.order = 'YXZ';
31+
32+
const idNames = this.boneNames.reduce((out, x) => {
33+
out[x.replace(/^(.)(.*)$/, (_, ch, rest) => (ch.toLowerCase()+rest))] = x;
34+
return out;
35+
}, {});
36+
Object.assign(this, new NamedJointWrappers(skeleton, idNames));
37+
}
38+
39+
walk(a, b, silent = false) {
40+
var bc = walkBoneChain(this.get(a), this.get(b));
41+
if (!silent && !bc.valid) {
42+
debugger;
43+
throw new Error('bad chain: '+[a,b].join('=>'));
44+
}
45+
return bc;
46+
}
47+
48+
// experimental support for "tearing off" subskeleton chains
49+
virtualSegment(from, to) {
50+
var container = {
51+
name: 'virtualArmatureSegment',
52+
id: -1,
53+
uuid: -1,
54+
__proto__: new THREE.Object3D()
55+
};
56+
//var solo = _decoupledSkeletonClone(this.skeleton, new THREE.Group());
57+
var bc = this.walk(this.get(from), this.get(to));
58+
if (bc.head.parent) {
59+
bc.head.parent.getWorldPosition(container.position);
60+
bc.head.parent.getWorldQuaternion(container.quaternion);
61+
bc.head.parent.getWorldScale(container.scale);
62+
container.id = bc.head.parent.id;
63+
container.uuid = bc.head.parent.uuid;
64+
}
65+
var keep = bc.lineage.reduce(function(out, x){ out[x.uuid] = true; return out; }, {});
66+
//console.info('virtualSegment', keep, bc.lineage);
67+
var proxies = {};
68+
var byname = {};
69+
var vbones = this.skeleton.bones
70+
.map((x,i)=>{ return byname[x.name] = proxies[x.uuid] = {
71+
parent: keep[x.parent && x.parent.uuid] ? x.parent : container,
72+
$boneInverse: this.skeleton.boneInverses[i],
73+
__proto__: x,
74+
}})
75+
.filter((x)=>keep[x.uuid])
76+
.map((x) => {
77+
x.parent = proxies[x.parent && x.parent.uuid] || container;
78+
x.children = x.children.filter((c) => keep[c.uuid]).map((c)=>proxies[c.uuid]);
79+
if (x.parent === container) container.children.push(x);
80+
return x;
81+
})
82+
var vboneInverses = vbones.map((b)=>b.$boneInverse);
83+
//console.info('//virtualSegment', vbones.map((x)=>[x.name,x.children.length].join('#')));
84+
85+
return Object.assign(new THREE.Skeleton(vbones, vboneInverses), {
86+
toString: function() { return `[PartialSkeleton head=${this.head.name} tail=${this.tail.name} bones=${this.bones.length}]`; },
87+
container: container,
88+
head: proxies[bc.head.uuid],
89+
tail: proxies[bc.tail.uuid],
90+
lineage: bc.lineage.map((x)=>proxies[x.uuid]),
91+
});
92+
}
93+
94+
// helper methods
95+
get(name) {
96+
if (typeof name === 'string') return this.bones[name];
97+
if (name && name.isBone) return name;
98+
return null;
99+
}
100+
inv(name) {
101+
var i = this.skeleton.bones.indexOf(this.get(name));
102+
if (~i) { return this.skeleton.boneInverses[i]; }
103+
return null;
104+
}
105+
setInv(name, mat) {
106+
var i = this.skeleton.bones.indexOf(this.get(name));
107+
if (!~i) return null;
108+
this.skeleton.boneInverses[i].copy(mat);
109+
}
110+
setUnInv(name, mat) {
111+
var i = this.skeleton.bones.indexOf(this.get(name));
112+
if (!~i) return null;
113+
this.skeleton.boneInverses[i].copy(new THREE.Matrix4().getInverse(mat));
114+
}
115+
mat(name) {
116+
var i = this.skeleton.bones.indexOf(this.get(name));
117+
if (!~i) return null;
118+
return new THREE.Matrix4().copy({elements:this.skeleton.boneMatrices.slice(i*16, i*16+16)});
119+
}
120+
img(name) {
121+
var i = this.skeleton.bones.indexOf(this.get(name));
122+
if (!~i) return null;
123+
return new THREE.Matrix4().copy({elements:this.skeleton.boneTexture.image.data.slice(i*16, i*16+16)});
124+
}
125+
setMat(name, mat) {
126+
var i = this.skeleton.bones.indexOf(this.get(name));
127+
if (!~i) return null;
128+
for (var j=0; j < 16; j++) this.skeleton.boneMatrices[i*16+j] = mat.elements[j];
129+
}
130+
setImg(name, mat) {
131+
var i = this.skeleton.bones.indexOf(this.get(name));
132+
if (!~i) return null;
133+
for (var j=0; j < 16; j++) this.skeleton.boneTexture.image.data[i*16+j] = mat.elements[j];
134+
}
135+
getLocalRotation(name) {
136+
var bone = this.get(name);
137+
var orientation = bone.getWorldQuaternion(new THREE.Quaternion());
138+
var parentOrientation = bone.parent.getWorldQuaternion(new THREE.Quaternion());
139+
return parentOrientation.inverse().multiply(orientation);
140+
}
141+
getBindPose(name) {
142+
return this.backup[name];
143+
}
144+
};
145+
146+
export default Armature;
147+
export { Armature };
148+
try { Object.assign(self, { Armature }); } catch(e) {}

proto/AutoIKChain.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// ik.AutoIKChain.js -- extended THREE.IK class supporting automated lineage
2+
3+
import { IK, IKChain, IKJoint } from './ephemeral-three-ik.js';
4+
import { QuaternionIKChain, QuaternionIKJoint } from './QuaternionIK.js';
5+
6+
class AutoIKChain extends IK {
7+
static get version() { return '0.0.0'; }
8+
constructor(lineage, target, options) {
9+
super();
10+
this.options = options;
11+
if (!lineage.length) {
12+
throw new Error('invalid lineage: ' + [lineage, target, options]);
13+
}
14+
var constraints = options.constraints || {};
15+
Object.assign(this, { lineage, options, target}, {
16+
joints: []
17+
});
18+
if (!this.target) {
19+
throw new Error('no ik target specified: ' + lineage.map((x)=>x.name));
20+
}
21+
this.chain = this._traceChain(lineage, target, constraints);
22+
this.chain.joints.forEach((j) => this.joints.push(this.joints[j.bone.name] = j));
23+
this.add(this.chain);
24+
console.log("+ %c%s", 'color: green;', this+'');
25+
}
26+
toString() { return `[AutoIKChain ${this.chain.joints.map((x)=>x.bone.name).join(' > ')} << ${this.target.name}]`; }
27+
28+
preSolve(time, deltaTime) {
29+
this.solve();
30+
this.options.preSolve && this.options.preSolve(time, deltaTime);
31+
}
32+
tick(time, deltaTime) {
33+
this.options.tick && this.options.tick(time, deltaTime);
34+
}
35+
postSolve(time, deltaTime) {
36+
this.options.postSolve && this.options.postSolve(time, deltaTime);
37+
}
38+
preConstrain(joint, time, deltaTime) {
39+
return this.options.preConstrain && this.options.preConstrain(joint, time, deltaTime);
40+
}
41+
backwardConstrain(joint, time, deltaTime) {
42+
return this.options.backwardConstrain && this.options.backwardConstrain(joint, time, deltaTime);
43+
}
44+
forwardConstrain(joint, time, deltaTime) {
45+
return this.options.forwardConstrain && this.options.forwardConstrain(joint, time, deltaTime);
46+
}
47+
postConstrain(joint, time, deltaTime) {
48+
return this.options.postConstrain && this.options.postConstrain(joint, time, deltaTime);
49+
}
50+
51+
get head() { return this.lineage[0]; }
52+
get tail() { return this.lineage[this.lineage.length-1]; }
53+
54+
get ikJoints() {
55+
var ikJoints = [];
56+
this.joints.forEach((j) => ikJoints.push(ikJoints[j.bone.name]=j));
57+
return ikJoints;
58+
}
59+
60+
syncTail() {
61+
const { tail, target } = this;
62+
if (!target) throw new Error('!target '+ tail.name);
63+
tail.parent.updateMatrixWorld(true);
64+
tail.quaternion.copy(getRelativeRotation(target, tail.parent));//this._armatureRelative(target).quaternion);//));
65+
tail.updateMatrixWorld(true);
66+
}
67+
68+
_traceChain(bones, target, constraints) {
69+
var chain = new QuaternionIKChain({
70+
forwardConstrain: (j) => this.forwardConstrain(j),
71+
backwardConstrain: (j) => this.backwardConstrain(j),
72+
});
73+
bones = bones.slice(); // take shallow copy
74+
var parent;
75+
while (bones.length) {
76+
var bone = bones.shift();
77+
bone.rotation.order = 'XYZ';
78+
if (this.debug) console.info('chain.add', bone.name, bone.children.length, target);
79+
var joint = new QuaternionIKJoint(bone, {
80+
preConstrain: (j)=> this.preConstrain(j),
81+
postConstrain: (j)=> this.postConstrain(j),
82+
constraints: constraints[bone.name],
83+
});//, { constraints: constraints[this.armature.mapper(b.name)] || constraints });
84+
joint.chain = chain;
85+
joint.parent = parent;
86+
if (bones.length) {
87+
chain.add(joint);
88+
} else {
89+
bone.getWorldPosition(target.position);
90+
chain.add(joint, { target });
91+
}
92+
parent = joint;
93+
}
94+
if (this.debug) console.info('//chain', chain);
95+
return chain;
96+
}
97+
};
98+
99+
class IKArray extends Array {
100+
constructor(config) { super(); this.config = config; }
101+
preSolve(time, deltaTime) {
102+
this.filter((x)=>!x.disabled).forEach((x)=>x.tick(time, deltaTime));
103+
}
104+
tick(time, deltaTime) {
105+
if (this.config.backpropagate) {
106+
this.filter((x)=>!x.disabled).forEach((x)=>x.syncTail(time, deltaTime));
107+
}
108+
}
109+
postSolve(time, deltaTime) {}
110+
};
111+
//this.iks = new IKArray(this.config);
112+
113+
function walkBoneChain(head, tail) {
114+
var lineage = [];
115+
var bone = tail;
116+
while (bone) {
117+
lineage.push(bone);
118+
if (bone === head) break;
119+
var name = bone.name;
120+
while (bone && bone.name === name && bone !== head) bone = bone.parent;
121+
}
122+
lineage.reverse();
123+
return {
124+
toString: function() { return "[BoneChain "+this.lineage.map((x)=>x.name).join(' > ')+"]"; },
125+
head: head,
126+
tail: tail,
127+
lineage: lineage,
128+
valid: head && tail && lineage[0].name === head.name && lineage[lineage.length-1].name === tail.name,
129+
};
130+
}
131+
132+
export default AutoIKChain;
133+
export { AutoIKChain, walkBoneChain };
134+
try { Object.assign(self, { AutoIKChain, walkBoneChain }); } catch(e) {}

proto/IKRiggedModel.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// wip exokit integration layer
2+
3+
import RiggedModel from './RiggedModel.js';
4+
import THREE from './ephemeral-three.js';
5+
6+
//TODO: TOREMOVE -- this attempts backwards-compatibility with existing branch's riggedmodeltest.html mods
7+
class LegacyRiggedModel extends RiggedModel {
8+
constructor(skeleton, options) {
9+
if (options && options.resource) Object.assign(options, options.resource);
10+
super(skeleton, options);
11+
this.backup.meta = this.backup.metrics;
12+
}
13+
fallback() {}
14+
rebind(group) {
15+
console[group === this.group ? 'warn' : 'error']('rig.rebind deprecated; pass { group } into ctor options instead.');
16+
}
17+
};
18+
try {
19+
const LEGACYMODE = /riggedmodeltest/.test(location.href);
20+
if (LEGACYMODE) Object.assign(self, { RiggedModel: LegacyRiggedModel });
21+
} catch(e) {}
22+
//////TOREMOVE
23+
24+
const VERSION = RiggedModel.version + '/0.0.0';
25+
class RiggedAvatarModel extends RiggedModel {
26+
static get version() { return VERSION }
27+
constructor(avatarModel, options) {
28+
console.time('RiggedAvatarModel:'+(options.url));
29+
super(avatarModel, Object.assign(options||{}, {
30+
group: options.group || new THREE.Group(),
31+
}));
32+
this.clock = new THREE.Clock();
33+
this.skeleton.pose();
34+
// FIXME: still having issues with THREE frumstum culling...
35+
this.group.traverse((x)=>x.frustumCulled=false);
36+
console.timeEnd('RiggedAvatarModel:'+(options.url));
37+
}
38+
setState(hmd, gamepads) {
39+
this.$lastState = { hmd: hmd, gamepads: gamepads };
40+
const targets = this.targets;
41+
var flip = quatFromDegrees([0,0,180]);
42+
targets.Head.position.fromArray(hmd.position);
43+
targets.Head.position.y *= (this.scale || 1.0);
44+
targets.Head.quaternion.fromArray(hmd.quaternion)
45+
targets.LeftHand.position.fromArray(gamepads[1].position)
46+
var q = targets.LeftHand.quaternion.clone();
47+
targets.LeftHand.quaternion.fromArray(gamepads[1].quaternion).multiply(flip).slerp(q, .5);
48+
targets.RightHand.position.fromArray(gamepads[0].position)
49+
var q = targets.RightHand.quaternion.clone();
50+
targets.RightHand.quaternion.fromArray(gamepads[0].quaternion).multiply(flip).slerp(q, .5);
51+
var h = targets.Head, l = targets.LeftHand, r = targets.RightHand;
52+
l.position.sub(h.position);
53+
l.position.multiplyScalar(this.armScale || .65);
54+
l.position.add(h.position);
55+
r.position.sub(h.position);
56+
r.position.multiplyScalar(this.armScale || .65);
57+
r.position.add(h.position);
58+
}
59+
applyRecording() {
60+
var rig = this;
61+
return TimPlayback.loadRecording({
62+
url: './avatars/assets/tracked-recording.json',
63+
//url: './data/recording-dance-avatar.json',
64+
config: { output: true, input: true },
65+
getBone: (name) => { return rig.getBone(name) },
66+
getTarget: (name) => { return rig.getIKTarget(name) },
67+
getScene: ()=> top.DEBUG.local('scene')
68+
}).then((r) => {
69+
r.replayer.isReplaying = true;
70+
rig.$recording = r;
71+
return r;
72+
}).catch((err)=>console.error('error loading recording: ', err));
73+
}
74+
update() {
75+
const timeDelta = this.clock.getDelta() * 1000;
76+
const time = this.clock.elapsedTime * 1000;
77+
if (this.$recording) {
78+
this.$recording.tick(time, timeDelta);
79+
} else {
80+
this.tick(time, timeDelta);
81+
}
82+
}
83+
};
84+
85+
export default RiggedAvatarModel;
86+
export { RiggedAvatarModel };
87+
try { Object.assign(self, { RiggedAvatarModel }); } catch(e) {}

0 commit comments

Comments
 (0)