import * as THREE from 'three' ; import { GLTFNode , GLTFPrimitive } from '../types' ; import { getWorldQuaternionLite } from '../utils/math' ; const VECTOR3_FRONT = Object . freeze ( new THREE . Vector3 ( 0.0 , 0.0 , - 1.0 ) ) ; const _quat = new THREE . Quaternion ( ) ; enum FirstPersonFlag { Auto , Both , ThirdPersonOnly , FirstPersonOnly } /**
 * This class represents a single [`meshAnnotation`](https://github.com/vrm-c/UniVRM/blob/master/specification/0.0/schema/vrm.firstperson.meshannotation.schema.json) entry.
 * Each mesh will be assigned to specified layer when you call [[VRMFirstPerson.setup]].
 */ export class VRMRendererFirstPersonFlags { private static _parseFirstPersonFlag ( firstPersonFlag ) { switch ( firstPersonFlag ) { case 'Both' : return FirstPersonFlag . Both ; case 'ThirdPersonOnly' : return FirstPersonFlag . ThirdPersonOnly ; case 'FirstPersonOnly' : return FirstPersonFlag . FirstPersonOnly ; default : return FirstPersonFlag . Auto ; } } /**
     * A [[FirstPersonFlag]] of the annotation entry.
     */ public firstPersonFlag : FirstPersonFlag ; /**
     * A mesh primitives of the annotation entry.
     */ public primitives : GLTFPrimitive [ ] ; param firstPersonFlag param node constructor ( firstPersonFlag , primitives ) { this . firstPersonFlag = VRMRendererFirstPersonFlags . _parseFirstPersonFlag ( firstPersonFlag ) ; this . primitives = primitives ; } } export class VRMFirstPerson { see private static readonly _DEFAULT_FIRSTPERSON_ONLY_LAYER = 9 ; see private static readonly _DEFAULT_THIRDPERSON_ONLY_LAYER = 10 ; private readonly _firstPersonBone : GLTFNode ; private readonly _meshAnnotations : VRMRendererFirstPersonFlags [ ] = [ ] ; private readonly _firstPersonBoneOffset : THREE . Vector3 ; private _firstPersonOnlyLayer = VRMFirstPerson . _DEFAULT_FIRSTPERSON_ONLY_LAYER ; private _thirdPersonOnlyLayer = VRMFirstPerson . _DEFAULT_THIRDPERSON_ONLY_LAYER ; private _initialized = false ; param firstPersonBone param firstPersonBoneOffset param meshAnnotations constructor ( firstPersonBone , firstPersonBoneOffset , meshAnnotations ) { this . _firstPersonBone = firstPersonBone ; this . _firstPersonBoneOffset = firstPersonBoneOffset ; this . _meshAnnotations = meshAnnotations ; } public get firstPersonBone ( ) : GLTFNode { return this . _firstPersonBone ; } public get meshAnnotations ( ) : VRMRendererFirstPersonFlags [ ] { return this . _meshAnnotations ; } public getFirstPersonWorldDirection ( target ) { return target . copy ( VECTOR3_FRONT ) . applyQuaternion ( getWorldQuaternionLite ( this . _firstPersonBone , _quat ) ) ; } see https see https public get firstPersonOnlyLayer ( ) : number { return this . _firstPersonOnlyLayer ; } see https see https public get thirdPersonOnlyLayer ( ) : number { return this . _thirdPersonOnlyLayer ; } public getFirstPersonBoneOffset ( target ) { return target . copy ( this . _firstPersonBoneOffset ) ; } param v3 returns public getFirstPersonWorldPosition ( v3 ) { const offset = this . _firstPersonBoneOffset ; const v4 = new THREE . Vector4 ( offset . x , offset . y , offset . z , 1.0 ) ; v4 . applyMatrix4 ( this . _firstPersonBone . matrixWorld ) ; return v3 . set ( v4 . x , v4 . y , v4 . z ) ; } param cameraLayer public setup ( { firstPersonOnlyLayer = VRMFirstPerson . _DEFAULT_FIRSTPERSON_ONLY_LAYER , thirdPersonOnlyLayer = VRMFirstPerson . _DEFAULT_THIRDPERSON_ONLY_LAYER , } = { } ) { if ( this . _initialized ) { return ; } this . _initialized = true ; this . _firstPersonOnlyLayer = firstPersonOnlyLayer ; this . _thirdPersonOnlyLayer = thirdPersonOnlyLayer ; this . _meshAnnotations . forEach ( ( item ) => { if ( item . firstPersonFlag === FirstPersonFlag . FirstPersonOnly ) { item . primitives . forEach ( ( primitive ) => { primitive . layers . set ( this . _firstPersonOnlyLayer ) ; } ) ; } else if ( item . firstPersonFlag === FirstPersonFlag . ThirdPersonOnly ) { item . primitives . forEach ( ( primitive ) => { primitive . layers . set ( this . _thirdPersonOnlyLayer ) ; } ) ; } else if ( item . firstPersonFlag === FirstPersonFlag . Auto ) { this . _createHeadlessModel ( item . primitives ) ; } } ) ; } private _excludeTriangles ( triangles , bws , skinIndex , exclude ) { let count = 0 ; if ( bws != null && bws . length > 0 ) { for ( let i = 0 ; i < triangles . length ; i += 3 ) { const a = triangles [ i ] ; const b = triangles [ i + 1 ] ; const c = triangles [ i + 2 ] ; const bw0 = bws [ a ] ; const skin0 = skinIndex [ a ] ; if ( bw0 [ 0 ] > 0 && exclude . includes ( skin0 [ 0 ] ) ) continue ; if ( bw0 [ 1 ] > 0 && exclude . includes ( skin0 [ 1 ] ) ) continue ; if ( bw0 [ 2 ] > 0 && exclude . includes ( skin0 [ 2 ] ) ) continue ; if ( bw0 [ 3 ] > 0 && exclude . includes ( skin0 [ 3 ] ) ) continue ; const bw1 = bws [ b ] ; const skin1 = skinIndex [ b ] ; if ( bw1 [ 0 ] > 0 && exclude . includes ( skin1 [ 0 ] ) ) continue ; if ( bw1 [ 1 ] > 0 && exclude . includes ( skin1 [ 1 ] ) ) continue ; if ( bw1 [ 2 ] > 0 && exclude . includes ( skin1 [ 2 ] ) ) continue ; if ( bw1 [ 3 ] > 0 && exclude . includes ( skin1 [ 3 ] ) ) continue ; const bw2 = bws [ c ] ; const skin2 = skinIndex [ c ] ; if ( bw2 [ 0 ] > 0 && exclude . includes ( skin2 [ 0 ] ) ) continue ; if ( bw2 [ 1 ] > 0 && exclude . includes ( skin2 [ 1 ] ) ) continue ; if ( bw2 [ 2 ] > 0 && exclude . includes ( skin2 [ 2 ] ) ) continue ; if ( bw2 [ 3 ] > 0 && exclude . includes ( skin2 [ 3 ] ) ) continue ; triangles [ count ++ ] = a ; triangles [ count ++ ] = b ; triangles [ count ++ ] = c ; } } return count ; } private _createErasedMesh ( src , erasingBonesIndex ) { const dst = new THREE . SkinnedMesh ( src . geometry . clone ( ) , src . material ) ; dst . name = `${ src . name }(erase)` ; dst . frustumCulled = src . frustumCulled ; dst . layers . set ( this . _firstPersonOnlyLayer ) ; const geometry = dst . geometry ; const skinIndexAttr = geometry . getAttribute ( 'skinIndex' ) . array ; const skinIndex = [ ] ; for ( let i = 0 ; i < skinIndexAttr . length ; i += 4 ) { skinIndex . push ( [ skinIndexAttr [ i ] , skinIndexAttr [ i + 1 ] , skinIndexAttr [ i + 2 ] , skinIndexAttr [ i + 3 ] ] ) ; } const skinWeightAttr = geometry . getAttribute ( 'skinWeight' ) . array ; const skinWeight = [ ] ; for ( let i = 0 ; i < skinWeightAttr . length ; i += 4 ) { skinWeight . push ( [ skinWeightAttr [ i ] , skinWeightAttr [ i + 1 ] , skinWeightAttr [ i + 2 ] , skinWeightAttr [ i + 3 ] ] ) ; } const index = geometry . getIndex ( ) ; if ( ! index ) { throw new Error ( "The geometry doesn't have an index buffer" ) ; } const oldTriangles = Array . from ( index . array ) ; const count = this . _excludeTriangles ( oldTriangles , skinWeight , skinIndex , erasingBonesIndex ) ; const newTriangle = [ ] ; for ( let i = 0 ; i < count ; i ++ ) { newTriangle [ i ] = oldTriangles [ i ] ; } geometry . setIndex ( newTriangle ) ; if ( src . onBeforeRender ) { dst . onBeforeRender = src . onBeforeRender ; } dst . bind ( new THREE . Skeleton ( src . skeleton . bones , src . skeleton . boneInverses ) , new THREE . Matrix4 ( ) ) ; return dst ; } private _createHeadlessModelForSkinnedMesh ( parent , mesh ) { const eraseBoneIndexes = [ ] ; mesh . skeleton . bones . forEach ( ( bone , index ) => { if ( this . _isEraseTarget ( bone ) ) eraseBoneIndexes . push ( index ) ; } ) ; if ( ! eraseBoneIndexes . length ) { mesh . layers . enable ( this . _thirdPersonOnlyLayer ) ; mesh . layers . enable ( this . _firstPersonOnlyLayer ) ; return ; } mesh . layers . set ( this . _thirdPersonOnlyLayer ) ; const newMesh = this . _createErasedMesh ( mesh , eraseBoneIndexes ) ; parent . add ( newMesh ) ; } private _createHeadlessModel ( primitives ) { primitives . forEach ( ( primitive ) => { if ( primitive . type === 'SkinnedMesh' ) { const skinnedMesh = primitive as THREE . SkinnedMesh ; this . _createHeadlessModelForSkinnedMesh ( skinnedMesh . parent ! , skinnedMesh ) ; } else { if ( this . _isEraseTarget ( primitive ) ) { primitive . layers . set ( this . _thirdPersonOnlyLayer ) ; } } } ) ; } param bone private _isEraseTarget ( bone ) { if ( bone === this . _firstPersonBone ) { return true ; } else if ( ! bone . parent ) { return false ; } else { return this . _isEraseTarget ( bone . parent ) ; } } }