UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

156 lines (153 loc) 5.79 kB
import { Debug } from '../core/debug.js'; import { math } from '../core/math/math.js'; import { Mat4 } from '../core/math/mat4.js'; import { PIXELFORMAT_RGBA32F, FILTER_NEAREST, TEXTURELOCK_READ } from '../platform/graphics/constants.js'; import { Texture } from '../platform/graphics/texture.js'; /** * @import { Entity } from '../framework/entity.js' * @import { GraphNode } from './graph-node.js' * @import { Skin } from './skin.js' */ var _invMatrix = new Mat4(); /** * A skin instance is responsible for generating the matrix palette that is used to skin vertices * from object space to world space. * * @category Graphics */ class SkinInstance { set rootBone(rootBone) { this._rootBone = rootBone; } get rootBone() { return this._rootBone; } init(device, numBones) { // texture size - roughly square that fits all bones, width is multiply of 3 to simplify shader math var numPixels = numBones * 3; var width = Math.ceil(Math.sqrt(numPixels)); width = math.roundUp(width, 3); var height = Math.ceil(numPixels / width); this.boneTexture = new Texture(device, { width: width, height: height, format: PIXELFORMAT_RGBA32F, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, name: 'skin' }); this.boneTextureSize = [ width, height, 1.0 / width, 1.0 / height ]; 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; // Resolve bone IDs to actual graph nodes of the hierarchy var skin = this.skin; var bones = []; for(var j = 0; j < skin.boneNames.length; j++){ var boneName = skin.boneNames[j]; /** @type {Entity|GraphNode|null} */ var 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; // Unique per clone this.bones = []; var numBones = skin.inverseBindPose.length; this.init(skin.device, numBones); this.matrices = []; for(var i = 0; i < numBones; i++){ this.matrices[i] = new Mat4(); } } uploadBones(device) { this.boneTexture.upload(); } _updateMatrices(rootNode, skinUpdateIndex) { // if not already up to date if (this._skinUpdateIndex !== skinUpdateIndex) { this._skinUpdateIndex = skinUpdateIndex; _invMatrix.copy(rootNode.getWorldTransform()).invert(); for(var i = this.bones.length - 1; i >= 0; i--){ this.matrices[i].mulAffine2(_invMatrix, this.bones[i].getWorldTransform()); // world space -> rootNode space this.matrices[i].mulAffine2(this.matrices[i], this.skin.inverseBindPose[i]); // rootNode space -> bind space } } } updateMatrices(rootNode, skinUpdateIndex) { if (this._updateBeforeCull) { this._updateMatrices(rootNode, skinUpdateIndex); } } updateMatrixPalette(rootNode, skinUpdateIndex) { // make sure matrices are up to date this._updateMatrices(rootNode, skinUpdateIndex); // copy matrices to palette var mp = this.matrixPalette; var count = this.bones.length; for(var i = 0; i < count; i++){ var pe = this.matrices[i].data; // Copy the matrix into the palette, ready to be sent to the vertex shader, transpose matrix from 4x4 to 4x3 format as well var 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); } /** * 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){ this._dirty = true; // optional root bone - used for cache lookup, not used for skinning this._rootBone = null; // sequential index of when the bone update was performed the last time this._skinUpdateIndex = -1; // true if bones need to be updated before the frustum culling (bones are needed to update bounds of the MeshInstance) this._updateBeforeCull = true; if (skin) { this.initSkin(skin); } } } export { SkinInstance };