UNPKG

@wonderlandengine/components

Version:

Wonderland Engine's official component library.

631 lines 29.9 kB
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