UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

141 lines (140 loc) 4.48 kB
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 };