@openhps/core
Version:
Open Hybrid Positioning System - Core component
280 lines (252 loc) • 10 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.skinning = exports.default = exports.computeSkinning = void 0;
var _Node = _interopRequireDefault(require("../core/Node.js"));
var _constants = require("../core/constants.js");
var _TSLBase = require("../tsl/TSLBase.js");
var _AttributeNode = require("../core/AttributeNode.js");
var _ReferenceNode = require("./ReferenceNode.js");
var _OperatorNode = require("../math/OperatorNode.js");
var _Normal = require("./Normal.js");
var _Position = require("./Position.js");
var _Tangent = require("./Tangent.js");
var _UniformNode = require("../core/UniformNode.js");
var _BufferNode = require("./BufferNode.js");
var _NodeUtils = require("../core/NodeUtils.js");
var _StorageBufferNode = require("./StorageBufferNode.js");
var _InstancedBufferAttribute = require("../../core/InstancedBufferAttribute.js");
var _IndexNode = require("../core/IndexNode.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const _frameId = new WeakMap();
/**
* This node implements the vertex transformation shader logic which is required
* for skinning/skeletal animation.
*
* @augments Node
*/
class SkinningNode extends _Node.default {
static get type() {
return 'SkinningNode';
}
/**
* Constructs a new skinning node.
*
* @param {SkinnedMesh} skinnedMesh - The skinned mesh.
*/
constructor(skinnedMesh) {
super('void');
/**
* The skinned mesh.
*
* @type {SkinnedMesh}
*/
this.skinnedMesh = skinnedMesh;
/**
* The update type overwritten since skinning nodes are updated per object.
*
* @type {string}
*/
this.updateType = _constants.NodeUpdateType.OBJECT;
//
/**
* The skin index attribute.
*
* @type {AttributeNode}
*/
this.skinIndexNode = (0, _AttributeNode.attribute)('skinIndex', 'uvec4');
/**
* The skin weight attribute.
*
* @type {AttributeNode}
*/
this.skinWeightNode = (0, _AttributeNode.attribute)('skinWeight', 'vec4');
/**
* The bind matrix node.
*
* @type {Node<mat4>}
*/
this.bindMatrixNode = (0, _ReferenceNode.reference)('bindMatrix', 'mat4');
/**
* The bind matrix inverse node.
*
* @type {Node<mat4>}
*/
this.bindMatrixInverseNode = (0, _ReferenceNode.reference)('bindMatrixInverse', 'mat4');
/**
* The bind matrices as a uniform buffer node.
*
* @type {Node}
*/
this.boneMatricesNode = (0, _ReferenceNode.referenceBuffer)('skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length);
/**
* The current vertex position in local space.
*
* @type {Node<vec3>}
*/
this.positionNode = _Position.positionLocal;
/**
* The result of vertex position in local space.
*
* @type {Node<vec3>}
*/
this.toPositionNode = _Position.positionLocal;
/**
* The previous bind matrices as a uniform buffer node.
* Required for computing motion vectors.
*
* @type {?Node}
* @default null
*/
this.previousBoneMatricesNode = null;
}
/**
* Transforms the given vertex position via skinning.
*
* @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices
* @param {Node<vec3>} [position=this.positionNode] - The vertex position in local space.
* @return {Node<vec3>} The transformed vertex position.
*/
getSkinnedPosition(boneMatrices = this.boneMatricesNode, position = this.positionNode) {
const {
skinIndexNode,
skinWeightNode,
bindMatrixNode,
bindMatrixInverseNode
} = this;
const boneMatX = boneMatrices.element(skinIndexNode.x);
const boneMatY = boneMatrices.element(skinIndexNode.y);
const boneMatZ = boneMatrices.element(skinIndexNode.z);
const boneMatW = boneMatrices.element(skinIndexNode.w);
// POSITION
const skinVertex = bindMatrixNode.mul(position);
const skinned = (0, _OperatorNode.add)(boneMatX.mul(skinWeightNode.x).mul(skinVertex), boneMatY.mul(skinWeightNode.y).mul(skinVertex), boneMatZ.mul(skinWeightNode.z).mul(skinVertex), boneMatW.mul(skinWeightNode.w).mul(skinVertex));
return bindMatrixInverseNode.mul(skinned).xyz;
}
/**
* Transforms the given vertex normal via skinning.
*
* @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices
* @param {Node<vec3>} [normal=normalLocal] - The vertex normal in local space.
* @return {Node<vec3>} The transformed vertex normal.
*/
getSkinnedNormal(boneMatrices = this.boneMatricesNode, normal = _Normal.normalLocal) {
const {
skinIndexNode,
skinWeightNode,
bindMatrixNode,
bindMatrixInverseNode
} = this;
const boneMatX = boneMatrices.element(skinIndexNode.x);
const boneMatY = boneMatrices.element(skinIndexNode.y);
const boneMatZ = boneMatrices.element(skinIndexNode.z);
const boneMatW = boneMatrices.element(skinIndexNode.w);
// NORMAL
let skinMatrix = (0, _OperatorNode.add)(skinWeightNode.x.mul(boneMatX), skinWeightNode.y.mul(boneMatY), skinWeightNode.z.mul(boneMatZ), skinWeightNode.w.mul(boneMatW));
skinMatrix = bindMatrixInverseNode.mul(skinMatrix).mul(bindMatrixNode);
return skinMatrix.transformDirection(normal).xyz;
}
/**
* Transforms the given vertex normal via skinning.
*
* @param {NodeBuilder} builder - The current node builder.
* @return {Node<vec3>} The skinned position from the previous frame.
*/
getPreviousSkinnedPosition(builder) {
const skinnedMesh = builder.object;
if (this.previousBoneMatricesNode === null) {
skinnedMesh.skeleton.previousBoneMatrices = new Float32Array(skinnedMesh.skeleton.boneMatrices);
this.previousBoneMatricesNode = (0, _ReferenceNode.referenceBuffer)('skeleton.previousBoneMatrices', 'mat4', skinnedMesh.skeleton.bones.length);
}
return this.getSkinnedPosition(this.previousBoneMatricesNode, _Position.positionPrevious);
}
/**
* Returns `true` if bone matrices from the previous frame are required.
*
* @param {NodeBuilder} builder - The current node builder.
* @return {boolean} Whether bone matrices from the previous frame are required or not.
*/
needsPreviousBoneMatrices(builder) {
const mrt = builder.renderer.getMRT();
return mrt && mrt.has('velocity') || (0, _NodeUtils.getDataFromObject)(builder.object).useVelocity === true;
}
/**
* Setups the skinning node by assigning the transformed vertex data to predefined node variables.
*
* @param {NodeBuilder} builder - The current node builder.
* @return {Node<vec3>} The transformed vertex position.
*/
setup(builder) {
if (this.needsPreviousBoneMatrices(builder)) {
_Position.positionPrevious.assign(this.getPreviousSkinnedPosition(builder));
}
const skinPosition = this.getSkinnedPosition();
if (this.toPositionNode) this.toPositionNode.assign(skinPosition);
//
if (builder.hasGeometryAttribute('normal')) {
const skinNormal = this.getSkinnedNormal();
_Normal.normalLocal.assign(skinNormal);
if (builder.hasGeometryAttribute('tangent')) {
_Tangent.tangentLocal.assign(skinNormal);
}
}
return skinPosition;
}
/**
* Generates the code snippet of the skinning node.
*
* @param {NodeBuilder} builder - The current node builder.
* @param {string} output - The current output.
* @return {string} The generated code snippet.
*/
generate(builder, output) {
if (output !== 'void') {
return super.generate(builder, output);
}
}
/**
* Updates the state of the skinned mesh by updating the skeleton once per frame.
*
* @param {NodeFrame} frame - The current node frame.
*/
update(frame) {
const skeleton = frame.object && frame.object.skeleton ? frame.object.skeleton : this.skinnedMesh.skeleton;
if (_frameId.get(skeleton) === frame.frameId) return;
_frameId.set(skeleton, frame.frameId);
if (this.previousBoneMatricesNode !== null) skeleton.previousBoneMatrices.set(skeleton.boneMatrices);
skeleton.update();
}
}
var _default = exports.default = SkinningNode;
/**
* TSL function for creating a skinning node.
*
* @tsl
* @function
* @param {SkinnedMesh} skinnedMesh - The skinned mesh.
* @returns {SkinningNode}
*/
const skinning = skinnedMesh => (0, _TSLBase.nodeObject)(new SkinningNode(skinnedMesh));
/**
* TSL function for computing skinning.
*
* @tsl
* @function
* @param {SkinnedMesh} skinnedMesh - The skinned mesh.
* @param {Node<vec3>} [toPosition=null] - The target position.
* @returns {SkinningNode}
*/
exports.skinning = skinning;
const computeSkinning = (skinnedMesh, toPosition = null) => {
const node = new SkinningNode(skinnedMesh);
node.positionNode = (0, _StorageBufferNode.storage)(new _InstancedBufferAttribute.InstancedBufferAttribute(skinnedMesh.geometry.getAttribute('position').array, 3), 'vec3').setPBO(true).toReadOnly().element(_IndexNode.instanceIndex).toVar();
node.skinIndexNode = (0, _StorageBufferNode.storage)(new _InstancedBufferAttribute.InstancedBufferAttribute(new Uint32Array(skinnedMesh.geometry.getAttribute('skinIndex').array), 4), 'uvec4').setPBO(true).toReadOnly().element(_IndexNode.instanceIndex).toVar();
node.skinWeightNode = (0, _StorageBufferNode.storage)(new _InstancedBufferAttribute.InstancedBufferAttribute(skinnedMesh.geometry.getAttribute('skinWeight').array, 4), 'vec4').setPBO(true).toReadOnly().element(_IndexNode.instanceIndex).toVar();
node.bindMatrixNode = (0, _UniformNode.uniform)(skinnedMesh.bindMatrix, 'mat4');
node.bindMatrixInverseNode = (0, _UniformNode.uniform)(skinnedMesh.bindMatrixInverse, 'mat4');
node.boneMatricesNode = (0, _BufferNode.buffer)(skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length);
node.toPositionNode = toPosition;
return (0, _TSLBase.nodeObject)(node);
};
exports.computeSkinning = computeSkinning;