@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,81 @@
1
+ import { type RefObject, useThree } from '@octanejs/three';
2
+ import { useEffect, useRef } from '@octanejs/three/renderer';
3
+ import * as THREE from 'three';
4
+
5
+ type CycleRaycastProps = {
6
+ onChanged?: (hits: THREE.Intersection[], cycle: number) => null;
7
+ preventDefault?: boolean;
8
+ scroll?: boolean;
9
+ keyCode?: number;
10
+ portal?: RefObject<HTMLElement>;
11
+ };
12
+
13
+ export function CycleRaycast({
14
+ onChanged,
15
+ portal,
16
+ preventDefault = true,
17
+ scroll = true,
18
+ keyCode = 9,
19
+ }: CycleRaycastProps) @{
20
+ const cycle = useRef(0);
21
+ const setEvents = useThree((state) => state.setEvents);
22
+ const get = useThree((state) => state.get);
23
+ const renderer = useThree((state) => state.gl);
24
+ useEffect(() => {
25
+ let hits: THREE.Intersection[] = [];
26
+ let lastEvent: PointerEvent = undefined!;
27
+ const previous = get().events.filter;
28
+ const target = portal?.current ?? (renderer.domElement as Node).parentNode;
29
+ const renderStatus = () =>
30
+ target && onChanged && onChanged(hits, Math.round(cycle.current) % hits.length);
31
+ setEvents({
32
+ filter: (intersections, state) => {
33
+ let clone = [...intersections];
34
+ if (
35
+ clone.length !== hits.length ||
36
+ !hits.every((hit) => clone.map((entry) => entry.object.uuid).includes(hit.object.uuid))
37
+ ) {
38
+ cycle.current = 0;
39
+ hits = clone;
40
+ renderStatus();
41
+ }
42
+ if (previous) clone = previous(clone, state);
43
+ for (let index = 0; index < Math.round(cycle.current) % clone.length; index++) {
44
+ const first = clone.shift() as THREE.Intersection;
45
+ clone = [...clone, first];
46
+ }
47
+ return clone;
48
+ },
49
+ });
50
+ const refresh = (update: (current: number) => number) => {
51
+ cycle.current = update(cycle.current);
52
+ get().events.handlers?.onPointerCancel(undefined as any);
53
+ get().events.handlers?.onPointerMove(lastEvent);
54
+ renderStatus();
55
+ };
56
+ const tabEvent = (event: KeyboardEvent) => {
57
+ if ((event.keyCode || event.which) === keyCode) {
58
+ if (preventDefault) event.preventDefault();
59
+ if (hits.length > 1) refresh((current) => current + 1);
60
+ }
61
+ };
62
+ const wheelEvent = (event: WheelEvent) => {
63
+ if (preventDefault) event.preventDefault();
64
+ let delta = 0;
65
+ if ((event as any).wheelDelta) delta = (event as any).wheelDelta / 120;
66
+ else if (event.detail) delta = -event.detail / 3;
67
+ if (hits.length > 1) refresh((current) => Math.abs(current - delta));
68
+ };
69
+ const moveEvent = (event: PointerEvent) => (lastEvent = event);
70
+ document.addEventListener('pointermove', moveEvent, { passive: true });
71
+ if (scroll) document.addEventListener('wheel', wheelEvent);
72
+ if (keyCode !== undefined) document.addEventListener('keydown', tabEvent);
73
+ return () => {
74
+ setEvents({ filter: previous });
75
+ if (keyCode !== undefined) document.removeEventListener('keydown', tabEvent);
76
+ if (scroll) document.removeEventListener('wheel', wheelEvent);
77
+ document.removeEventListener('pointermove', moveEvent);
78
+ };
79
+ }, [renderer, get, setEvents, preventDefault, scroll, keyCode]);
80
+ null;
81
+ }
@@ -0,0 +1,82 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements, type ThreeRef, useThree } from '@octanejs/three';
3
+ import { useImperativeHandle, useLayoutEffect, useRef } from '@octanejs/three/renderer';
4
+ import type { DragConfig } from '@use-gesture/core/types';
5
+ import type { OctaneNode } from 'octane';
6
+ import * as THREE from 'three';
7
+ import { useGesture } from './useGesture.js';
8
+
9
+ const initialModelPosition = new THREE.Vector3();
10
+ const mousePosition2D = new THREE.Vector2();
11
+ const mousePosition3D = new THREE.Vector3();
12
+ const dragOffset = new THREE.Vector3();
13
+ const dragPlaneNormal = new THREE.Vector3();
14
+ const dragPlane = new THREE.Plane();
15
+
16
+ type ControlsProto = { enabled: boolean };
17
+
18
+ export type DragControlsProps = Omit<ThreeElements['group'], 'ref' | 'children' | 'matrix'> & {
19
+ autoTransform?: boolean;
20
+ matrix?: THREE.Matrix4;
21
+ axisLock?: 'x' | 'y' | 'z';
22
+ dragLimits?: [[number, number] | undefined, [number, number] | undefined, [number, number] | undefined];
23
+ onHover?: (hovering: boolean) => void;
24
+ onDragStart?: (origin: THREE.Vector3) => void;
25
+ onDrag?: (localMatrix: THREE.Matrix4, deltaLocalMatrix: THREE.Matrix4, worldMatrix: THREE.Matrix4, deltaWorldMatrix: THREE.Matrix4) => void;
26
+ onDragEnd?: () => void;
27
+ children: OctaneNode;
28
+ dragConfig?: DragConfig;
29
+ };
30
+
31
+ export function DragControls({
32
+ autoTransform = true, matrix, axisLock, dragLimits, onHover, onDragStart, onDrag, onDragEnd, children, dragConfig, ref: forwardedRef, ...props
33
+ }: DragControlsProps & { ref?: ThreeRef<THREE.Group> }) @{
34
+ const defaultControls = useThree((state) => (state as any).controls) as ControlsProto | undefined;
35
+ const { camera, size, raycaster, invalidate } = useThree();
36
+ const ref = useRef<THREE.Group | null>(null);
37
+ const bind = useGesture({
38
+ onHover: ({ hovering }: any) => onHover?.(hovering ?? false),
39
+ onDragStart: ({ event }: any) => {
40
+ if (defaultControls) defaultControls.enabled = false;
41
+ const { point } = event;
42
+ ref.current!.matrix.decompose(initialModelPosition, new THREE.Quaternion(), new THREE.Vector3());
43
+ mousePosition3D.copy(point);
44
+ dragOffset.copy(mousePosition3D).sub(initialModelPosition);
45
+ onDragStart?.(initialModelPosition);
46
+ invalidate();
47
+ },
48
+ onDrag: ({ xy: [dragX, dragY], intentional }: any) => {
49
+ if (!intentional) return;
50
+ const normalizedMouseX = ((dragX - size.left) / size.width) * 2 - 1;
51
+ const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
52
+ mousePosition2D.set(normalizedMouseX, normalizedMouseY);
53
+ raycaster.setFromCamera(mousePosition2D, camera);
54
+ if (!axisLock) camera.getWorldDirection(dragPlaneNormal).negate();
55
+ else dragPlaneNormal.set(axisLock === 'x' ? 1 : 0, axisLock === 'y' ? 1 : 0, axisLock === 'z' ? 1 : 0);
56
+ dragPlane.setFromNormalAndCoplanarPoint(dragPlaneNormal, mousePosition3D);
57
+ raycaster.ray.intersectPlane(dragPlane, mousePosition3D);
58
+ const previousLocalMatrix = ref.current!.matrix.clone();
59
+ const previousWorldMatrix = ref.current!.matrixWorld.clone();
60
+ const intendedNewPosition = new THREE.Vector3(mousePosition3D.x - dragOffset.x, mousePosition3D.y - dragOffset.y, mousePosition3D.z - dragOffset.z);
61
+ if (dragLimits) {
62
+ intendedNewPosition.x = dragLimits[0] ? THREE.MathUtils.clamp(intendedNewPosition.x, ...dragLimits[0]) : intendedNewPosition.x;
63
+ intendedNewPosition.y = dragLimits[1] ? THREE.MathUtils.clamp(intendedNewPosition.y, ...dragLimits[1]) : intendedNewPosition.y;
64
+ intendedNewPosition.z = dragLimits[2] ? THREE.MathUtils.clamp(intendedNewPosition.z, ...dragLimits[2]) : intendedNewPosition.z;
65
+ }
66
+ const localMatrix = autoTransform ? ref.current!.matrix : new THREE.Matrix4().copy(ref.current!.matrix);
67
+ localMatrix.setPosition(intendedNewPosition);
68
+ const deltaLocalMatrix = localMatrix.clone().multiply(previousLocalMatrix.invert());
69
+ const deltaWorldMatrix = localMatrix.clone().multiply(previousWorldMatrix.invert());
70
+ onDrag?.(localMatrix, deltaLocalMatrix, ref.current!.matrixWorld, deltaWorldMatrix);
71
+ invalidate();
72
+ },
73
+ onDragEnd: () => {
74
+ if (defaultControls) defaultControls.enabled = true;
75
+ onDragEnd?.();
76
+ invalidate();
77
+ },
78
+ }, { drag: { filterTaps: true, threshold: 1, ...(typeof dragConfig === 'object' ? dragConfig : {}) } });
79
+ useImperativeHandle((forwardedRef ?? null) as any, () => ref.current!, []);
80
+ useLayoutEffect(() => { if (matrix) ref.current!.matrix = matrix; }, [matrix]);
81
+ <group ref={ref} {...((bind?.() ?? {}) as any)} matrix={matrix as any} matrixAutoUpdate={false} {...props}>{children}</group>
82
+ }
@@ -0,0 +1,178 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import type { FaceLandmarker, FaceLandmarkerResult } from '@mediapipe/tasks-vision';
3
+ import { type ThreeRef, useFrame, useThree } from '@octanejs/three';
4
+ import {
5
+ type RefObject,
6
+ useCallback,
7
+ useEffect,
8
+ useImperativeHandle,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from '@octanejs/three/renderer';
13
+ import { createContext, useContext } from 'octane';
14
+ import { easing } from 'maath';
15
+ import * as THREE from 'three';
16
+ import { VideoTexture, type VideoTextureProps } from '../core/VideoTexture.three.tsrx';
17
+ import { Facemesh, type FacemeshApi, type FacemeshProps } from './Facemesh.three.tsrx';
18
+ import { useFaceLandmarker } from './FaceLandmarker.three.tsrx';
19
+ import { WebcamVideoTexture } from './media-textures.three.tsrx';
20
+
21
+ type VideoFrame = Parameters<FaceLandmarker['detectForVideo']>[0];
22
+
23
+ function mean(v1: THREE.Vector3, v2: THREE.Vector3): THREE.Vector3 {
24
+ return v1.clone().add(v2).multiplyScalar(0.5);
25
+ }
26
+
27
+ function localToLocal(objSrc: THREE.Object3D, value: THREE.Vector3, objDst: THREE.Object3D): THREE.Vector3 {
28
+ return objDst.worldToLocal(objSrc.localToWorld(value));
29
+ }
30
+
31
+ export type FaceControlsProps = {
32
+ camera?: THREE.Camera;
33
+ videoTexture?: VideoTextureProps;
34
+ manualDetect?: boolean;
35
+ faceLandmarkerResult?: FaceLandmarkerResult;
36
+ manualUpdate?: boolean;
37
+ makeDefault?: boolean;
38
+ smoothTime?: number;
39
+ offset?: boolean;
40
+ offsetScalar?: number;
41
+ eyes?: boolean;
42
+ eyesAsOrigin?: boolean;
43
+ depth?: number;
44
+ debug?: boolean;
45
+ facemesh?: FacemeshProps;
46
+ };
47
+
48
+ export type FaceControlsApi = THREE.EventDispatcher & {
49
+ computeTarget: () => THREE.Object3D;
50
+ update: (delta: number, target?: THREE.Object3D) => void;
51
+ facemeshApiRef: RefObject<FacemeshApi | null>;
52
+ };
53
+
54
+ const FaceControlsContext = createContext({} as FaceControlsApi);
55
+
56
+ export function FaceControls({
57
+ camera,
58
+ videoTexture = { start: true } as VideoTextureProps,
59
+ manualDetect = false,
60
+ faceLandmarkerResult,
61
+ manualUpdate = false,
62
+ makeDefault,
63
+ smoothTime = 0.25,
64
+ offset = true,
65
+ offsetScalar = 80,
66
+ eyes = false,
67
+ eyesAsOrigin = true,
68
+ depth = 0.15,
69
+ debug = false,
70
+ facemesh,
71
+ ref,
72
+ }: FaceControlsProps & { ref?: ThreeRef<FaceControlsApi> }) @{
73
+ const scene = useThree((state) => state.scene);
74
+ const defaultCamera = useThree((state) => state.camera);
75
+ const set = useThree((state) => state.set);
76
+ const get = useThree((state) => state.get);
77
+ const explCamera = camera || defaultCamera;
78
+ const facemeshApiRef = useRef<FacemeshApi | null>(null);
79
+ const [target] = useState(() => new THREE.Object3D());
80
+ const [irisRightDirPos] = useState(() => new THREE.Vector3());
81
+ const [irisLeftDirPos] = useState(() => new THREE.Vector3());
82
+ const [irisRightLookAt] = useState(() => new THREE.Vector3());
83
+ const [irisLeftLookAt] = useState(() => new THREE.Vector3());
84
+ const computeTarget = useCallback<FaceControlsApi['computeTarget']>(() => {
85
+ target.parent = explCamera.parent;
86
+ const facemeshApi = facemeshApiRef.current;
87
+ if (facemeshApi) {
88
+ const { outerRef, eyeRightRef, eyeLeftRef } = facemeshApi;
89
+ if (eyeRightRef.current && eyeLeftRef.current) {
90
+ const { irisDirRef: irisRightDirRef } = eyeRightRef.current;
91
+ const { irisDirRef: irisLeftDirRef } = eyeLeftRef.current;
92
+ if (irisRightDirRef.current && irisLeftDirRef.current && outerRef.current) {
93
+ irisRightDirPos.copy(localToLocal(irisRightDirRef.current, new THREE.Vector3(0, 0, 0), outerRef.current));
94
+ irisLeftDirPos.copy(localToLocal(irisLeftDirRef.current, new THREE.Vector3(0, 0, 0), outerRef.current));
95
+ target.position.copy(localToLocal(outerRef.current, mean(irisRightDirPos, irisLeftDirPos), explCamera.parent || scene));
96
+ irisRightLookAt.copy(localToLocal(irisRightDirRef.current, new THREE.Vector3(0, 0, 1), outerRef.current));
97
+ irisLeftLookAt.copy(localToLocal(irisLeftDirRef.current, new THREE.Vector3(0, 0, 1), outerRef.current));
98
+ target.lookAt(outerRef.current.localToWorld(mean(irisRightLookAt, irisLeftLookAt)));
99
+ }
100
+ } else if (outerRef.current) {
101
+ target.position.copy(localToLocal(outerRef.current, new THREE.Vector3(0, 0, 0), explCamera.parent || scene));
102
+ target.lookAt(outerRef.current.localToWorld(new THREE.Vector3(0, 0, 1)));
103
+ }
104
+ }
105
+ return target;
106
+ }, [explCamera, irisLeftDirPos, irisLeftLookAt, irisRightDirPos, irisRightLookAt, scene, target]);
107
+
108
+ const [current] = useState(() => new THREE.Object3D());
109
+ const update = useCallback<FaceControlsApi['update']>((delta, nextTarget) => {
110
+ if (explCamera) {
111
+ nextTarget ??= computeTarget();
112
+ if (smoothTime > 0) {
113
+ const eps = 1e-9;
114
+ easing.damp3(current.position, nextTarget.position, smoothTime, delta, undefined, undefined, eps);
115
+ easing.dampE(current.rotation, nextTarget.rotation, smoothTime, delta, undefined, undefined, eps);
116
+ } else {
117
+ current.position.copy(nextTarget.position);
118
+ current.rotation.copy(nextTarget.rotation);
119
+ }
120
+ explCamera.position.copy(current.position);
121
+ explCamera.rotation.copy(current.rotation);
122
+ }
123
+ }, [explCamera, computeTarget, smoothTime, current.position, current.rotation]);
124
+ useFrame((_, delta) => { if (!manualUpdate) update(delta); });
125
+
126
+ const videoTextureRef = useRef<THREE.VideoTexture | null>(null);
127
+ const [_faceLandmarkerResult, setFaceLandmarkerResult] = useState<FaceLandmarkerResult | undefined>(undefined);
128
+ const faceLandmarker = useFaceLandmarker();
129
+ const onVideoFrame = useCallback<NonNullable<VideoTextureProps['onVideoFrame']>>((now) => {
130
+ const texture = videoTextureRef.current;
131
+ if (!texture) return;
132
+ const videoFrame = texture.source.data as VideoFrame;
133
+ setFaceLandmarkerResult(faceLandmarker?.detectForVideo(videoFrame, now));
134
+ }, [faceLandmarker]);
135
+
136
+ const api = useMemo<FaceControlsApi>(() => Object.assign(Object.create(THREE.EventDispatcher.prototype), {
137
+ computeTarget, update, facemeshApiRef,
138
+ }), [computeTarget, update]);
139
+ useImperativeHandle((ref ?? null) as any, () => api, [api]);
140
+ useEffect(() => {
141
+ if (makeDefault) {
142
+ const old = get().controls;
143
+ set({ controls: api });
144
+ return () => set({ controls: old });
145
+ }
146
+ }, [makeDefault, api, get, set]);
147
+
148
+ const result = faceLandmarkerResult ?? _faceLandmarkerResult;
149
+ const points = result?.faceLandmarks[0];
150
+ const facialTransformationMatrix = result?.facialTransformationMatrixes?.[0];
151
+ const faceBlendshapes = result?.faceBlendshapes?.[0];
152
+ const videoTextureProps = { onVideoFrame, ...videoTexture };
153
+ const facemeshChildren = facemesh?.children ?? <meshNormalMaterial side={THREE.DoubleSide} />;
154
+ <FaceControlsContext.Provider value={api}>
155
+ {!manualDetect &&
156
+ ('src' in videoTextureProps
157
+ ? <VideoTexture ref={videoTextureRef} {...videoTextureProps as VideoTextureProps} />
158
+ : <WebcamVideoTexture ref={videoTextureRef} {...videoTextureProps as any} />)}
159
+ <Facemesh
160
+ ref={facemeshApiRef}
161
+ {...facemesh as any}
162
+ points={points}
163
+ depth={depth}
164
+ facialTransformationMatrix={facialTransformationMatrix}
165
+ faceBlendshapes={faceBlendshapes}
166
+ eyes={eyes}
167
+ eyesAsOrigin={eyesAsOrigin}
168
+ offset={offset}
169
+ offsetScalar={offsetScalar}
170
+ debug={debug}
171
+ rotation-z={Math.PI}
172
+ visible={debug}
173
+ children={facemeshChildren}
174
+ />
175
+ </FaceControlsContext.Provider>
176
+ }
177
+
178
+ export const useFaceControls = (): FaceControlsApi => useContext(FaceControlsContext);
@@ -0,0 +1,48 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import type { FaceLandmarker as FaceLandmarkerImpl, FaceLandmarkerOptions } from '@mediapipe/tasks-vision';
3
+ import { use, useEffect, useImperativeHandle } from '@octanejs/three/renderer';
4
+ import { createContext, type OctaneNode, useContext } from 'octane';
5
+ import { clearResource, resourcePromise } from '../core/resource.js';
6
+ import { loadFaceLandmarker } from './face-landmarker-resource.js';
7
+
8
+ const FaceLandmarkerContext = createContext<FaceLandmarkerImpl | undefined>(undefined);
9
+
10
+ export type FaceLandmarkerProps = {
11
+ basePath?: string;
12
+ options?: FaceLandmarkerOptions;
13
+ children?: OctaneNode;
14
+ };
15
+
16
+ export const FaceLandmarkerDefaults = {
17
+ basePath: 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.17/wasm',
18
+ options: {
19
+ baseOptions: {
20
+ modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task',
21
+ delegate: 'GPU',
22
+ },
23
+ runningMode: 'VIDEO',
24
+ outputFaceBlendshapes: true,
25
+ outputFacialTransformationMatrixes: true,
26
+ } as FaceLandmarkerOptions,
27
+ };
28
+
29
+ export function FaceLandmarker({
30
+ basePath = FaceLandmarkerDefaults.basePath,
31
+ options = FaceLandmarkerDefaults.options,
32
+ children,
33
+ ref,
34
+ }: FaceLandmarkerProps & { ref?: unknown }) @{
35
+ const serializedOptions = JSON.stringify(options);
36
+ const keys = ['face-landmarker', basePath, serializedOptions] as const;
37
+ const faceLandmarker = use(resourcePromise(loadFaceLandmarker, keys));
38
+ useEffect(() => () => {
39
+ faceLandmarker?.close();
40
+ clearResource(keys);
41
+ }, [faceLandmarker, basePath, serializedOptions]);
42
+ useImperativeHandle((ref ?? null) as any, () => faceLandmarker, [faceLandmarker]);
43
+ <FaceLandmarkerContext.Provider value={faceLandmarker}>{children}</FaceLandmarkerContext.Provider>
44
+ }
45
+
46
+ export function useFaceLandmarker(): FaceLandmarkerImpl | undefined {
47
+ return useContext(FaceLandmarkerContext);
48
+ }