playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
141 lines (140 loc) • 4.48 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { Debug } from "../core/debug.js";
import { math } from "../core/math/math.js";
import { Mat4 } from "../core/math/mat4.js";
import { FILTER_NEAREST, PIXELFORMAT_RGBA32F, TEXTURELOCK_READ } from "../platform/graphics/constants.js";
import { Texture } from "../platform/graphics/texture.js";
const _invMatrix = new Mat4();
class SkinInstance {
/**
* Create a new SkinInstance instance.
*
* @param {Skin} skin - The skin that will provide the inverse bind pose
* matrices to generate the final matrix palette.
*/
constructor(skin) {
/**
* An array of nodes representing each bone in this skin instance.
*
* @type {GraphNode[]}
*/
__publicField(this, "bones");
this._dirty = true;
this._rootBone = null;
this._skinUpdateIndex = -1;
this._updateBeforeCull = true;
if (skin) {
this.initSkin(skin);
}
}
set rootBone(rootBone) {
this._rootBone = rootBone;
}
get rootBone() {
return this._rootBone;
}
init(device, numBones) {
const numPixels = numBones * 3;
let width = Math.ceil(Math.sqrt(numPixels));
width = math.roundUp(width, 3);
const height = Math.ceil(numPixels / width);
this.boneTexture = new Texture(device, {
width,
height,
format: PIXELFORMAT_RGBA32F,
mipmaps: false,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
name: "skin"
});
this.matrixPalette = this.boneTexture.lock({ mode: TEXTURELOCK_READ });
this.boneTexture.unlock();
}
destroy() {
if (this.boneTexture) {
this.boneTexture.destroy();
this.boneTexture = null;
}
}
/**
* Resolves skin bones to a hierarchy with the rootBone at its root.
*
* @param {Entity} rootBone - A reference to the entity to be used as the root bone.
* @param {Entity} entity - Specifies the entity used if the bone match is not found in the
* hierarchy - usually the entity the render component is attached to.
* @ignore
*/
resolve(rootBone, entity) {
this.rootBone = rootBone;
const skin = this.skin;
const bones = [];
for (let j = 0; j < skin.boneNames.length; j++) {
const boneName = skin.boneNames[j];
let bone = rootBone.findByName(boneName);
if (!bone) {
Debug.error(`Failed to find bone [${boneName}] in the entity hierarchy, RenderComponent on ${entity.name}, rootBone: ${rootBone.name}`);
bone = entity;
}
bones.push(bone);
}
this.bones = bones;
}
/**
* @param {Skin} skin - The skin.
*/
initSkin(skin) {
this.skin = skin;
this.bones = [];
const numBones = skin.inverseBindPose.length;
this.init(skin.device, numBones);
this.matrices = [];
for (let i = 0; i < numBones; i++) {
this.matrices[i] = new Mat4();
}
}
uploadBones(device) {
this.boneTexture.upload();
}
_updateMatrices(rootNode, skinUpdateIndex) {
if (this._skinUpdateIndex !== skinUpdateIndex) {
this._skinUpdateIndex = skinUpdateIndex;
_invMatrix.copy(rootNode.getWorldTransform()).invert();
for (let i = this.bones.length - 1; i >= 0; i--) {
this.matrices[i].mulAffine2(_invMatrix, this.bones[i].getWorldTransform());
this.matrices[i].mulAffine2(this.matrices[i], this.skin.inverseBindPose[i]);
}
}
}
updateMatrices(rootNode, skinUpdateIndex) {
if (this._updateBeforeCull) {
this._updateMatrices(rootNode, skinUpdateIndex);
}
}
updateMatrixPalette(rootNode, skinUpdateIndex) {
this._updateMatrices(rootNode, skinUpdateIndex);
const mp = this.matrixPalette;
const count = this.bones.length;
for (let i = 0; i < count; i++) {
const pe = this.matrices[i].data;
const base = i * 12;
mp[base] = pe[0];
mp[base + 1] = pe[4];
mp[base + 2] = pe[8];
mp[base + 3] = pe[12];
mp[base + 4] = pe[1];
mp[base + 5] = pe[5];
mp[base + 6] = pe[9];
mp[base + 7] = pe[13];
mp[base + 8] = pe[2];
mp[base + 9] = pe[6];
mp[base + 10] = pe[10];
mp[base + 11] = pe[14];
}
this.uploadBones(this.skin.device);
}
}
export {
SkinInstance
};