@wonderlandengine/components
Version:
Wonderland Engine's official component library.
631 lines • 29.9 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Component } from '@wonderlandengine/api';
import { quat, quat2, vec3 } from 'gl-matrix';
import { property } from '@wonderlandengine/api/decorators.js';
const VRM_ROLL_AXES = {
X: [1.0, 0.0, 0.0],
Y: [0.0, 1.0, 0.0],
Z: [0.0, 0.0, 1.0],
};
const VRM_AIM_AXES = {
PositiveX: [1.0, 0.0, 0.0],
NegativeX: [-1.0, 0.0, 0.0],
PositiveY: [0.0, 1.0, 0.0],
NegativeY: [0.0, -1.0, 0.0],
PositiveZ: [0.0, 0.0, 1.0],
NegativeZ: [0.0, 0.0, -1.0],
};
const Rad2Deg = 180.0 / Math.PI;
const RightVector = vec3.fromValues(1, 0, 0);
const UpVector = vec3.fromValues(0, 1, 0);
const ForwardVector = vec3.fromValues(0, 0, 1);
/**
* Component for loading and handling VRM 1.0 models.
*
* Posing of the model should be done exclusively by rotating the bones. These can be
* accessed using the `.bones` property and follow the VRM bone naming. Note that not
* all VRM models will have all possible bones. The rest pose (T-pose) is captured in
* the `.restPose` property. Resetting a bone to its rest pose can be done as follows:
* ```js
* vrmComponent.bones[vrmBoneName].rotationLocal = vrmComponent.restPose[vrmBoneName];
* ```
*
* Moving the model through the world should be done by moving the object this component
* is attached to. In other words, by moving the root of the VRM model. The bones and any
* descendant objects should *not* be used to move the VRM model.
*
* The core extension `VRMC_vrm` as well as the`VRMC_springBone` and `VRMC_node_constraint`
* extensions are supported.
*
* **Limitations:**
* - No support for `VRMC_material_mtoon`
* - Expressions aren't supported
* - Expression based lookAt isn't supported
* - Mesh annotation mode `auto` is not supported (first person mode)
*/
class Vrm extends Component {
static TypeName = 'vrm';
/** URL to a VRM file to load */
src;
/** Object the VRM is looking at */
lookAtTarget;
/** Meta information about the VRM model */
meta = null;
/** The humanoid bones of the VRM model */
bones = {
/* Torso */
hips: null,
spine: null,
chest: null,
upperChest: null,
neck: null,
/* Head */
head: null,
leftEye: null,
rightEye: null,
jaw: null,
/* Legs */
leftUpperLeg: null,
leftLowerLeg: null,
leftFoot: null,
leftToes: null,
rightUpperLeg: null,
rightLowerLeg: null,
rightFoot: null,
rightToes: null,
/* Arms */
leftShoulder: null,
leftUpperArm: null,
leftLowerArm: null,
leftHand: null,
rightShoulder: null,
rightUpperArm: null,
rightLowerArm: null,
rightHand: null,
/* Fingers */
leftThumbMetacarpal: null,
leftThumbProximal: null,
leftThumbDistal: null,
leftIndexProximal: null,
leftIndexIntermediate: null,
leftIndexDistal: null,
leftMiddleProximal: null,
leftMiddleIntermediate: null,
leftMiddleDistal: null,
leftRingProximal: null,
leftRingIntermediate: null,
leftRingDistal: null,
leftLittleProximal: null,
leftLittleIntermediate: null,
leftLittleDistal: null,
rightThumbMetacarpal: null,
rightThumbProximal: null,
rightThumbDistal: null,
rightIndexProximal: null,
rightIndexIntermediate: null,
rightIndexDistal: null,
rightMiddleProximal: null,
rightMiddleIntermediate: null,
rightMiddleDistal: null,
rightRingProximal: null,
rightRingIntermediate: null,
rightRingDistal: null,
rightLittleProximal: null,
rightLittleIntermediate: null,
rightLittleDistal: null,
};
/** Rotations of the bones in the rest pose (T-pose) */
restPose = {};
/* All node constraints, ordered to deal with dependencies */
_nodeConstraints = [];
/* VRMC_springBone chains */
_springChains = [];
/* Spherical colliders for spring bones */
_sphereColliders = [];
/* Capsule shaped colliders for spring bones */
_capsuleColliders = [];
/* Indicates which meshes are rendered in first/third person views */
_firstPersonAnnotations = [];
/* Contains details for (bone type) lookAt behaviour */
_lookAt = null;
/* Whether or not the VRM component has been initialized with `initializeVrm` */
_initialized = false;
_tempV3 = vec3.create();
_tempV3A = vec3.create();
_tempV3B = vec3.create();
_tempQuat = quat.create();
_tempQuatA = quat.create();
_tempQuatB = quat.create();
_tempQuat2 = quat2.create();
_tailToShape = vec3.create();
_headToTail = vec3.create();
_inertia = vec3.create();
_stiffness = vec3.create();
_external = vec3.create();
_identityQuat = quat.identity(quat.create());
async start() {
if (!this.src) {
console.error('vrm: src property not set');
return;
}
const prefab = await this.engine.loadGLTF({ url: this.src, extensions: true });
const { root, extensions } = this.engine.scene.instantiate(prefab);
root.children.forEach((child) => (child.parent = this.object));
this._initializeVrm(prefab.extensions, extensions?.idMapping);
root.destroy();
}
/**
* Parses the VRM glTF extensions and initializes the vrm component.
* @param extensions The glTF extensions for the VRM model
*/
_initializeVrm(extensions, idMapping) {
if (this._initialized) {
throw new Error('VRM component has already been initialized');
}
const VRMC_vrm = extensions.root['VRMC_vrm'];
if (!VRMC_vrm) {
throw new Error('Missing VRM extensions');
}
if (VRMC_vrm.specVersion !== '1.0') {
throw new Error(`Unsupported VRM version, only 1.0 is supported, but encountered '${VRMC_vrm.specVersion}'`);
}
this.meta = VRMC_vrm.meta;
this._parseHumanoid(VRMC_vrm.humanoid, idMapping);
if (VRMC_vrm.firstPerson) {
this._parseFirstPerson(VRMC_vrm.firstPerson, extensions);
}
if (VRMC_vrm.lookAt) {
this._parseLookAt(VRMC_vrm.lookAt);
}
this._findAndParseNodeConstraints(extensions, idMapping);
const springBone = extensions.root['VRMC_springBone'];
if (springBone) {
this._parseAndInitializeSpringBones(springBone, idMapping);
}
this._initialized = true;
}
_parseHumanoid(humanoid, idMapping) {
for (const boneName in humanoid.humanBones) {
if (!(boneName in this.bones)) {
console.warn(`Unrecognized bone '${boneName}'`);
continue;
}
const node = humanoid.humanBones[boneName].node;
const objectId = idMapping[node];
this.bones[boneName] = this.engine.scene.wrap(objectId);
this.restPose[boneName] = this.bones[boneName].getRotationLocal(quat.create());
}
}
_parseFirstPerson(firstPerson, idMapping) {
for (const meshAnnotation of firstPerson.meshAnnotations) {
const annotation = {
node: this.engine.scene.wrap(idMapping[meshAnnotation.node]),
firstPerson: true,
thirdPerson: true,
};
switch (meshAnnotation.type) {
case 'firstPersonOnly':
annotation.thirdPerson = false;
break;
case 'thirdPersonOnly':
annotation.firstPerson = false;
break;
case 'both':
break;
case 'auto':
console.warn("First person mesh annotation type 'auto' is not supported, treating as 'both'!");
break;
default:
console.error(`Invalid mesh annotation type '${meshAnnotation.type}'`);
break;
}
this._firstPersonAnnotations.push(annotation);
}
}
_parseLookAt(lookAt) {
if (lookAt.type !== 'bone') {
console.warn(`Unsupported lookAt type '${lookAt.type}', only 'bone' is supported`);
return;
}
const parseRangeMap = (rangeMap) => {
return {
inputMaxValue: rangeMap.inputMaxValue,
outputScale: rangeMap.outputScale,
};
};
this._lookAt = {
offsetFromHeadBone: lookAt.offsetFromHeadBone || [0, 0, 0],
horizontalInner: parseRangeMap(lookAt.rangeMapHorizontalInner),
horizontalOuter: parseRangeMap(lookAt.rangeMapHorizontalOuter),
verticalDown: parseRangeMap(lookAt.rangeMapVerticalDown),
verticalUp: parseRangeMap(lookAt.rangeMapVerticalUp),
};
}
_findAndParseNodeConstraints(extensions, idMapping) {
const traverse = (object) => {
const nodeExtensions = extensions.node[object.objectId];
if (nodeExtensions && 'VRMC_node_constraint' in nodeExtensions) {
const nodeConstraintExtension = nodeExtensions['VRMC_node_constraint'];
const constraint = nodeConstraintExtension.constraint;
let type, axis;
if ('roll' in constraint) {
type = 'roll';
axis = VRM_ROLL_AXES[constraint.roll.rollAxis];
}
else if ('aim' in constraint) {
type = 'aim';
axis = VRM_AIM_AXES[constraint.aim.aimAxis];
}
else if ('rotation' in constraint) {
type = 'rotation';
}
if (type) {
const source = this.engine.scene.wrap(idMapping[constraint[type].source]);
this._nodeConstraints.push({
type,
source,
destination: object,
axis: axis,
weight: constraint[type].weight,
/* Rest pose */
destinationRestLocalRotation: object.getRotationLocal(quat.create()),
sourceRestLocalRotation: source.getRotationLocal(quat.create()),
sourceRestLocalRotationInv: quat.invert(quat.create(), source.getRotationLocal(this._tempQuat)),
});
}
else {
console.warn('Unrecognized or invalid VRMC_node_constraint, ignoring it');
}
}
for (const child of object.children) {
traverse(child);
}
};
traverse(this.object);
}
_parseAndInitializeSpringBones(springBone, idMapping) {
const colliders = (springBone.colliders || []).map((collider, i) => {
const shapeType = 'capsule' in collider.shape ? 'capsule' : 'sphere';
return {
id: i,
object: this.engine.scene.wrap(idMapping[collider.node]),
shape: {
isCapsule: shapeType === 'capsule',
radius: collider.shape[shapeType].radius,
offset: collider.shape[shapeType].offset,
tail: collider.shape[shapeType].tail,
},
cache: {
head: vec3.create(),
tail: vec3.create(),
},
};
});
this._sphereColliders = colliders.filter((c) => !c.shape.isCapsule);
this._capsuleColliders = colliders.filter((c) => c.shape.isCapsule);
const colliderGroups = (springBone.colliderGroups || []).map((group) => ({
name: group.name,
colliders: group.colliders.map((c) => colliders[c]),
}));
for (const spring of springBone.springs) {
const joints = [];
for (const joint of spring.joints) {
const springJoint = {
hitRadius: 0.0,
stiffness: 1.0,
gravityPower: 0.0,
gravityDir: [0.0, -1.0, 0.0],
dragForce: 0.5,
node: null,
state: null,
};
Object.assign(springJoint, joint);
springJoint.node = this.engine.scene.wrap(idMapping[joint.node]);
joints.push(springJoint);
}
const springChainColliders = (spring.colliderGroups || []).flatMap((cg) => colliderGroups[cg].colliders);
this._springChains.push({
name: spring.name,
center: spring.center
? this.engine.scene.wrap(idMapping[spring.center])
: null,
joints,
sphereColliders: springChainColliders.filter((c) => !c.shape.isCapsule),
capsuleColliders: springChainColliders.filter((c) => c.shape.isCapsule),
});
}
/* Initialize spring bone joint state */
for (const springChain of this._springChains) {
for (let i = 0; i < springChain.joints.length - 1; ++i) {
const springBoneJoint = springChain.joints[i];
const childSpringBoneJoint = springChain.joints[i + 1];
const springBonePosition = springBoneJoint.node.getPositionWorld(vec3.create());
const childSpringBonePosition = childSpringBoneJoint.node.getPositionWorld(vec3.create());
const boneDirection = vec3.subtract(this._tempV3A, springBonePosition, childSpringBonePosition);
const state = {
prevTail: vec3.copy(vec3.create(), childSpringBonePosition),
currentTail: vec3.copy(vec3.create(), childSpringBonePosition),
initialLocalRotation: springBoneJoint.node.getRotationLocal(quat.create()),
initialLocalTransformInvert: quat2.invert(quat2.create(), springBoneJoint.node.getTransformLocal(this._tempQuat2)),
boneAxis: vec3.normalize(vec3.create(), childSpringBoneJoint.node.getPositionLocal(this._tempV3)),
/* Ensure bone length is at least 1cm to avoid jittery behaviour from zero-length bones */
boneLength: Math.max(0.01, vec3.length(boneDirection)),
/* Tail positions in center space, if needed */
prevTailCenter: null,
currentTailCenter: null,
};
if (springChain.center) {
state.prevTailCenter = springChain.center.transformPointInverseWorld(vec3.create(), childSpringBonePosition);
state.currentTailCenter = vec3.copy(vec3.create(), childSpringBonePosition);
}
springBoneJoint.state = state;
}
}
}
update(dt) {
if (!this._initialized) {
return;
}
/* 1. Resolve humanoid bones (performed by user) */
/* 2. Resolve LookAt (bone type) as the position of the head is determined */
this._resolveLookAt();
/* 3. Expression update (TODO) */
/* 4. Apply Expression (TODO) */
/* 5. Resolve constraints */
this._resolveConstraints();
/* 6. Resolve Spring Bone */
this._updateSpringBones(dt);
}
_rangeMap(rangeMap, input) {
const maxValue = rangeMap.inputMaxValue;
const outputScale = rangeMap.outputScale;
return (Math.min(input, maxValue) / maxValue) * outputScale;
}
_resolveLookAt() {
if (!this._lookAt || !this.lookAtTarget) {
return;
}
const lookAtSource = this.bones.head.transformPointWorld(this._tempV3A, this._lookAt.offsetFromHeadBone);
const lookAtTarget = this.lookAtTarget.getPositionWorld(this._tempV3B);
const lookAtDirection = vec3.sub(this._tempV3A, lookAtTarget, lookAtSource);
vec3.normalize(lookAtDirection, lookAtDirection);
/* Convert the direction into LookAt space */
this.bones.head.parent.transformVectorInverseWorld(lookAtDirection);
const z = vec3.dot(lookAtDirection, ForwardVector);
const x = vec3.dot(lookAtDirection, RightVector);
const yaw = Math.atan2(x, z) * Rad2Deg;
const xz = Math.sqrt(x * x + z * z);
const y = vec3.dot(lookAtDirection, UpVector);
let pitch = Math.atan2(-y, xz) * Rad2Deg;
/* Limit pitch */
if (pitch > 0) {
pitch = this._rangeMap(this._lookAt.verticalDown, pitch);
}
else {
pitch = -this._rangeMap(this._lookAt.verticalUp, -pitch);
}
/* Left eye (limit yaw) */
if (this.bones.leftEye) {
let yawLeft = yaw;
if (yawLeft > 0) {
yawLeft = this._rangeMap(this._lookAt.horizontalInner, yawLeft);
}
else {
yawLeft = -this._rangeMap(this._lookAt.horizontalOuter, -yawLeft);
}
const eyeRotation = quat.fromEuler(this._tempQuatA, pitch, yawLeft, 0);
this.bones.leftEye.setRotationLocal(quat.multiply(eyeRotation, this.restPose.leftEye, eyeRotation));
}
/* Right eye (limit yaw) */
if (this.bones.rightEye) {
let yawRight = yaw;
if (yawRight > 0) {
yawRight = this._rangeMap(this._lookAt.horizontalOuter, yawRight);
}
else {
yawRight = -this._rangeMap(this._lookAt.horizontalInner, -yawRight);
}
const eyeRotation = quat.fromEuler(this._tempQuatA, pitch, yawRight, 0);
this.bones.rightEye.setRotationLocal(quat.multiply(eyeRotation, this.restPose.rightEye, eyeRotation));
}
}
_resolveConstraints() {
for (const nodeConstraint of this._nodeConstraints) {
this._resolveConstraint(nodeConstraint);
}
}
_resolveConstraint(nodeConstraint) {
const dstRestQuat = nodeConstraint.destinationRestLocalRotation;
const srcRestQuatInv = nodeConstraint.sourceRestLocalRotationInv;
const targetQuat = quat.identity(this._tempQuatA);
switch (nodeConstraint.type) {
case 'roll':
{
const deltaSrcQuat = quat.multiply(this._tempQuatA, srcRestQuatInv, nodeConstraint.source.rotationLocal);
/* source to parent */
const deltaSrcQuatInParent = quat.multiply(this._tempQuatA, nodeConstraint.sourceRestLocalRotation, deltaSrcQuat);
quat.mul(deltaSrcQuatInParent, deltaSrcQuatInParent, srcRestQuatInv);
/* parent to destination */
const dstRestQuatInv = quat.invert(this._tempQuatB, dstRestQuat);
const deltaSrcQuatInDst = quat.multiply(this._tempQuatB, dstRestQuatInv, deltaSrcQuatInParent);
quat.multiply(deltaSrcQuatInDst, deltaSrcQuatInDst, dstRestQuat);
const toVec = vec3.transformQuat(this._tempV3A, nodeConstraint.axis, deltaSrcQuatInDst);
const fromToQuat = quat.rotationTo(this._tempQuatA, nodeConstraint.axis, toVec);
quat.mul(targetQuat, dstRestQuat, quat.invert(this._tempQuat, fromToQuat));
quat.mul(targetQuat, targetQuat, deltaSrcQuatInDst);
}
break;
case 'aim':
{
const dstParentWorldQuat = nodeConstraint.destination.parent.rotationWorld;
/* fromVec = aimAxis.applyQuaternion( dstParentWorldQuat * dstRestQuat ) */
const fromVec = vec3.transformQuat(this._tempV3A, nodeConstraint.axis, dstRestQuat);
vec3.transformQuat(fromVec, fromVec, dstParentWorldQuat);
/* toVec = ( srcWorldPos - dstWorldPos ).normalized */
const toVec = nodeConstraint.source.getTranslationWorld(this._tempV3B);
vec3.sub(toVec, toVec, nodeConstraint.destination.getTranslationWorld(this._tempV3));
vec3.normalize(toVec, toVec);
/* fromToQuat = Quaternion.fromToRotation( fromVec, toVec ) */
const fromToQuat = quat.rotationTo(this._tempQuatA, fromVec, toVec);
quat.mul(targetQuat, quat.invert(this._tempQuat, dstParentWorldQuat), fromToQuat);
quat.mul(targetQuat, targetQuat, dstParentWorldQuat);
quat.mul(targetQuat, targetQuat, dstRestQuat);
}
break;
case 'rotation':
{
const srcDeltaQuat = quat.mul(targetQuat, srcRestQuatInv, nodeConstraint.source.rotationLocal);
quat.mul(targetQuat, dstRestQuat, srcDeltaQuat);
}
break;
}
/* Apply constraint */
quat.slerp(targetQuat, dstRestQuat, targetQuat, nodeConstraint.weight);
nodeConstraint.destination.rotationLocal = targetQuat;
}
_updateSpringBones(dt) {
/* Pre-compute collider positions */
this._sphereColliders.forEach(({ object, shape, cache }) => {
const offset = vec3.copy(cache.head, shape.offset);
object.transformVectorWorld(offset);
vec3.add(cache.head, object.getPositionWorld(this._tempV3), offset);
});
this._capsuleColliders.forEach(({ object, shape, cache }) => {
const shapeCenter = object.getPositionWorld(this._tempV3A);
const headOffset = vec3.copy(cache.head, shape.offset);
object.transformVectorWorld(headOffset);
vec3.add(cache.head, shapeCenter, headOffset);
const tailOffset = vec3.copy(cache.tail, shape.tail);
object.transformVectorWorld(tailOffset);
vec3.add(cache.tail, shapeCenter, tailOffset);
});
/* Update spring chains */
this._springChains.forEach((springChain) => {
for (let i = 0; i < springChain.joints.length - 1; ++i) {
const joint = springChain.joints[i];
if (!joint.state)
continue;
const parentWorldRotation = joint.node.parent
? joint.node.parent.getRotationWorld(this._tempQuat)
: this._identityQuat;
/* 1. Forces */
/* inertia = (currentTail - prevTail) * (1.0f - dragForce); */
const inertia = this._inertia;
if (springChain.center) {
vec3.sub(inertia, joint.state.currentTailCenter, joint.state.prevTailCenter);
springChain.center.transformVectorWorld(inertia);
}
else {
vec3.sub(inertia, joint.state.currentTail, joint.state.prevTail);
}
vec3.scale(inertia, inertia, 1.0 - joint.dragForce);
/* stiffness = deltaTime * parentWorldRotation * localRotation * boneAxis * stiffnessForce; */
const stiffness = vec3.copy(this._stiffness, joint.state.boneAxis);
vec3.transformQuat(stiffness, stiffness, joint.state.initialLocalRotation);
vec3.transformQuat(stiffness, stiffness, parentWorldRotation);
vec3.scale(stiffness, stiffness, dt * joint.stiffness);
/* external = deltaTime * gravityDir * gravityPower; */
const external = vec3.scale(this._external, joint.gravityDir, dt * joint.gravityPower);
/* nextTail = currentTail + inertia + stiffness + external; */
const nextTail = vec3.copy(this._tempV3A, joint.state.currentTail);
vec3.add(nextTail, nextTail, inertia);
vec3.add(nextTail, nextTail, stiffness);
vec3.add(nextTail, nextTail, external);
/* constrain the length */
/* nextTail = worldPosition + (nextTail - worldPosition).normalized * boneLength; */
const worldPosition = joint.node.getPositionWorld(this._tempV3B);
vec3.sub(nextTail, nextTail, worldPosition);
vec3.normalize(nextTail, nextTail);
vec3.scaleAndAdd(nextTail, worldPosition, nextTail, joint.state.boneLength);
/* 2. Collision with colliders */
/* Sphere colliders */
for (const { shape, cache } of springChain.sphereColliders) {
let tailToShape = this._tailToShape;
const sphereCenter = cache.head;
tailToShape = vec3.sub(tailToShape, nextTail, sphereCenter);
const radius = shape.radius + joint.hitRadius;
const dist = vec3.length(tailToShape) - radius;
if (dist < 0.0) {
vec3.normalize(tailToShape, tailToShape);
vec3.scaleAndAdd(nextTail, nextTail, tailToShape, -dist);
/* constraint the length */
vec3.sub(nextTail, nextTail, worldPosition);
vec3.normalize(nextTail, nextTail);
vec3.scaleAndAdd(nextTail, worldPosition, nextTail, joint.state.boneLength);
}
}
/* Capsule colliders */
for (const { shape, cache } of springChain.capsuleColliders) {
let tailToShape = this._tailToShape;
const head = cache.head;
const tail = cache.tail;
/* Naively start with distance to the head */
tailToShape = vec3.sub(tailToShape, nextTail, head);
const headToTail = vec3.sub(this._headToTail, tail, head);
const dot = vec3.dot(headToTail, tailToShape);
if (vec3.squaredLength(headToTail) <= dot) {
/* Closest to tail */
vec3.sub(tailToShape, nextTail, tail);
}
else if (dot > 0.0) {
/* Closest to middle */
vec3.scale(headToTail, headToTail, dot / vec3.squaredLength(headToTail));
vec3.sub(tailToShape, tailToShape, headToTail);
}
const radius = shape.radius + joint.hitRadius;
const dist = vec3.length(tailToShape) - radius;
if (dist < 0.0) {
vec3.normalize(tailToShape, tailToShape);
vec3.scaleAndAdd(nextTail, nextTail, tailToShape, -dist);
/* constraint the length */
vec3.sub(nextTail, nextTail, worldPosition);
vec3.normalize(nextTail, nextTail);
vec3.scaleAndAdd(nextTail, worldPosition, nextTail, joint.state.boneLength);
}
}
/* 3. Applying rotation */
vec3.copy(joint.state.prevTail, joint.state.currentTail);
vec3.copy(joint.state.currentTail, nextTail);
if (springChain.center) {
vec3.copy(joint.state.prevTailCenter, joint.state.currentTailCenter);
vec3.copy(joint.state.currentTailCenter, nextTail);
springChain.center.transformPointInverseWorld(joint.state.currentTailCenter);
}
/* to = (nextTail * (node.parent.worldMatrix * initialLocalMatrix).inverse).normalized */
joint.node.parent.transformPointInverseWorld(nextTail);
const nextTailDualQuat = quat2.fromTranslation(this._tempQuat2, nextTail);
quat2.multiply(nextTailDualQuat, joint.state.initialLocalTransformInvert, nextTailDualQuat);
quat2.getTranslation(nextTail, nextTailDualQuat);
vec3.normalize(nextTail, nextTail);
/* node.rotation = initialLocalRotation * Quaternion.fromToQuaternion(boneAxis, to); */
const jointRotation = quat.rotationTo(this._tempQuatA, joint.state.boneAxis, nextTail);
joint.node.setRotationLocal(quat.mul(this._tempQuatA, joint.state.initialLocalRotation, jointRotation));
}
});
}
/**
* @param firstPerson Whether the model should render for first person or third person views
*/
set firstPerson(firstPerson) {
this._firstPersonAnnotations.forEach((annotation) => {
const visible = firstPerson == annotation.firstPerson ||
firstPerson != annotation.thirdPerson;
annotation.node.getComponents('mesh').forEach((mesh) => {
mesh.active = visible;
});
});
}
}
__decorate([
property.string()
], Vrm.prototype, "src", void 0);
__decorate([
property.object()
], Vrm.prototype, "lookAtTarget", void 0);
export { Vrm };
//# sourceMappingURL=vrm.js.map