@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,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
|
+
}
|