File: /home/creaqbdc/public_html/wp-content/uploads/ac_assets/morph_slider/vendor/ogl/src/extras/Skin.js
import { Mesh } from '../core/Mesh.js';
import { Transform } from '../core/Transform.js';
import { Mat4 } from '../math/Mat4.js';
import { Texture } from '../core/Texture.js';
import { Animation } from './Animation.js';
const tempMat4 = new Mat4();
export class Skin extends Mesh {
constructor(gl, { rig, geometry, program, mode = gl.TRIANGLES } = {}) {
super(gl, { geometry, program, mode });
this.createBones(rig);
this.createBoneTexture();
this.animations = [];
Object.assign(this.program.uniforms, {
boneTexture: { value: this.boneTexture },
boneTextureSize: { value: this.boneTextureSize },
});
}
createBones(rig) {
// Create root so that can simply update world matrix of whole skeleton
this.root = new Transform();
// Create bones
this.bones = [];
if (!rig.bones || !rig.bones.length) return;
for (let i = 0; i < rig.bones.length; i++) {
const bone = new Transform();
// Set initial values (bind pose)
bone.position.fromArray(rig.bindPose.position, i * 3);
bone.quaternion.fromArray(rig.bindPose.quaternion, i * 4);
bone.scale.fromArray(rig.bindPose.scale, i * 3);
this.bones.push(bone);
}
// Once created, set the hierarchy
rig.bones.forEach((data, i) => {
this.bones[i].name = data.name;
if (data.parent === -1) return this.bones[i].setParent(this.root);
this.bones[i].setParent(this.bones[data.parent]);
});
// Then update to calculate world matrices
this.root.updateMatrixWorld(true);
// Store inverse of bind pose to calculate differences
this.bones.forEach((bone) => {
bone.bindInverse = new Mat4(...bone.worldMatrix).inverse();
});
}
createBoneTexture() {
if (!this.bones.length) return;
const size = Math.max(4, Math.pow(2, Math.ceil(Math.log(Math.sqrt(this.bones.length * 4)) / Math.LN2)));
this.boneMatrices = new Float32Array(size * size * 4);
this.boneTextureSize = size;
this.boneTexture = new Texture(this.gl, {
image: this.boneMatrices,
generateMipmaps: false,
type: this.gl.FLOAT,
internalFormat: this.gl.renderer.isWebgl2 ? this.gl.RGBA32F : this.gl.RGBA,
minFilter: this.gl.NEAREST,
magFilter: this.gl.NEAREST,
flipY: false,
width: size,
});
}
addAnimation(data) {
const animation = new Animation({ objects: this.bones, data });
this.animations.push(animation);
return animation;
}
update() {
// Calculate combined animation weight
let total = 0;
this.animations.forEach((animation) => (total += animation.weight));
this.animations.forEach((animation, i) => {
// force first animation to set in order to reset frame
animation.update(total, i === 0);
});
}
draw({ camera } = {}) {
// Update world matrices manually, as not part of scene graph
this.root.updateMatrixWorld(true);
// Update bone texture
this.bones.forEach((bone, i) => {
// Find difference between current and bind pose
tempMat4.multiply(bone.worldMatrix, bone.bindInverse);
this.boneMatrices.set(tempMat4, i * 16);
});
if (this.boneTexture) this.boneTexture.needsUpdate = true;
super.draw({ camera });
}
}