@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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/UPSTREAM.md +92 -0
  4. package/package.json +96 -0
  5. package/src/config.ts +14 -0
  6. package/src/core/AccumulativeShadows.three.tsrx +313 -0
  7. package/src/core/AdaptivePerformance.three.tsrx +49 -0
  8. package/src/core/AsciiRenderer.three.tsrx +61 -0
  9. package/src/core/Bounds.three.tsrx +323 -0
  10. package/src/core/Bvh.three.tsrx +116 -0
  11. package/src/core/CameraControls.three.tsrx +181 -0
  12. package/src/core/CameraShake.three.tsrx +104 -0
  13. package/src/core/Caustics.three.tsrx +107 -0
  14. package/src/core/Center.three.tsrx +145 -0
  15. package/src/core/Clone.three.tsrx +167 -0
  16. package/src/core/Cloud.three.tsrx +314 -0
  17. package/src/core/ComputedAttribute.three.tsrx +27 -0
  18. package/src/core/ContactShadows.three.tsrx +106 -0
  19. package/src/core/CubeTexture.three.tsrx +32 -0
  20. package/src/core/CurveModifier.three.tsrx +37 -0
  21. package/src/core/Decal.three.tsrx +132 -0
  22. package/src/core/Detailed.three.tsrx +28 -0
  23. package/src/core/DetectGPU.three.tsrx +22 -0
  24. package/src/core/Effects.three.tsrx +123 -0
  25. package/src/core/Environment.three.tsrx +220 -0
  26. package/src/core/Example.three.tsrx +56 -0
  27. package/src/core/Fbo.three.tsrx +70 -0
  28. package/src/core/Fbx.three.tsrx +25 -0
  29. package/src/core/Fisheye.three.tsrx +82 -0
  30. package/src/core/GizmoHelper.three.tsrx +115 -0
  31. package/src/core/GizmoViewcube.three.tsrx +82 -0
  32. package/src/core/GizmoViewport.three.tsrx +122 -0
  33. package/src/core/Gltf.three.tsrx +62 -0
  34. package/src/core/GradientTexture.three.tsrx +66 -0
  35. package/src/core/Grid.three.tsrx +170 -0
  36. package/src/core/Helper.three.tsrx +56 -0
  37. package/src/core/Hud.three.tsrx +52 -0
  38. package/src/core/Image.three.tsrx +146 -0
  39. package/src/core/Instances.three.tsrx +332 -0
  40. package/src/core/Ktx2.three.tsrx +59 -0
  41. package/src/core/Lightformer.three.tsrx +75 -0
  42. package/src/core/MarchingCubes.three.tsrx +125 -0
  43. package/src/core/MeshPortalMaterial.three.tsrx +103 -0
  44. package/src/core/MeshReflectorMaterial.three.tsrx +67 -0
  45. package/src/core/MeshRefractionMaterial.three.tsrx +70 -0
  46. package/src/core/MeshTransmissionMaterial.three.tsrx +71 -0
  47. package/src/core/MotionPathControls.three.tsrx +139 -0
  48. package/src/core/MultiMaterial.three.tsrx +32 -0
  49. package/src/core/Outlines.three.tsrx +162 -0
  50. package/src/core/PerformanceMonitor.three.tsrx +144 -0
  51. package/src/core/PointerLockControls.three.tsrx +84 -0
  52. package/src/core/Points.three.tsrx +242 -0
  53. package/src/core/PositionalAudio.three.tsrx +76 -0
  54. package/src/core/Preload.three.tsrx +40 -0
  55. package/src/core/Progress.three.tsrx +47 -0
  56. package/src/core/RenderCubeTexture.three.tsrx +111 -0
  57. package/src/core/RenderTexture.three.tsrx +102 -0
  58. package/src/core/Resize.three.tsrx +59 -0
  59. package/src/core/RoundedBox.three.tsrx +106 -0
  60. package/src/core/Sampler.three.tsrx +116 -0
  61. package/src/core/ScreenSizer.three.tsrx +43 -0
  62. package/src/core/ScreenSpace.three.tsrx +21 -0
  63. package/src/core/Segments.three.tsrx +119 -0
  64. package/src/core/Sky.three.tsrx +63 -0
  65. package/src/core/Sparkles.three.tsrx +145 -0
  66. package/src/core/Splat.three.tsrx +52 -0
  67. package/src/core/SpotLight.three.tsrx +350 -0
  68. package/src/core/SpriteAnimator.three.tsrx +338 -0
  69. package/src/core/Stage.three.tsrx +112 -0
  70. package/src/core/Stars.three.tsrx +103 -0
  71. package/src/core/Stats.three.tsrx +35 -0
  72. package/src/core/StatsGl.three.tsrx +60 -0
  73. package/src/core/Svg.three.tsrx +82 -0
  74. package/src/core/Text.three.tsrx +96 -0
  75. package/src/core/Text3D.three.tsrx +90 -0
  76. package/src/core/Texture.three.tsrx +72 -0
  77. package/src/core/Trail.three.tsrx +167 -0
  78. package/src/core/TrailTexture.three.tsrx +171 -0
  79. package/src/core/TransformControls.three.tsrx +155 -0
  80. package/src/core/VideoTexture.three.tsrx +116 -0
  81. package/src/core/Wireframe.three.tsrx +92 -0
  82. package/src/core/basic-controls.three.tsrx +353 -0
  83. package/src/core/calculateScaleFactor.ts +42 -0
  84. package/src/core/cameras.three.tsrx +230 -0
  85. package/src/core/catalog-resource.ts +12 -0
  86. package/src/core/catalog-textures.three.tsrx +105 -0
  87. package/src/core/compiler-slot.ts +9 -0
  88. package/src/core/effects-utils.ts +8 -0
  89. package/src/core/environment-loader.ts +87 -0
  90. package/src/core/font-resource.ts +31 -0
  91. package/src/core/gltf-loader.ts +33 -0
  92. package/src/core/gradient-types.ts +4 -0
  93. package/src/core/index.ts +295 -0
  94. package/src/core/lines.three.tsrx +329 -0
  95. package/src/core/meshBounds.ts +26 -0
  96. package/src/core/motion-materials.three.tsrx +93 -0
  97. package/src/core/octane-children.ts +18 -0
  98. package/src/core/octane-element.ts +16 -0
  99. package/src/core/resource.ts +29 -0
  100. package/src/core/scene-motion.three.tsrx +183 -0
  101. package/src/core/shaderMaterial.ts +50 -0
  102. package/src/core/shadows.three.tsrx +164 -0
  103. package/src/core/shapes.three.tsrx +170 -0
  104. package/src/core/simple-materials.three.tsrx +124 -0
  105. package/src/core/softShadows.three.tsrx +151 -0
  106. package/src/core/splat-resource.ts +598 -0
  107. package/src/core/useAnimations.three.tsrx +57 -0
  108. package/src/core/useAspect.three.tsrx +10 -0
  109. package/src/core/useBoxProjectedEnv.three.tsrx +113 -0
  110. package/src/core/useCamera.three.tsrx +26 -0
  111. package/src/core/useContextBridge.three.tsrx +25 -0
  112. package/src/core/useDepthBuffer.three.tsrx +35 -0
  113. package/src/core/useEnvironment.three.tsrx +81 -0
  114. package/src/core/useFont.three.tsrx +12 -0
  115. package/src/core/useIntersect.three.tsrx +38 -0
  116. package/src/core/useSpriteLoader.three.tsrx +249 -0
  117. package/src/core/video-texture-resource.ts +14 -0
  118. package/src/index.ts +2 -0
  119. package/src/materials/BlurPass.ts +94 -0
  120. package/src/materials/CausticsMaterials.ts +197 -0
  121. package/src/materials/ConvolutionMaterial.ts +65 -0
  122. package/src/materials/MeshReflectorMaterial.ts +180 -0
  123. package/src/materials/MeshRefractionMaterial.ts +131 -0
  124. package/src/materials/MeshTransmissionMaterial.ts +182 -0
  125. package/src/materials/PortalMaterial.ts +38 -0
  126. package/src/materials/WireframeMaterial.ts +241 -0
  127. package/src/materials/makeSDFGenerator.ts +155 -0
  128. package/src/troika-three-text.d.ts +19 -0
  129. package/src/web/CycleRaycast.three.tsrx +81 -0
  130. package/src/web/DragControls.three.tsrx +82 -0
  131. package/src/web/FaceControls.three.tsrx +178 -0
  132. package/src/web/FaceLandmarker.three.tsrx +48 -0
  133. package/src/web/Facemesh.three.tsrx +464 -0
  134. package/src/web/Html.three.tsrx +390 -0
  135. package/src/web/KeyboardControls.three.tsrx +98 -0
  136. package/src/web/Loader.tsrx +61 -0
  137. package/src/web/PresentationControls.three.tsrx +63 -0
  138. package/src/web/ScrollControls.three.tsrx +120 -0
  139. package/src/web/Select.three.tsrx +155 -0
  140. package/src/web/View.three.tsrx +46 -0
  141. package/src/web/face-landmarker-resource.ts +18 -0
  142. package/src/web/facemesh-data.ts +54 -0
  143. package/src/web/index.ts +41 -0
  144. package/src/web/keyboard-controls-types.ts +33 -0
  145. package/src/web/media-textures.three.tsrx +50 -0
  146. package/src/web/pivotControls/annotation.tsrx +17 -0
  147. package/src/web/pivotControls/context.ts +38 -0
  148. package/src/web/pivotControls/index.three.tsrx +877 -0
  149. package/src/web/scroll-html-root.tsrx +27 -0
  150. package/src/web/useCursor.three.tsrx +19 -0
  151. package/src/web/useGesture.ts +27 -0
  152. 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
+ }