|
|
import _extends from '@babel/runtime/helpers/esm/extends'; |
|
|
import * as THREE from 'three'; |
|
|
import * as React from 'react'; |
|
|
import { forwardRef, useRef, useState, useCallback, useMemo, useImperativeHandle, useEffect, Suspense, useContext, createContext } from 'react'; |
|
|
import { useThree, useFrame } from '@react-three/fiber'; |
|
|
import { easing } from 'maath'; |
|
|
import { VideoTexture } from '../core/VideoTexture.js'; |
|
|
import { WebcamVideoTexture } from './WebcamVideoTexture.js'; |
|
|
import { Facemesh } from './Facemesh.js'; |
|
|
import { useFaceLandmarker } from './FaceLandmarker.js'; |
|
|
|
|
|
function mean(v1, v2) { |
|
|
return v1.clone().add(v2).multiplyScalar(0.5); |
|
|
} |
|
|
function localToLocal(objSrc, v, objDst) { |
|
|
|
|
|
const v_world = objSrc.localToWorld(v); |
|
|
return objDst.worldToLocal(v_world); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const FaceControlsContext = createContext({}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const FaceControls = forwardRef(({ |
|
|
camera, |
|
|
videoTexture = { |
|
|
start: true |
|
|
}, |
|
|
manualDetect = false, |
|
|
faceLandmarkerResult, |
|
|
manualUpdate = false, |
|
|
makeDefault, |
|
|
smoothTime = 0.25, |
|
|
offset = true, |
|
|
offsetScalar = 80, |
|
|
eyes = false, |
|
|
eyesAsOrigin = true, |
|
|
depth = 0.15, |
|
|
debug = false, |
|
|
facemesh |
|
|
}, fref) => { |
|
|
var _result$facialTransfo, _result$faceBlendshap; |
|
|
const scene = useThree(state => state.scene); |
|
|
const defaultCamera = useThree(state => state.camera); |
|
|
const set = useThree(state => state.set); |
|
|
const get = useThree(state => state.get); |
|
|
const explCamera = camera || defaultCamera; |
|
|
const facemeshApiRef = useRef(null); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [target] = useState(() => new THREE.Object3D()); |
|
|
const [irisRightDirPos] = useState(() => new THREE.Vector3()); |
|
|
const [irisLeftDirPos] = useState(() => new THREE.Vector3()); |
|
|
const [irisRightLookAt] = useState(() => new THREE.Vector3()); |
|
|
const [irisLeftLookAt] = useState(() => new THREE.Vector3()); |
|
|
const computeTarget = useCallback(() => { |
|
|
|
|
|
target.parent = explCamera.parent; |
|
|
const facemeshApi = facemeshApiRef.current; |
|
|
if (facemeshApi) { |
|
|
const { |
|
|
outerRef, |
|
|
eyeRightRef, |
|
|
eyeLeftRef |
|
|
} = facemeshApi; |
|
|
if (eyeRightRef.current && eyeLeftRef.current) { |
|
|
|
|
|
|
|
|
const { |
|
|
irisDirRef: irisRightDirRef |
|
|
} = eyeRightRef.current; |
|
|
const { |
|
|
irisDirRef: irisLeftDirRef |
|
|
} = eyeLeftRef.current; |
|
|
if (irisRightDirRef.current && irisLeftDirRef.current && outerRef.current) { |
|
|
|
|
|
|
|
|
|
|
|
irisRightDirPos.copy(localToLocal(irisRightDirRef.current, new THREE.Vector3(0, 0, 0), outerRef.current)); |
|
|
irisLeftDirPos.copy(localToLocal(irisLeftDirRef.current, new THREE.Vector3(0, 0, 0), outerRef.current)); |
|
|
target.position.copy(localToLocal(outerRef.current, mean(irisRightDirPos, irisLeftDirPos), explCamera.parent || scene)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
irisRightLookAt.copy(localToLocal(irisRightDirRef.current, new THREE.Vector3(0, 0, 1), outerRef.current)); |
|
|
irisLeftLookAt.copy(localToLocal(irisLeftDirRef.current, new THREE.Vector3(0, 0, 1), outerRef.current)); |
|
|
target.lookAt(outerRef.current.localToWorld(mean(irisRightLookAt, irisLeftLookAt))); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
if (outerRef.current) { |
|
|
target.position.copy(localToLocal(outerRef.current, new THREE.Vector3(0, 0, 0), explCamera.parent || scene)); |
|
|
target.lookAt(outerRef.current.localToWorld(new THREE.Vector3(0, 0, 1))); |
|
|
} |
|
|
} |
|
|
} |
|
|
return target; |
|
|
}, [explCamera, irisLeftDirPos, irisLeftLookAt, irisRightDirPos, irisRightLookAt, scene, target]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [current] = useState(() => new THREE.Object3D()); |
|
|
const update = useCallback(function (delta, target) { |
|
|
if (explCamera) { |
|
|
var _target; |
|
|
(_target = target) !== null && _target !== void 0 ? _target : target = computeTarget(); |
|
|
if (smoothTime > 0) { |
|
|
|
|
|
const eps = 1e-9; |
|
|
easing.damp3(current.position, target.position, smoothTime, delta, undefined, undefined, eps); |
|
|
easing.dampE(current.rotation, target.rotation, smoothTime, delta, undefined, undefined, eps); |
|
|
} else { |
|
|
|
|
|
current.position.copy(target.position); |
|
|
current.rotation.copy(target.rotation); |
|
|
} |
|
|
explCamera.position.copy(current.position); |
|
|
explCamera.rotation.copy(current.rotation); |
|
|
} |
|
|
}, [explCamera, computeTarget, smoothTime, current.position, current.rotation]); |
|
|
useFrame((_, delta) => { |
|
|
if (manualUpdate) return; |
|
|
update(delta); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const videoTextureRef = useRef(null); |
|
|
const [_faceLandmarkerResult, setFaceLandmarkerResult] = useState(); |
|
|
const faceLandmarker = useFaceLandmarker(); |
|
|
const onVideoFrame = useCallback((now, metadata) => { |
|
|
const texture = videoTextureRef.current; |
|
|
if (!texture) return; |
|
|
const videoFrame = texture.source.data; |
|
|
const result = faceLandmarker == null ? void 0 : faceLandmarker.detectForVideo(videoFrame, now); |
|
|
setFaceLandmarkerResult(result); |
|
|
}, [faceLandmarker]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const api = useMemo(() => Object.assign(Object.create(THREE.EventDispatcher.prototype), { |
|
|
computeTarget, |
|
|
update, |
|
|
facemeshApiRef |
|
|
}), [computeTarget, update]); |
|
|
useImperativeHandle(fref, () => api, [api]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (makeDefault) { |
|
|
const old = get().controls; |
|
|
set({ |
|
|
controls: api |
|
|
}); |
|
|
return () => set({ |
|
|
controls: old |
|
|
}); |
|
|
} |
|
|
}, [makeDefault, api, get, set]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const result = faceLandmarkerResult !== null && faceLandmarkerResult !== void 0 ? faceLandmarkerResult : _faceLandmarkerResult; |
|
|
const points = result == null ? void 0 : result.faceLandmarks[0]; |
|
|
const facialTransformationMatrix = result == null || (_result$facialTransfo = result.facialTransformationMatrixes) == null ? void 0 : _result$facialTransfo[0]; |
|
|
const faceBlendshapes = result == null || (_result$faceBlendshap = result.faceBlendshapes) == null ? void 0 : _result$faceBlendshap[0]; |
|
|
const videoTextureProps = { |
|
|
onVideoFrame, |
|
|
...videoTexture |
|
|
}; |
|
|
return React.createElement(FaceControlsContext.Provider, { |
|
|
value: api |
|
|
}, !manualDetect && React.createElement(Suspense, { |
|
|
fallback: null |
|
|
}, 'src' in videoTextureProps ? React.createElement(VideoTexture, _extends({ |
|
|
ref: videoTextureRef |
|
|
}, videoTextureProps)) : React.createElement(WebcamVideoTexture, _extends({ |
|
|
ref: videoTextureRef |
|
|
}, videoTextureProps))), React.createElement(Facemesh, _extends({ |
|
|
ref: facemeshApiRef, |
|
|
children: React.createElement("meshNormalMaterial", { |
|
|
side: THREE.DoubleSide |
|
|
}) |
|
|
}, facemesh, { |
|
|
points: points, |
|
|
depth: depth, |
|
|
facialTransformationMatrix: facialTransformationMatrix, |
|
|
faceBlendshapes: faceBlendshapes, |
|
|
eyes: eyes, |
|
|
eyesAsOrigin: eyesAsOrigin, |
|
|
offset: offset, |
|
|
offsetScalar: offsetScalar, |
|
|
debug: debug, |
|
|
"rotation-z": Math.PI, |
|
|
visible: debug |
|
|
}))); |
|
|
}); |
|
|
const useFaceControls = () => useContext(FaceControlsContext); |
|
|
|
|
|
export { FaceControls, useFaceControls }; |
|
|
|