@octanejs/drei 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/UPSTREAM.md +92 -0
- package/package.json +96 -0
- package/src/config.ts +14 -0
- package/src/core/AccumulativeShadows.three.tsrx +313 -0
- package/src/core/AdaptivePerformance.three.tsrx +49 -0
- package/src/core/AsciiRenderer.three.tsrx +61 -0
- package/src/core/Bounds.three.tsrx +323 -0
- package/src/core/Bvh.three.tsrx +116 -0
- package/src/core/CameraControls.three.tsrx +181 -0
- package/src/core/CameraShake.three.tsrx +104 -0
- package/src/core/Caustics.three.tsrx +107 -0
- package/src/core/Center.three.tsrx +145 -0
- package/src/core/Clone.three.tsrx +167 -0
- package/src/core/Cloud.three.tsrx +314 -0
- package/src/core/ComputedAttribute.three.tsrx +27 -0
- package/src/core/ContactShadows.three.tsrx +106 -0
- package/src/core/CubeTexture.three.tsrx +32 -0
- package/src/core/CurveModifier.three.tsrx +37 -0
- package/src/core/Decal.three.tsrx +132 -0
- package/src/core/Detailed.three.tsrx +28 -0
- package/src/core/DetectGPU.three.tsrx +22 -0
- package/src/core/Effects.three.tsrx +123 -0
- package/src/core/Environment.three.tsrx +220 -0
- package/src/core/Example.three.tsrx +56 -0
- package/src/core/Fbo.three.tsrx +70 -0
- package/src/core/Fbx.three.tsrx +25 -0
- package/src/core/Fisheye.three.tsrx +82 -0
- package/src/core/GizmoHelper.three.tsrx +115 -0
- package/src/core/GizmoViewcube.three.tsrx +82 -0
- package/src/core/GizmoViewport.three.tsrx +122 -0
- package/src/core/Gltf.three.tsrx +62 -0
- package/src/core/GradientTexture.three.tsrx +66 -0
- package/src/core/Grid.three.tsrx +170 -0
- package/src/core/Helper.three.tsrx +56 -0
- package/src/core/Hud.three.tsrx +52 -0
- package/src/core/Image.three.tsrx +146 -0
- package/src/core/Instances.three.tsrx +332 -0
- package/src/core/Ktx2.three.tsrx +59 -0
- package/src/core/Lightformer.three.tsrx +75 -0
- package/src/core/MarchingCubes.three.tsrx +125 -0
- package/src/core/MeshPortalMaterial.three.tsrx +103 -0
- package/src/core/MeshReflectorMaterial.three.tsrx +67 -0
- package/src/core/MeshRefractionMaterial.three.tsrx +70 -0
- package/src/core/MeshTransmissionMaterial.three.tsrx +71 -0
- package/src/core/MotionPathControls.three.tsrx +139 -0
- package/src/core/MultiMaterial.three.tsrx +32 -0
- package/src/core/Outlines.three.tsrx +162 -0
- package/src/core/PerformanceMonitor.three.tsrx +144 -0
- package/src/core/PointerLockControls.three.tsrx +84 -0
- package/src/core/Points.three.tsrx +242 -0
- package/src/core/PositionalAudio.three.tsrx +76 -0
- package/src/core/Preload.three.tsrx +40 -0
- package/src/core/Progress.three.tsrx +47 -0
- package/src/core/RenderCubeTexture.three.tsrx +111 -0
- package/src/core/RenderTexture.three.tsrx +102 -0
- package/src/core/Resize.three.tsrx +59 -0
- package/src/core/RoundedBox.three.tsrx +106 -0
- package/src/core/Sampler.three.tsrx +116 -0
- package/src/core/ScreenSizer.three.tsrx +43 -0
- package/src/core/ScreenSpace.three.tsrx +21 -0
- package/src/core/Segments.three.tsrx +119 -0
- package/src/core/Sky.three.tsrx +63 -0
- package/src/core/Sparkles.three.tsrx +145 -0
- package/src/core/Splat.three.tsrx +52 -0
- package/src/core/SpotLight.three.tsrx +350 -0
- package/src/core/SpriteAnimator.three.tsrx +338 -0
- package/src/core/Stage.three.tsrx +112 -0
- package/src/core/Stars.three.tsrx +103 -0
- package/src/core/Stats.three.tsrx +35 -0
- package/src/core/StatsGl.three.tsrx +60 -0
- package/src/core/Svg.three.tsrx +82 -0
- package/src/core/Text.three.tsrx +96 -0
- package/src/core/Text3D.three.tsrx +90 -0
- package/src/core/Texture.three.tsrx +72 -0
- package/src/core/Trail.three.tsrx +167 -0
- package/src/core/TrailTexture.three.tsrx +171 -0
- package/src/core/TransformControls.three.tsrx +155 -0
- package/src/core/VideoTexture.three.tsrx +116 -0
- package/src/core/Wireframe.three.tsrx +92 -0
- package/src/core/basic-controls.three.tsrx +353 -0
- package/src/core/calculateScaleFactor.ts +42 -0
- package/src/core/cameras.three.tsrx +230 -0
- package/src/core/catalog-resource.ts +12 -0
- package/src/core/catalog-textures.three.tsrx +105 -0
- package/src/core/compiler-slot.ts +9 -0
- package/src/core/effects-utils.ts +8 -0
- package/src/core/environment-loader.ts +87 -0
- package/src/core/font-resource.ts +31 -0
- package/src/core/gltf-loader.ts +33 -0
- package/src/core/gradient-types.ts +4 -0
- package/src/core/index.ts +295 -0
- package/src/core/lines.three.tsrx +329 -0
- package/src/core/meshBounds.ts +26 -0
- package/src/core/motion-materials.three.tsrx +93 -0
- package/src/core/octane-children.ts +18 -0
- package/src/core/octane-element.ts +16 -0
- package/src/core/resource.ts +29 -0
- package/src/core/scene-motion.three.tsrx +183 -0
- package/src/core/shaderMaterial.ts +50 -0
- package/src/core/shadows.three.tsrx +164 -0
- package/src/core/shapes.three.tsrx +170 -0
- package/src/core/simple-materials.three.tsrx +124 -0
- package/src/core/softShadows.three.tsrx +151 -0
- package/src/core/splat-resource.ts +598 -0
- package/src/core/useAnimations.three.tsrx +57 -0
- package/src/core/useAspect.three.tsrx +10 -0
- package/src/core/useBoxProjectedEnv.three.tsrx +113 -0
- package/src/core/useCamera.three.tsrx +26 -0
- package/src/core/useContextBridge.three.tsrx +25 -0
- package/src/core/useDepthBuffer.three.tsrx +35 -0
- package/src/core/useEnvironment.three.tsrx +81 -0
- package/src/core/useFont.three.tsrx +12 -0
- package/src/core/useIntersect.three.tsrx +38 -0
- package/src/core/useSpriteLoader.three.tsrx +249 -0
- package/src/core/video-texture-resource.ts +14 -0
- package/src/index.ts +2 -0
- package/src/materials/BlurPass.ts +94 -0
- package/src/materials/CausticsMaterials.ts +197 -0
- package/src/materials/ConvolutionMaterial.ts +65 -0
- package/src/materials/MeshReflectorMaterial.ts +180 -0
- package/src/materials/MeshRefractionMaterial.ts +131 -0
- package/src/materials/MeshTransmissionMaterial.ts +182 -0
- package/src/materials/PortalMaterial.ts +38 -0
- package/src/materials/WireframeMaterial.ts +241 -0
- package/src/materials/makeSDFGenerator.ts +155 -0
- package/src/troika-three-text.d.ts +19 -0
- package/src/web/CycleRaycast.three.tsrx +81 -0
- package/src/web/DragControls.three.tsrx +82 -0
- package/src/web/FaceControls.three.tsrx +178 -0
- package/src/web/FaceLandmarker.three.tsrx +48 -0
- package/src/web/Facemesh.three.tsrx +464 -0
- package/src/web/Html.three.tsrx +390 -0
- package/src/web/KeyboardControls.three.tsrx +98 -0
- package/src/web/Loader.tsrx +61 -0
- package/src/web/PresentationControls.three.tsrx +63 -0
- package/src/web/ScrollControls.three.tsrx +120 -0
- package/src/web/Select.three.tsrx +155 -0
- package/src/web/View.three.tsrx +46 -0
- package/src/web/face-landmarker-resource.ts +18 -0
- package/src/web/facemesh-data.ts +54 -0
- package/src/web/index.ts +41 -0
- package/src/web/keyboard-controls-types.ts +33 -0
- package/src/web/media-textures.three.tsrx +50 -0
- package/src/web/pivotControls/annotation.tsrx +17 -0
- package/src/web/pivotControls/context.ts +38 -0
- package/src/web/pivotControls/index.three.tsrx +877 -0
- package/src/web/scroll-html-root.tsrx +27 -0
- package/src/web/useCursor.three.tsrx +19 -0
- package/src/web/useGesture.ts +27 -0
- package/status.json +14 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { type ThreeElements, type ThreeRef, useThree } from '@octanejs/three';
|
|
4
|
+
import {
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
type RefObject,
|
|
12
|
+
} from '@octanejs/three/renderer';
|
|
13
|
+
|
|
14
|
+
import { Line } from '../core/lines.three.tsrx';
|
|
15
|
+
import { FacemeshDatas } from './facemesh-data.js';
|
|
16
|
+
|
|
17
|
+
export { FacemeshDatas } from './facemesh-data.js';
|
|
18
|
+
|
|
19
|
+
export type MediaPipeFaceMesh = typeof FacemeshDatas.SAMPLE_FACE;
|
|
20
|
+
|
|
21
|
+
export type MediaPipePoints =
|
|
22
|
+
| typeof FacemeshDatas.SAMPLE_FACE.keypoints
|
|
23
|
+
| (typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.faceLandmarks)[0];
|
|
24
|
+
|
|
25
|
+
export type FacemeshProps =
|
|
26
|
+
{
|
|
27
|
+
/** an array of 468+ keypoints as returned by google/mediapipe tasks-vision, default: a sample face */
|
|
28
|
+
points?: MediaPipePoints;
|
|
29
|
+
/** @deprecated an face object as returned by tensorflow/tfjs-models face-landmarks-detection */
|
|
30
|
+
face?: MediaPipeFaceMesh;
|
|
31
|
+
/** constant width of the mesh, default: undefined */
|
|
32
|
+
width?: number;
|
|
33
|
+
/** or constant height of the mesh, default: undefined */
|
|
34
|
+
height?: number;
|
|
35
|
+
/** or constant depth of the mesh, default: 1 */
|
|
36
|
+
depth?: number;
|
|
37
|
+
/** a landmarks tri supposed to be vertical, default: [159, 386, 200] (see: https://github.com/tensorflow/tfjs-models/tree/master/face-landmarks-detection#mediapipe-facemesh-keypoints) */
|
|
38
|
+
verticalTri?: [number, number, number];
|
|
39
|
+
/** a landmark index (to get the position from) or a vec3 to be the origin of the mesh. default: undefined (ie. the bbox center) */
|
|
40
|
+
origin?: number | THREE.Vector3;
|
|
41
|
+
/** A facial transformation matrix, as returned by FaceLandmarkerResult.facialTransformationMatrixes (see: https://developers.google.com/mediapipe/solutions/vision/face_landmarker/web_js#handle_and_display_results) */
|
|
42
|
+
facialTransformationMatrix?: (typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.facialTransformationMatrixes)[0];
|
|
43
|
+
/** Apply position offset extracted from `facialTransformationMatrix` */
|
|
44
|
+
offset?: boolean;
|
|
45
|
+
/** Offset sensitivity factor, less is more sensible */
|
|
46
|
+
offsetScalar?: number;
|
|
47
|
+
/** Fface blendshapes, as returned by FaceLandmarkerResult.faceBlendshapes (see: https://developers.google.com/mediapipe/solutions/vision/face_landmarker/web_js#handle_and_display_results) */
|
|
48
|
+
faceBlendshapes?: (typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.faceBlendshapes)[0];
|
|
49
|
+
/** whether to enable eyes (nb. `faceBlendshapes` is required for), default: true */
|
|
50
|
+
eyes?: boolean;
|
|
51
|
+
/** Force `origin` to be the middle of the 2 eyes (nb. `eyes` is required for), default: false */
|
|
52
|
+
eyesAsOrigin?: boolean;
|
|
53
|
+
/** debug mode, default: false */
|
|
54
|
+
debug?: boolean;
|
|
55
|
+
} & Omit<ThreeElements['group'], 'ref'>;
|
|
56
|
+
|
|
57
|
+
export type FacemeshApi = {
|
|
58
|
+
meshRef: RefObject<THREE.Mesh | null>;
|
|
59
|
+
outerRef: RefObject<THREE.Group | null>;
|
|
60
|
+
eyeRightRef: RefObject<FacemeshEyeApi | null>;
|
|
61
|
+
eyeLeftRef: RefObject<FacemeshEyeApi | null>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const defaultLookAt = /* @__PURE__ */ new THREE.Vector3(0, 0, -1);
|
|
65
|
+
|
|
66
|
+
const normal = /* @__PURE__ */ (function () {
|
|
67
|
+
const a = new THREE.Vector3();
|
|
68
|
+
const b = new THREE.Vector3();
|
|
69
|
+
const c = new THREE.Vector3();
|
|
70
|
+
const ab = new THREE.Vector3();
|
|
71
|
+
const ac = new THREE.Vector3();
|
|
72
|
+
|
|
73
|
+
return function (
|
|
74
|
+
v1: THREE.Vector3,
|
|
75
|
+
v2: THREE.Vector3,
|
|
76
|
+
v3: THREE.Vector3,
|
|
77
|
+
v: THREE.Vector3, // result
|
|
78
|
+
) {
|
|
79
|
+
a.copy(v1);
|
|
80
|
+
b.copy(v2);
|
|
81
|
+
c.copy(v3);
|
|
82
|
+
|
|
83
|
+
ab.copy(b).sub(a);
|
|
84
|
+
ac.copy(c).sub(a);
|
|
85
|
+
|
|
86
|
+
return v.crossVectors(ac, ab).normalize();
|
|
87
|
+
};
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
function mean(v1: THREE.Vector3, v2: THREE.Vector3) {
|
|
91
|
+
return v1.clone().add(v2).multiplyScalar(0.5);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function Facemesh({
|
|
95
|
+
points = FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.faceLandmarks[0],
|
|
96
|
+
face,
|
|
97
|
+
facialTransformationMatrix,
|
|
98
|
+
faceBlendshapes,
|
|
99
|
+
offset,
|
|
100
|
+
offsetScalar = 80,
|
|
101
|
+
width,
|
|
102
|
+
height,
|
|
103
|
+
depth = 1,
|
|
104
|
+
verticalTri = [159, 386, 152],
|
|
105
|
+
origin,
|
|
106
|
+
eyes = true,
|
|
107
|
+
eyesAsOrigin = false,
|
|
108
|
+
debug = false,
|
|
109
|
+
children,
|
|
110
|
+
ref,
|
|
111
|
+
...props
|
|
112
|
+
}: FacemeshProps & { ref?: ThreeRef<FacemeshApi> }) @{
|
|
113
|
+
if (face) {
|
|
114
|
+
points = face.keypoints;
|
|
115
|
+
console.warn('Facemesh `face` prop is deprecated: use `points` instead');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const offsetRef = useRef<THREE.Group | null>(null);
|
|
119
|
+
const scaleRef = useRef<THREE.Group | null>(null);
|
|
120
|
+
const originRef = useRef<THREE.Group | null>(null);
|
|
121
|
+
const outerRef = useRef<THREE.Group | null>(null);
|
|
122
|
+
const meshRef = useRef<THREE.Mesh | null>(null);
|
|
123
|
+
const eyeRightRef = useRef<FacemeshEyeApi | null>(null);
|
|
124
|
+
const eyeLeftRef = useRef<FacemeshEyeApi | null>(null);
|
|
125
|
+
|
|
126
|
+
const [sightDir] = useState(() => new THREE.Vector3());
|
|
127
|
+
const [transform] = useState(() => new THREE.Object3D());
|
|
128
|
+
const [sightDirQuaternion] = useState(() => new THREE.Quaternion());
|
|
129
|
+
const [_origin] = useState(() => new THREE.Vector3());
|
|
130
|
+
|
|
131
|
+
const { invalidate } = useThree();
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
meshRef.current?.geometry.setIndex(FacemeshDatas.TRIANGULATION);
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
const [bboxSize] = useState(() => new THREE.Vector3());
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const faceGeometry = meshRef.current?.geometry;
|
|
141
|
+
if (!faceGeometry) return;
|
|
142
|
+
|
|
143
|
+
faceGeometry.setFromPoints(points as THREE.Vector3[]);
|
|
144
|
+
faceGeometry.setDrawRange(0, FacemeshDatas.TRIANGULATION.length);
|
|
145
|
+
|
|
146
|
+
//
|
|
147
|
+
// A. compute sightDir vector
|
|
148
|
+
//
|
|
149
|
+
// - either from `facialTransformationMatrix` if available
|
|
150
|
+
// - or from `verticalTri`
|
|
151
|
+
//
|
|
152
|
+
|
|
153
|
+
if (facialTransformationMatrix) {
|
|
154
|
+
// from facialTransformationMatrix
|
|
155
|
+
transform.matrix.fromArray(facialTransformationMatrix.data);
|
|
156
|
+
transform.matrix.decompose(transform.position, transform.quaternion, transform.scale);
|
|
157
|
+
|
|
158
|
+
// Rotation: y and z axes are inverted
|
|
159
|
+
transform.rotation.y *= -1;
|
|
160
|
+
transform.rotation.z *= -1;
|
|
161
|
+
sightDirQuaternion.setFromEuler(transform.rotation);
|
|
162
|
+
|
|
163
|
+
// Offset: y and z axes are inverted
|
|
164
|
+
if (offset) {
|
|
165
|
+
transform.position.y *= -1;
|
|
166
|
+
transform.position.z *= -1;
|
|
167
|
+
offsetRef.current?.position.copy(transform.position.divideScalar(offsetScalar));
|
|
168
|
+
} else {
|
|
169
|
+
offsetRef.current?.position.set(0, 0, 0); // reset
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// normal to verticalTri
|
|
173
|
+
normal(
|
|
174
|
+
points[verticalTri[0]] as THREE.Vector3,
|
|
175
|
+
points[verticalTri[1]] as THREE.Vector3,
|
|
176
|
+
points[verticalTri[2]] as THREE.Vector3,
|
|
177
|
+
sightDir,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
sightDirQuaternion.setFromUnitVectors(defaultLookAt, sightDir);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const sightDirQuaternionInverse = sightDirQuaternion.clone().invert();
|
|
184
|
+
|
|
185
|
+
//
|
|
186
|
+
// B. geometry (straightened)
|
|
187
|
+
//
|
|
188
|
+
|
|
189
|
+
// 1. center (before rotate back)
|
|
190
|
+
faceGeometry.computeBoundingBox();
|
|
191
|
+
if (debug) invalidate(); // invalidate to force re-render for box3Helper (after .computeBoundingBox())
|
|
192
|
+
faceGeometry.center();
|
|
193
|
+
|
|
194
|
+
// 2. rotate back + rotate outerRef (once 1.)
|
|
195
|
+
faceGeometry.applyQuaternion(sightDirQuaternionInverse);
|
|
196
|
+
outerRef.current?.setRotationFromQuaternion(sightDirQuaternion);
|
|
197
|
+
|
|
198
|
+
// 3. 👀 eyes
|
|
199
|
+
if (eyes) {
|
|
200
|
+
if (!faceBlendshapes) {
|
|
201
|
+
console.warn(
|
|
202
|
+
'Facemesh `eyes` option only works if `faceBlendshapes` is provided: skipping.',
|
|
203
|
+
);
|
|
204
|
+
} else {
|
|
205
|
+
if (eyeRightRef.current && eyeLeftRef.current && originRef.current) {
|
|
206
|
+
if (eyesAsOrigin) {
|
|
207
|
+
// compute the middle of the 2 eyes as the `origin`
|
|
208
|
+
const eyeRightSphere = eyeRightRef.current._computeSphere(faceGeometry);
|
|
209
|
+
const eyeLeftSphere = eyeLeftRef.current._computeSphere(faceGeometry);
|
|
210
|
+
const eyesCenter = mean(eyeRightSphere.center, eyeLeftSphere.center);
|
|
211
|
+
origin = eyesCenter.negate(); // eslint-disable-line react-hooks/exhaustive-deps
|
|
212
|
+
|
|
213
|
+
eyeRightRef.current._update(faceGeometry, faceBlendshapes, eyeRightSphere);
|
|
214
|
+
eyeLeftRef.current._update(faceGeometry, faceBlendshapes, eyeLeftSphere);
|
|
215
|
+
} else {
|
|
216
|
+
eyeRightRef.current._update(faceGeometry, faceBlendshapes);
|
|
217
|
+
eyeLeftRef.current._update(faceGeometry, faceBlendshapes);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 3. origin
|
|
224
|
+
if (originRef.current) {
|
|
225
|
+
if (origin !== undefined) {
|
|
226
|
+
if (typeof origin === 'number') {
|
|
227
|
+
const position = faceGeometry.getAttribute('position') as THREE.BufferAttribute;
|
|
228
|
+
_origin.set(-position.getX(origin), -position.getY(origin), -position.getZ(origin));
|
|
229
|
+
} else if (origin.isVector3) {
|
|
230
|
+
_origin.copy(origin);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
_origin.setScalar(0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
originRef.current.position.copy(_origin);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 4. re-scale
|
|
240
|
+
if (scaleRef.current) {
|
|
241
|
+
let scale = 1;
|
|
242
|
+
if (width || height || depth) {
|
|
243
|
+
faceGeometry.boundingBox!.getSize(bboxSize);
|
|
244
|
+
if (width) scale = width / bboxSize.x; // fit in width
|
|
245
|
+
if (height) scale = height / bboxSize.y; // fit in height
|
|
246
|
+
if (depth) scale = depth / bboxSize.z; // fit in depth
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
scaleRef.current.scale.setScalar(scale !== 1 ? scale : 1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
faceGeometry.computeVertexNormals();
|
|
253
|
+
faceGeometry.attributes.position.needsUpdate = true;
|
|
254
|
+
}, [
|
|
255
|
+
points,
|
|
256
|
+
facialTransformationMatrix,
|
|
257
|
+
faceBlendshapes,
|
|
258
|
+
transform,
|
|
259
|
+
offset,
|
|
260
|
+
offsetScalar,
|
|
261
|
+
width,
|
|
262
|
+
height,
|
|
263
|
+
depth,
|
|
264
|
+
verticalTri,
|
|
265
|
+
origin,
|
|
266
|
+
eyes,
|
|
267
|
+
debug,
|
|
268
|
+
invalidate,
|
|
269
|
+
sightDir,
|
|
270
|
+
sightDirQuaternion,
|
|
271
|
+
bboxSize,
|
|
272
|
+
_origin,
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
//
|
|
276
|
+
// API
|
|
277
|
+
//
|
|
278
|
+
|
|
279
|
+
const api = useMemo<FacemeshApi>(
|
|
280
|
+
() => ({ outerRef, meshRef, eyeRightRef, eyeLeftRef }),
|
|
281
|
+
[],
|
|
282
|
+
);
|
|
283
|
+
useImperativeHandle((ref ?? null) as any, () => api, [api]);
|
|
284
|
+
|
|
285
|
+
const [meshBboxSize] = useState(() => new THREE.Vector3());
|
|
286
|
+
const bbox = meshRef.current?.geometry.boundingBox;
|
|
287
|
+
const one = bbox?.getSize(meshBboxSize).z || 1;
|
|
288
|
+
<group {...props}>
|
|
289
|
+
<group ref={offsetRef}>
|
|
290
|
+
<group ref={outerRef}>
|
|
291
|
+
<group ref={scaleRef}>
|
|
292
|
+
{debug
|
|
293
|
+
? <>
|
|
294
|
+
<axesHelper args={[one]} />
|
|
295
|
+
<Line
|
|
296
|
+
points={[
|
|
297
|
+
[0, 0, 0],
|
|
298
|
+
[0, 0, -one],
|
|
299
|
+
]}
|
|
300
|
+
color={0x00ffff}
|
|
301
|
+
/>
|
|
302
|
+
</>
|
|
303
|
+
: null}
|
|
304
|
+
|
|
305
|
+
<group ref={originRef}>
|
|
306
|
+
{eyes && faceBlendshapes &&
|
|
307
|
+
<group name="eyes">
|
|
308
|
+
<FacemeshEye side="left" ref={eyeRightRef} debug={debug} />
|
|
309
|
+
<FacemeshEye side="right" ref={eyeLeftRef} debug={debug} />
|
|
310
|
+
</group>}
|
|
311
|
+
<mesh ref={meshRef} name="face">
|
|
312
|
+
{children}
|
|
313
|
+
|
|
314
|
+
{debug
|
|
315
|
+
? <>
|
|
316
|
+
{bbox && <box3Helper args={[bbox]} />}
|
|
317
|
+
</>
|
|
318
|
+
: null}
|
|
319
|
+
</mesh>
|
|
320
|
+
</group>
|
|
321
|
+
</group>
|
|
322
|
+
</group>
|
|
323
|
+
</group>
|
|
324
|
+
</group>
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//
|
|
328
|
+
// 👁️ FacemeshEye
|
|
329
|
+
//
|
|
330
|
+
|
|
331
|
+
export type FacemeshEyeProps = {
|
|
332
|
+
side: 'left' | 'right';
|
|
333
|
+
debug?: boolean;
|
|
334
|
+
};
|
|
335
|
+
export type FacemeshEyeApi = {
|
|
336
|
+
eyeMeshRef: RefObject<THREE.Group | null>;
|
|
337
|
+
irisDirRef: RefObject<THREE.Group | null>;
|
|
338
|
+
_computeSphere: (faceGeometry: THREE.BufferGeometry) => THREE.Sphere;
|
|
339
|
+
_update: (faceGeometry: THREE.BufferGeometry, faceBlendshapes: FacemeshProps['faceBlendshapes'], sphere?: THREE.Sphere) => void;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export const FacemeshEyeDefaults = {
|
|
343
|
+
contourLandmarks: {
|
|
344
|
+
right: [33, 133, 159, 145, 153],
|
|
345
|
+
left: [263, 362, 386, 374, 380],
|
|
346
|
+
},
|
|
347
|
+
blendshapes: {
|
|
348
|
+
right: [14, 16, 18, 12], // lookIn,lookOut, lookUp,lookDown
|
|
349
|
+
left: [13, 15, 17, 11], // lookIn,lookOut, lookUp,lookDown
|
|
350
|
+
},
|
|
351
|
+
color: {
|
|
352
|
+
right: 'red',
|
|
353
|
+
left: '#00ff00',
|
|
354
|
+
},
|
|
355
|
+
fov: {
|
|
356
|
+
horizontal: 100,
|
|
357
|
+
vertical: 90,
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export function FacemeshEye({ side, debug = true, ref }: FacemeshEyeProps & {
|
|
362
|
+
ref?: ThreeRef<FacemeshEyeApi>;
|
|
363
|
+
}) @{
|
|
364
|
+
const eyeMeshRef = useRef<THREE.Group | null>(null);
|
|
365
|
+
const irisDirRef = useRef<THREE.Group | null>(null);
|
|
366
|
+
|
|
367
|
+
//
|
|
368
|
+
// _computeSphere()
|
|
369
|
+
//
|
|
370
|
+
// Compute eye's sphere .position and .radius
|
|
371
|
+
//
|
|
372
|
+
|
|
373
|
+
const [sphere] = useState(() => new THREE.Sphere());
|
|
374
|
+
const _computeSphere = useCallback<FacemeshEyeApi['_computeSphere']>((faceGeometry) => {
|
|
375
|
+
const position = faceGeometry.getAttribute('position') as THREE.BufferAttribute;
|
|
376
|
+
|
|
377
|
+
// get some eye contour landmarks points (from geometry)
|
|
378
|
+
const eyeContourLandmarks = FacemeshEyeDefaults.contourLandmarks[side];
|
|
379
|
+
const eyeContourPoints = eyeContourLandmarks.map(
|
|
380
|
+
(i) => new THREE.Vector3(position.getX(i), position.getY(i), position.getZ(i)),
|
|
381
|
+
); // prettier-ignore
|
|
382
|
+
|
|
383
|
+
// compute center (centroid from eyeContourPoints)
|
|
384
|
+
sphere.center.set(0, 0, 0);
|
|
385
|
+
eyeContourPoints.forEach((v) => sphere.center.add(v));
|
|
386
|
+
sphere.center.divideScalar(eyeContourPoints.length);
|
|
387
|
+
|
|
388
|
+
// radius (eye half-width)
|
|
389
|
+
sphere.radius = eyeContourPoints[0].sub(eyeContourPoints[1]).length() / 2;
|
|
390
|
+
|
|
391
|
+
return sphere;
|
|
392
|
+
}, [sphere, side]);
|
|
393
|
+
|
|
394
|
+
//
|
|
395
|
+
// _update()
|
|
396
|
+
//
|
|
397
|
+
// Update:
|
|
398
|
+
// - A. eye's mesh (according to sphere)
|
|
399
|
+
// - B. iris direction (according to "look*" blendshapes)
|
|
400
|
+
//
|
|
401
|
+
|
|
402
|
+
const [rotation] = useState(() => new THREE.Euler());
|
|
403
|
+
const _update = useCallback<FacemeshEyeApi['_update']>((
|
|
404
|
+
faceGeometry,
|
|
405
|
+
faceBlendshapes,
|
|
406
|
+
sphere,
|
|
407
|
+
) => {
|
|
408
|
+
// A.
|
|
409
|
+
if (eyeMeshRef.current) {
|
|
410
|
+
sphere ??= _computeSphere(faceGeometry); // compute sphere dims (if not passed)
|
|
411
|
+
eyeMeshRef.current.position.copy(sphere.center);
|
|
412
|
+
eyeMeshRef.current.scale.setScalar(sphere.radius);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// B.
|
|
416
|
+
if (faceBlendshapes && irisDirRef.current) {
|
|
417
|
+
const blendshapes = FacemeshEyeDefaults.blendshapes[side];
|
|
418
|
+
|
|
419
|
+
const lookIn = faceBlendshapes.categories[blendshapes[0]].score;
|
|
420
|
+
const lookOut = faceBlendshapes.categories[blendshapes[1]].score;
|
|
421
|
+
const lookUp = faceBlendshapes.categories[blendshapes[2]].score;
|
|
422
|
+
const lookDown = faceBlendshapes.categories[blendshapes[3]].score;
|
|
423
|
+
|
|
424
|
+
const hfov = FacemeshEyeDefaults.fov.horizontal * THREE.MathUtils.DEG2RAD;
|
|
425
|
+
const vfov = FacemeshEyeDefaults.fov.vertical * THREE.MathUtils.DEG2RAD;
|
|
426
|
+
const rx = hfov * 0.5 * (lookDown - lookUp);
|
|
427
|
+
const ry = vfov * 0.5 * (lookIn - lookOut) * (side === 'left' ? 1 : -1);
|
|
428
|
+
rotation.set(rx, ry, 0);
|
|
429
|
+
|
|
430
|
+
irisDirRef.current.setRotationFromEuler(rotation);
|
|
431
|
+
}
|
|
432
|
+
}, [_computeSphere, side, rotation]);
|
|
433
|
+
|
|
434
|
+
//
|
|
435
|
+
// API
|
|
436
|
+
//
|
|
437
|
+
|
|
438
|
+
const api = useMemo<FacemeshEyeApi>(
|
|
439
|
+
() => ({ eyeMeshRef, irisDirRef, _computeSphere, _update }),
|
|
440
|
+
[_computeSphere, _update],
|
|
441
|
+
);
|
|
442
|
+
useImperativeHandle((ref ?? null) as any, () => api, [api]);
|
|
443
|
+
|
|
444
|
+
const color = FacemeshEyeDefaults.color[side];
|
|
445
|
+
<group>
|
|
446
|
+
<group ref={eyeMeshRef}>
|
|
447
|
+
{debug && <axesHelper />}
|
|
448
|
+
|
|
449
|
+
<group ref={irisDirRef}>
|
|
450
|
+
<>
|
|
451
|
+
{debug &&
|
|
452
|
+
<Line
|
|
453
|
+
points={[
|
|
454
|
+
[0, 0, 0],
|
|
455
|
+
[0, 0, -2],
|
|
456
|
+
]}
|
|
457
|
+
lineWidth={1}
|
|
458
|
+
color={color}
|
|
459
|
+
/>}
|
|
460
|
+
</>
|
|
461
|
+
</group>
|
|
462
|
+
</group>
|
|
463
|
+
</group>
|
|
464
|
+
}
|