@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,120 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type DomEvent, type RootState, type ThreeRef, useFrame, useStore, useThree } from '@octanejs/three';
|
|
3
|
+
import { type RefObject, useEffect, useImperativeHandle, useMemo, useRef, useState } from '@octanejs/three/renderer';
|
|
4
|
+
import { createContext, type OctaneNode, useContext } from 'octane';
|
|
5
|
+
import { easing } from 'maath';
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
import { mountScrollHtml, setScrollControlsStateProvider } from './scroll-html-root.tsrx';
|
|
8
|
+
|
|
9
|
+
export type ScrollControlsProps = {
|
|
10
|
+
eps?: number; horizontal?: boolean; infinite?: boolean; pages?: number; distance?: number;
|
|
11
|
+
damping?: number; maxSpeed?: number; prepend?: boolean; enabled?: boolean;
|
|
12
|
+
style?: Partial<CSSStyleDeclaration>; children: OctaneNode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ScrollControlsState = {
|
|
16
|
+
el: HTMLDivElement; eps: number; fill: HTMLDivElement; fixed: HTMLDivElement;
|
|
17
|
+
horizontal: boolean | undefined; damping: number; offset: number; delta: number; pages: number;
|
|
18
|
+
range(from: number, distance: number, margin?: number): number;
|
|
19
|
+
curve(from: number, distance: number, margin?: number): number;
|
|
20
|
+
visible(from: number, distance: number, margin?: number): boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const context = createContext<ScrollControlsState>(null as unknown as ScrollControlsState);
|
|
24
|
+
setScrollControlsStateProvider(context.Provider);
|
|
25
|
+
export function useScroll(): ScrollControlsState { return useContext(context); }
|
|
26
|
+
|
|
27
|
+
export function ScrollControls({ eps = 0.00001, enabled = true, infinite, horizontal, pages = 1,
|
|
28
|
+
distance = 1, damping = 0.25, maxSpeed = Infinity, prepend = false, style = {}, children }: ScrollControlsProps) @{
|
|
29
|
+
const rootState = useThree();
|
|
30
|
+
const { get, setEvents, gl, size, invalidate, events } = rootState;
|
|
31
|
+
const [el] = useState(() => document.createElement('div'));
|
|
32
|
+
const [fill] = useState(() => document.createElement('div'));
|
|
33
|
+
const [fixed] = useState(() => document.createElement('div'));
|
|
34
|
+
const target = (gl as unknown as THREE.WebGLRenderer).domElement.parentNode as HTMLElement;
|
|
35
|
+
const scroll = useRef(0);
|
|
36
|
+
const state = useMemo<ScrollControlsState & { scroll: RefObject<number> }>(() => ({
|
|
37
|
+
el, eps, fill, fixed, horizontal, damping, offset: 0, delta: 0, scroll, pages,
|
|
38
|
+
range(from, rangeDistance, margin = 0) { const start = from - margin; const end = start + rangeDistance + margin * 2; return this.offset < start ? 0 : this.offset > end ? 1 : (this.offset - start) / (end - start); },
|
|
39
|
+
curve(from, rangeDistance, margin = 0) { return Math.sin(this.range(from, rangeDistance, margin) * Math.PI); },
|
|
40
|
+
visible(from, rangeDistance, margin = 0) { const start = from - margin; const end = start + rangeDistance + margin * 2; return this.offset >= start && this.offset <= end; },
|
|
41
|
+
}), [eps, damping, horizontal, pages]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
el.style.position = 'absolute'; el.style.width = '100%'; el.style.height = '100%';
|
|
45
|
+
el.style[horizontal ? 'overflowX' : 'overflowY'] = 'auto';
|
|
46
|
+
el.style[horizontal ? 'overflowY' : 'overflowX'] = 'hidden';
|
|
47
|
+
el.style.top = '0px'; el.style.left = '0px';
|
|
48
|
+
for (const key in style) (el.style as any)[key] = (style as any)[key];
|
|
49
|
+
fixed.style.position = 'sticky'; fixed.style.top = '0px'; fixed.style.left = '0px';
|
|
50
|
+
fixed.style.width = '100%'; fixed.style.height = '100%'; fixed.style.overflow = 'hidden'; el.appendChild(fixed);
|
|
51
|
+
fill.style.height = horizontal ? '100%' : `${pages * distance * 100}%`;
|
|
52
|
+
fill.style.width = horizontal ? `${pages * distance * 100}%` : '100%'; fill.style.pointerEvents = 'none'; el.appendChild(fill);
|
|
53
|
+
if (prepend) target.prepend(el); else target.appendChild(el);
|
|
54
|
+
el[horizontal ? 'scrollLeft' : 'scrollTop'] = 1;
|
|
55
|
+
const oldTarget = (events.connected || (gl as unknown as THREE.WebGLRenderer).domElement) as HTMLElement;
|
|
56
|
+
requestAnimationFrame(() => events.connect?.(el));
|
|
57
|
+
const oldCompute = get().events.compute;
|
|
58
|
+
setEvents({ compute(event: DomEvent, current: RootState) {
|
|
59
|
+
const { left, top } = target.getBoundingClientRect();
|
|
60
|
+
current.pointer.set(((event.clientX - left) / current.size.width) * 2 - 1, -((event.clientY - top) / current.size.height) * 2 + 1);
|
|
61
|
+
current.raycaster.setFromCamera(current.pointer, current.camera);
|
|
62
|
+
} });
|
|
63
|
+
return () => { target.removeChild(el); setEvents({ compute: oldCompute }); events.connect?.(oldTarget); };
|
|
64
|
+
}, [pages, distance, horizontal, el, fill, fixed, target]);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (events.connected !== el) return;
|
|
68
|
+
const containerLength = size[horizontal ? 'width' : 'height'];
|
|
69
|
+
const scrollLength = el[horizontal ? 'scrollWidth' : 'scrollHeight'];
|
|
70
|
+
const threshold = scrollLength - containerLength;
|
|
71
|
+
let disableScroll = true; let firstRun = true;
|
|
72
|
+
const onScroll = () => {
|
|
73
|
+
if (!enabled || firstRun) return;
|
|
74
|
+
invalidate(); const current = el[horizontal ? 'scrollLeft' : 'scrollTop']; scroll.current = current / threshold;
|
|
75
|
+
if (infinite) {
|
|
76
|
+
if (!disableScroll) {
|
|
77
|
+
if (current >= threshold) { const damp = 1 - state.offset; el[horizontal ? 'scrollLeft' : 'scrollTop'] = 1; scroll.current = state.offset = -damp; disableScroll = true; }
|
|
78
|
+
else if (current <= 0) { const damp = 1 + state.offset; el[horizontal ? 'scrollLeft' : 'scrollTop'] = scrollLength; scroll.current = state.offset = damp; disableScroll = true; }
|
|
79
|
+
}
|
|
80
|
+
if (disableScroll) setTimeout(() => (disableScroll = false), 40);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
el.addEventListener('scroll', onScroll, { passive: true });
|
|
84
|
+
requestAnimationFrame(() => (firstRun = false));
|
|
85
|
+
const onWheel = (event: WheelEvent) => { el.scrollLeft += event.deltaY / 2; };
|
|
86
|
+
if (horizontal) el.addEventListener('wheel', onWheel, { passive: true });
|
|
87
|
+
return () => { el.removeEventListener('scroll', onScroll); if (horizontal) el.removeEventListener('wheel', onWheel); };
|
|
88
|
+
}, [el, events, size, infinite, state, invalidate, horizontal, enabled]);
|
|
89
|
+
|
|
90
|
+
let last = 0;
|
|
91
|
+
useFrame((_, delta) => { last = state.offset; easing.damp(state, 'offset', scroll.current, damping, delta, maxSpeed, undefined, eps); easing.damp(state, 'delta', Math.abs(last - state.offset), damping, delta, maxSpeed, undefined, eps); if (state.delta > eps) invalidate(); });
|
|
92
|
+
<context.Provider value={state}>{children}</context.Provider>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type ScrollProps = { children?: OctaneNode; html?: false; style?: never } | { children?: OctaneNode; html: true; style?: Partial<CSSStyleDeclaration>; [key: string]: unknown };
|
|
96
|
+
|
|
97
|
+
function ScrollCanvas({ children, ref }: ScrollProps & { ref?: ThreeRef<THREE.Group> }) @{
|
|
98
|
+
const group = useRef<THREE.Group | null>(null); useImperativeHandle((ref ?? null) as any, () => group.current!, []);
|
|
99
|
+
const state = useScroll(); const { width, height } = useThree((value) => value.viewport);
|
|
100
|
+
useFrame(() => { if (group.current) { group.current.position.x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0; group.current.position.y = state.horizontal ? 0 : height * (state.pages - 1) * state.offset; } });
|
|
101
|
+
<group ref={group}>{children}</group>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function ScrollHtml({ children, style, ref, ...props }: Extract<ScrollProps, { html: true }> & { ref?: ThreeRef<HTMLDivElement> }) @{
|
|
105
|
+
const state = useScroll(); const [group] = useState(() => document.createElement('div')); const fiberState = useStore();
|
|
106
|
+
const { width, height } = useThree((value) => value.size);
|
|
107
|
+
useImperativeHandle((ref ?? null) as any, () => group, [group]);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
Object.assign(group.style, style, { position: 'absolute', top: '0px', left: '0px', willChange: 'transform' });
|
|
110
|
+
for (const [key, value] of Object.entries(props)) if (value != null) group.setAttribute(key, String(value));
|
|
111
|
+
state.fixed.appendChild(group);
|
|
112
|
+
const unmount = mountScrollHtml(group, { state, fiberState, children });
|
|
113
|
+
return () => { unmount(); group.remove(); };
|
|
114
|
+
}, [state.fixed]);
|
|
115
|
+
useFrame(() => { if (state.delta > state.eps) group.style.transform = `translate3d(${state.horizontal ? -width * (state.pages - 1) * state.offset : 0}px,${state.horizontal ? 0 : height * (state.pages - 1) * -state.offset}px,0)`; });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function Scroll({ html, ref, ...props }: ScrollProps & { ref?: ThreeRef<THREE.Group & HTMLDivElement> }) @{
|
|
119
|
+
<>{html ? <ScrollHtml ref={ref as any} {...props as any} /> : <ScrollCanvas ref={ref as any} {...props as any} />}</>
|
|
120
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type ThreeElements, useThree } from '@octanejs/three';
|
|
3
|
+
import { useCallback, useEffect, useReducer, useRef, useState } from '@octanejs/three/renderer';
|
|
4
|
+
import { createContext, useContext } from 'octane';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { SelectionBox } from 'three-stdlib';
|
|
7
|
+
import { shallow } from '@octanejs/zustand/shallow';
|
|
8
|
+
|
|
9
|
+
const SelectionContext = createContext<THREE.Object3D[]>([]);
|
|
10
|
+
|
|
11
|
+
export type SelectProps =
|
|
12
|
+
Omit<ThreeElements['group'], 'ref'> & {
|
|
13
|
+
multiple?: boolean;
|
|
14
|
+
box?: boolean;
|
|
15
|
+
border?: string;
|
|
16
|
+
backgroundColor?: string;
|
|
17
|
+
onChange?: (selected: THREE.Object3D[]) => void;
|
|
18
|
+
onChangePointerUp?: (selected: THREE.Object3D[]) => void;
|
|
19
|
+
filter?: (selected: THREE.Object3D[]) => THREE.Object3D[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type Action = { object?: THREE.Object3D | THREE.Object3D[]; shift?: boolean };
|
|
23
|
+
|
|
24
|
+
export function Select({
|
|
25
|
+
box,
|
|
26
|
+
multiple,
|
|
27
|
+
children,
|
|
28
|
+
onChange,
|
|
29
|
+
onChangePointerUp,
|
|
30
|
+
border = '1px solid #55aaff',
|
|
31
|
+
backgroundColor = 'rgba(75, 160, 255, 0.1)',
|
|
32
|
+
filter: customFilter = (item) => item,
|
|
33
|
+
...props
|
|
34
|
+
}: SelectProps) @{
|
|
35
|
+
const [downed, down] = useState(false);
|
|
36
|
+
const { setEvents, camera, raycaster, gl, controls, size, get } = useThree();
|
|
37
|
+
const [hovered, hover] = useState(false);
|
|
38
|
+
const [active, dispatch] = useReducer((state: THREE.Object3D[], { object, shift }: Action) => {
|
|
39
|
+
if (object === undefined) return [];
|
|
40
|
+
if (Array.isArray(object)) return object;
|
|
41
|
+
if (!shift) return state[0] === object ? [] : [object];
|
|
42
|
+
if (state.includes(object)) return state.filter((entry) => entry !== object);
|
|
43
|
+
return [object, ...state];
|
|
44
|
+
}, [] as THREE.Object3D[]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (downed) onChange?.(active);
|
|
47
|
+
else onChangePointerUp?.(active);
|
|
48
|
+
}, [active, downed]);
|
|
49
|
+
const onClick = useCallback((event: any) => {
|
|
50
|
+
event.stopPropagation();
|
|
51
|
+
dispatch({ object: customFilter([event.object])[0], shift: multiple && event.shiftKey });
|
|
52
|
+
}, []);
|
|
53
|
+
const onPointerMissed = useCallback(() => {
|
|
54
|
+
if (!hovered) dispatch({});
|
|
55
|
+
}, [hovered]);
|
|
56
|
+
const ref = useRef<THREE.Group | null>(null);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!box || !multiple || !ref.current) return;
|
|
59
|
+
const Selection = SelectionBox as unknown as new (camera: THREE.Camera, scene: THREE.Scene) => SelectionBox;
|
|
60
|
+
const selectionBox = new Selection(camera, ref.current as unknown as THREE.Scene);
|
|
61
|
+
const element = document.createElement('div');
|
|
62
|
+
element.style.pointerEvents = 'none';
|
|
63
|
+
element.style.border = border;
|
|
64
|
+
element.style.backgroundColor = backgroundColor;
|
|
65
|
+
element.style.position = 'fixed';
|
|
66
|
+
const startPoint = new THREE.Vector2();
|
|
67
|
+
const pointTopLeft = new THREE.Vector2();
|
|
68
|
+
const pointBottomRight = new THREE.Vector2();
|
|
69
|
+
const oldRaycasterEnabled = get().events.enabled;
|
|
70
|
+
const oldControlsEnabled = (controls as any)?.enabled;
|
|
71
|
+
let isDown = false;
|
|
72
|
+
function prepareRay(event: PointerEvent, vector: THREE.Vector2) {
|
|
73
|
+
const { offsetX, offsetY } = event;
|
|
74
|
+
vector.set((offsetX / size.width) * 2 - 1, -(offsetY / size.height) * 2 + 1);
|
|
75
|
+
}
|
|
76
|
+
function onSelectStart(event: PointerEvent) {
|
|
77
|
+
if (controls) (controls as any).enabled = false;
|
|
78
|
+
setEvents({ enabled: false });
|
|
79
|
+
down(isDown = true);
|
|
80
|
+
(gl.domElement as HTMLElement).parentElement?.appendChild(element);
|
|
81
|
+
element.style.left = `${event.clientX}px`;
|
|
82
|
+
element.style.top = `${event.clientY}px`;
|
|
83
|
+
element.style.width = '0px';
|
|
84
|
+
element.style.height = '0px';
|
|
85
|
+
startPoint.set(event.clientX, event.clientY);
|
|
86
|
+
}
|
|
87
|
+
function onSelectMove(event: PointerEvent) {
|
|
88
|
+
pointBottomRight.set(
|
|
89
|
+
Math.max(startPoint.x, event.clientX),
|
|
90
|
+
Math.max(startPoint.y, event.clientY),
|
|
91
|
+
);
|
|
92
|
+
pointTopLeft.set(
|
|
93
|
+
Math.min(startPoint.x, event.clientX),
|
|
94
|
+
Math.min(startPoint.y, event.clientY),
|
|
95
|
+
);
|
|
96
|
+
element.style.left = `${pointTopLeft.x}px`;
|
|
97
|
+
element.style.top = `${pointTopLeft.y}px`;
|
|
98
|
+
element.style.width = `${pointBottomRight.x - pointTopLeft.x}px`;
|
|
99
|
+
element.style.height = `${pointBottomRight.y - pointTopLeft.y}px`;
|
|
100
|
+
}
|
|
101
|
+
function onSelectOver() {
|
|
102
|
+
if (isDown) {
|
|
103
|
+
if (controls) (controls as any).enabled = oldControlsEnabled;
|
|
104
|
+
setEvents({ enabled: oldRaycasterEnabled });
|
|
105
|
+
down(isDown = false);
|
|
106
|
+
element.parentElement?.removeChild(element);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function pointerDown(event: PointerEvent) {
|
|
110
|
+
if (event.shiftKey) {
|
|
111
|
+
onSelectStart(event);
|
|
112
|
+
prepareRay(event, selectionBox.startPoint as unknown as THREE.Vector2);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
let previous: THREE.Mesh[] = [];
|
|
116
|
+
function pointerMove(event: PointerEvent) {
|
|
117
|
+
if (isDown) {
|
|
118
|
+
onSelectMove(event);
|
|
119
|
+
prepareRay(event, selectionBox.endPoint as unknown as THREE.Vector2);
|
|
120
|
+
const allSelected = (selectionBox.select() as unknown as THREE.Object3D[])
|
|
121
|
+
.sort((object) => object.uuid as unknown as number)
|
|
122
|
+
.filter((object): object is THREE.Mesh => (object as THREE.Mesh).isMesh === true);
|
|
123
|
+
if (!shallow(allSelected, previous)) {
|
|
124
|
+
previous = allSelected;
|
|
125
|
+
dispatch({ object: customFilter(allSelected) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function pointerUp() {
|
|
130
|
+
if (isDown) onSelectOver();
|
|
131
|
+
}
|
|
132
|
+
document.addEventListener('pointerdown', pointerDown, { passive: true });
|
|
133
|
+
document.addEventListener('pointermove', pointerMove, { passive: true, capture: true });
|
|
134
|
+
document.addEventListener('pointerup', pointerUp, { passive: true });
|
|
135
|
+
return () => {
|
|
136
|
+
document.removeEventListener('pointerdown', pointerDown);
|
|
137
|
+
document.removeEventListener('pointermove', pointerMove, true);
|
|
138
|
+
document.removeEventListener('pointerup', pointerUp);
|
|
139
|
+
};
|
|
140
|
+
}, [size.width, size.height, raycaster, camera, controls, gl]);
|
|
141
|
+
<group
|
|
142
|
+
ref={ref}
|
|
143
|
+
onClick={onClick}
|
|
144
|
+
onPointerOver={() => hover(true)}
|
|
145
|
+
onPointerOut={() => hover(false)}
|
|
146
|
+
onPointerMissed={onPointerMissed}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
<SelectionContext.Provider value={active}>{children}</SelectionContext.Provider>
|
|
150
|
+
</group>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function useSelect(): THREE.Object3D[] {
|
|
154
|
+
return useContext(SelectionContext);
|
|
155
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { context as threeContext, createPortal, type RootState, type ThreeRef, useFrame, useThree } from '@octanejs/three';
|
|
3
|
+
import { useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from '@octanejs/three/renderer';
|
|
4
|
+
import { type OctaneNode } from 'octane';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
|
|
7
|
+
type CanvasSize = { top: number; left: number; height: number; width: number };
|
|
8
|
+
export type ContainerProps = { visible: boolean; scene: THREE.Scene; index: number; children?: OctaneNode; frames: number; rect: { current: DOMRect | null }; track?: { current: HTMLElement | null }; canvasSize: CanvasSize };
|
|
9
|
+
export type ViewProps = { as?: string; id?: string; className?: string; style?: Record<string, unknown>; visible?: boolean; index?: number; frames?: number; children?: OctaneNode; track?: { current: HTMLElement | null }; [key: string]: unknown };
|
|
10
|
+
export type ViewportProps = {
|
|
11
|
+
(props: ViewProps & { ref?: ThreeRef<HTMLElement | THREE.Group> }): any;
|
|
12
|
+
Port: typeof ViewPort;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const col = new THREE.Color();
|
|
16
|
+
function position(canvasSize: CanvasSize, rect: DOMRect) { const canvasBottom = canvasSize.top + canvasSize.height; return { position: { width: rect.width, height: rect.height, left: rect.left - canvasSize.left, top: rect.top, bottom: canvasBottom - rect.bottom, right: rect.right }, isOffscreen: rect.bottom < 0 || rect.top > canvasSize.height || rect.right < 0 || rect.left > canvasSize.width }; }
|
|
17
|
+
function prepare(state: RootState, value: ReturnType<typeof position>['position']) { const gl = state.gl as any; const aspect = value.width / value.height; const camera = state.camera as any; if (camera.isOrthographicCamera) { if (!camera.manual) Object.assign(camera, { left: value.width / -2, right: value.width / 2, top: value.height / 2, bottom: value.height / -2 }); camera.updateProjectionMatrix(); } else if (camera.aspect !== aspect) { camera.aspect = aspect; camera.updateProjectionMatrix(); } const autoClear = gl.autoClear; gl.autoClear = false; gl.setViewport(value.left, value.bottom, value.width, value.height); gl.setScissor(value.left, value.bottom, value.width, value.height); gl.setScissorTest(true); return autoClear; }
|
|
18
|
+
function finish(state: RootState, autoClear: boolean) { const gl = state.gl as any; gl.setScissorTest(false); gl.autoClear = autoClear; }
|
|
19
|
+
function clear(state: RootState) { const gl = state.gl as any; gl.getClearColor(col); gl.setClearColor(col, gl.getClearAlpha()); gl.clear(true, true); }
|
|
20
|
+
|
|
21
|
+
function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }: ContainerProps) @{
|
|
22
|
+
const root = useThree(); const [offscreen, setOffscreen] = useState(false); let count = 0;
|
|
23
|
+
useFrame((state) => { if (frames === Infinity || count <= frames) { if (track) rect.current = track.current?.getBoundingClientRect() ?? null; count++; } if (rect.current) { const result = position(canvasSize, rect.current); if (offscreen !== result.isOffscreen) setOffscreen(result.isOffscreen); if (visible && !offscreen) { const old = prepare(state, result.position); (state.gl as any).render(children ? state.scene : scene, state.camera); finish(state, old); } } }, index);
|
|
24
|
+
useLayoutEffect(() => { if (rect.current && (!visible || !offscreen)) { const old = prepare(root, position(canvasSize, rect.current).position); clear(root); finish(root, old); } }, [visible, offscreen]);
|
|
25
|
+
useEffect(() => { if (!track) return; const currentRect = rect.current; const old = root.get().events.connected; root.setEvents({ connected: track.current }); return () => { if (currentRect) { const auto = prepare(root, position(canvasSize, currentRect).position); clear(root); finish(root, auto); } root.setEvents({ connected: old }); }; }, [track]);
|
|
26
|
+
<>{children}<group onPointerOver={() => null} /></>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function CanvasView({ track, visible = true, index = 1, frames = Infinity, children, ref, ...props }: ViewProps & { ref?: ThreeRef<THREE.Group> }) @{
|
|
30
|
+
const rect = useRef<DOMRect | null>(null); const { size, scene } = useThree(); const [virtualScene] = useState(() => new THREE.Scene()); const [ready, setReady] = useState(false);
|
|
31
|
+
const compute = useCallback((event: any, state: RootState) => { if (rect.current && track?.current && event.target === track.current) { const x = event.clientX - rect.current.left; const y = event.clientY - rect.current.top; state.pointer.set((x / rect.current.width) * 2 - 1, -(y / rect.current.height) * 2 + 1); state.raycaster.setFromCamera(state.pointer, state.camera); } }, [rect, track]);
|
|
32
|
+
useEffect(() => { if (track) rect.current = track.current?.getBoundingClientRect() ?? null; setReady(true); }, [track]);
|
|
33
|
+
<group ref={ref as any} {...props as any}>{ready && createPortal(<Container visible={visible} canvasSize={size} frames={frames} scene={scene} track={track} rect={rect} index={index}>{children}</Container>, virtualScene, { events: { compute, priority: index }, size: { width: rect.current?.width ?? size.width, height: rect.current?.height ?? size.height, top: rect.current?.top ?? size.top, left: rect.current?.left ?? size.left } })}</group>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ViewComponent(props: ViewProps & { ref?: ThreeRef<HTMLElement | THREE.Group> }) @{
|
|
37
|
+
useContext(threeContext);
|
|
38
|
+
<CanvasView {...props as any} />
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// OCTANE DIVERGENCE[view-renderer-boundary][ordinary:view-renderer-boundary]
|
|
42
|
+
export function ViewPort() @{
|
|
43
|
+
<></>
|
|
44
|
+
}
|
|
45
|
+
export const View = ViewComponent as ViewportProps;
|
|
46
|
+
View.Port = ViewPort;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FaceLandmarker as FaceLandmarkerImpl,
|
|
3
|
+
FaceLandmarkerOptions,
|
|
4
|
+
} from '@mediapipe/tasks-vision';
|
|
5
|
+
|
|
6
|
+
export async function loadFaceLandmarker(
|
|
7
|
+
_prefix: string,
|
|
8
|
+
basePath: string,
|
|
9
|
+
serializedOptions: string,
|
|
10
|
+
): Promise<FaceLandmarkerImpl> {
|
|
11
|
+
const { FilesetResolver, FaceLandmarker: Implementation } =
|
|
12
|
+
await import('@mediapipe/tasks-vision');
|
|
13
|
+
const vision = await FilesetResolver.forVisionTasks(basePath);
|
|
14
|
+
return Implementation.createFromOptions(
|
|
15
|
+
vision,
|
|
16
|
+
JSON.parse(serializedOptions) as FaceLandmarkerOptions,
|
|
17
|
+
);
|
|
18
|
+
}
|