@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,125 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type ThreeElements, type ThreeRef, useFrame } from '@octanejs/three';
|
|
3
|
+
import { useMemo, useRef, useState } from '@octanejs/three/renderer';
|
|
4
|
+
import { createContext, useContext } from 'octane';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { MarchingCubes as MarchingCubesImpl } from 'three-stdlib';
|
|
7
|
+
|
|
8
|
+
type MarchingApi = { getParent(): { current: MarchingCubesImpl | null } };
|
|
9
|
+
const MarchingContext = createContext<MarchingApi | null>(null);
|
|
10
|
+
|
|
11
|
+
function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
|
|
12
|
+
if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
|
|
13
|
+
else if (typeof ref === 'function') ref(value);
|
|
14
|
+
else if (ref && 'current' in ref) ref.current = value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type MarchingCubesProps = {
|
|
18
|
+
resolution?: number;
|
|
19
|
+
maxPolyCount?: number;
|
|
20
|
+
enableUvs?: boolean;
|
|
21
|
+
enableColors?: boolean;
|
|
22
|
+
} & Omit<ThreeElements['group'], 'ref'>;
|
|
23
|
+
|
|
24
|
+
export function MarchingCubes({
|
|
25
|
+
resolution = 28,
|
|
26
|
+
maxPolyCount = 10000,
|
|
27
|
+
enableUvs = false,
|
|
28
|
+
enableColors = false,
|
|
29
|
+
children,
|
|
30
|
+
ref,
|
|
31
|
+
...props
|
|
32
|
+
}: MarchingCubesProps & { ref?: ThreeRef<MarchingCubesImpl> }) @{
|
|
33
|
+
const marchingRef = useRef<MarchingCubesImpl | null>(null);
|
|
34
|
+
const marchingCubes = useMemo(() => {
|
|
35
|
+
const Constructor = MarchingCubesImpl as unknown as new (
|
|
36
|
+
resolution: number,
|
|
37
|
+
material: THREE.Material,
|
|
38
|
+
enableUvs: boolean,
|
|
39
|
+
enableColors: boolean,
|
|
40
|
+
maxPolyCount: number,
|
|
41
|
+
) => MarchingCubesImpl;
|
|
42
|
+
return new Constructor(resolution, null as unknown as THREE.Material, enableUvs, enableColors, maxPolyCount);
|
|
43
|
+
}, [resolution, maxPolyCount, enableUvs, enableColors]);
|
|
44
|
+
const api = useMemo<MarchingApi>(() => ({ getParent: () => marchingRef }), []);
|
|
45
|
+
const mergedRef = useMemo(
|
|
46
|
+
() => (value: MarchingCubesImpl | null) => { marchingRef.current = value; assignRef(ref, value); },
|
|
47
|
+
[ref],
|
|
48
|
+
);
|
|
49
|
+
useFrame(() => {
|
|
50
|
+
marchingCubes.update();
|
|
51
|
+
marchingCubes.reset();
|
|
52
|
+
}, -1);
|
|
53
|
+
<primitive object={marchingCubes} ref={mergedRef as any} {...props as any}>
|
|
54
|
+
<MarchingContext.Provider value={api}>{children}</MarchingContext.Provider>
|
|
55
|
+
</primitive>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type MarchingCubeProps = {
|
|
59
|
+
strength?: number;
|
|
60
|
+
subtract?: number;
|
|
61
|
+
color?: THREE.Color;
|
|
62
|
+
} & Omit<ThreeElements['group'], 'ref'>;
|
|
63
|
+
|
|
64
|
+
export function MarchingCube({
|
|
65
|
+
strength = 0.5,
|
|
66
|
+
subtract = 12,
|
|
67
|
+
color,
|
|
68
|
+
ref,
|
|
69
|
+
...props
|
|
70
|
+
}: MarchingCubeProps & { ref?: ThreeRef<THREE.Group> }) @{
|
|
71
|
+
const api = useContext(MarchingContext);
|
|
72
|
+
if (!api) throw new Error('MarchingCube must be used inside MarchingCubes.');
|
|
73
|
+
const parentRef = useMemo(() => api.getParent(), [api]);
|
|
74
|
+
const cubeRef = useRef<THREE.Group | null>(null);
|
|
75
|
+
const vector = new THREE.Vector3();
|
|
76
|
+
const mergedRef = useMemo(
|
|
77
|
+
() => (value: THREE.Group | null) => { cubeRef.current = value; assignRef(ref, value); },
|
|
78
|
+
[ref],
|
|
79
|
+
);
|
|
80
|
+
useFrame(() => {
|
|
81
|
+
if (!parentRef.current || !cubeRef.current) return;
|
|
82
|
+
cubeRef.current.getWorldPosition(vector);
|
|
83
|
+
parentRef.current.addBall(
|
|
84
|
+
0.5 + vector.x * 0.5,
|
|
85
|
+
0.5 + vector.y * 0.5,
|
|
86
|
+
0.5 + vector.z * 0.5,
|
|
87
|
+
strength,
|
|
88
|
+
subtract,
|
|
89
|
+
color as any,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
<group ref={mergedRef} {...props} />
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type MarchingPlaneProps = {
|
|
96
|
+
planeType?: 'x' | 'y' | 'z';
|
|
97
|
+
strength?: number;
|
|
98
|
+
subtract?: number;
|
|
99
|
+
} & Omit<ThreeElements['group'], 'ref'>;
|
|
100
|
+
|
|
101
|
+
export function MarchingPlane({
|
|
102
|
+
planeType: requestedPlaneType = 'x',
|
|
103
|
+
strength = 0.5,
|
|
104
|
+
subtract = 12,
|
|
105
|
+
ref,
|
|
106
|
+
...props
|
|
107
|
+
}: MarchingPlaneProps & { ref?: ThreeRef<THREE.Group> }) @{
|
|
108
|
+
const api = useContext(MarchingContext);
|
|
109
|
+
if (!api) throw new Error('MarchingPlane must be used inside MarchingCubes.');
|
|
110
|
+
const parentRef = useMemo(() => api.getParent(), [api]);
|
|
111
|
+
const wallRef = useRef<THREE.Group | null>(null);
|
|
112
|
+
const planeType = useMemo(
|
|
113
|
+
() => requestedPlaneType === 'x' ? 'addPlaneX' : requestedPlaneType === 'y' ? 'addPlaneY' : 'addPlaneZ',
|
|
114
|
+
[requestedPlaneType],
|
|
115
|
+
);
|
|
116
|
+
const mergedRef = useMemo(
|
|
117
|
+
() => (value: THREE.Group | null) => { wallRef.current = value; assignRef(ref, value); },
|
|
118
|
+
[ref],
|
|
119
|
+
);
|
|
120
|
+
useFrame(() => {
|
|
121
|
+
if (!parentRef.current || !wallRef.current) return;
|
|
122
|
+
parentRef.current[planeType](strength, subtract);
|
|
123
|
+
});
|
|
124
|
+
<group ref={mergedRef} {...props} />
|
|
125
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { extend, type ComputeFunction, type ThreeElements, type ThreeRef, useFrame, useInstanceHandle, useThree } from '@octanejs/three';
|
|
3
|
+
import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, type RefObject } from '@octanejs/three/renderer';
|
|
4
|
+
import type { OctaneNode } from 'octane';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { FullScreenQuad } from 'three-stdlib';
|
|
7
|
+
import { PortalMaterialImpl, type PortalMaterialInstance } from '../materials/PortalMaterial.js';
|
|
8
|
+
import { makeSDFGenerator } from '../materials/makeSDFGenerator.js';
|
|
9
|
+
import { useFBO } from './Fbo.three.tsrx';
|
|
10
|
+
import { RenderTexture } from './RenderTexture.three.tsrx';
|
|
11
|
+
import { useIntersect } from './useIntersect.three.tsrx';
|
|
12
|
+
|
|
13
|
+
export type PortalProps = Omit<ThreeElements['shaderMaterial'], 'ref'> & {
|
|
14
|
+
blend?: number; blur?: number; resolution?: number; worldUnits?: boolean; eventPriority?: number;
|
|
15
|
+
renderPriority?: number; events?: boolean; children?: OctaneNode;
|
|
16
|
+
};
|
|
17
|
+
const PortalMaterialNode = extend(PortalMaterialImpl);
|
|
18
|
+
|
|
19
|
+
export function MeshPortalMaterial({
|
|
20
|
+
children, events = undefined, blur = 0, eventPriority = 0, renderPriority = 0, worldUnits = false,
|
|
21
|
+
resolution = 512, ref: forwardedRef, ...props
|
|
22
|
+
}: PortalProps & { ref?: ThreeRef<PortalMaterialInstance> }) @{
|
|
23
|
+
const ref = useRef<PortalMaterialInstance | null>(null);
|
|
24
|
+
const instance = useInstanceHandle(ref);
|
|
25
|
+
const { scene, gl, size, viewport, setEvents } = useThree();
|
|
26
|
+
const maskRenderTarget = useFBO(resolution, resolution);
|
|
27
|
+
const [priority, setPriority] = useState(0);
|
|
28
|
+
useFrame(() => {
|
|
29
|
+
const next = ref.current!.blend > 0 ? Math.max(1, renderPriority) : 0;
|
|
30
|
+
if (priority !== next) setPriority(next);
|
|
31
|
+
});
|
|
32
|
+
useEffect(() => { if (events !== undefined) setEvents({ enabled: !events }); }, [events]);
|
|
33
|
+
const [visible, setVisible] = useState(true);
|
|
34
|
+
const parent = useIntersect<THREE.Mesh<THREE.BufferGeometry>>(setVisible);
|
|
35
|
+
useLayoutEffect(() => { parent.current = instance.current?.parent?.object as THREE.Mesh<THREE.BufferGeometry> | null; }, []);
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
if (!parent.current || !blur || ref.current!.sdf !== null) return;
|
|
38
|
+
const tempMesh = new THREE.Mesh(parent.current.geometry, new THREE.MeshBasicMaterial());
|
|
39
|
+
const boundingBox = new THREE.Box3().setFromBufferAttribute(tempMesh.geometry.attributes.position as THREE.BufferAttribute);
|
|
40
|
+
const orthoCam = new THREE.OrthographicCamera(
|
|
41
|
+
boundingBox.min.x * (1 + 2 / resolution), boundingBox.max.x * (1 + 2 / resolution),
|
|
42
|
+
boundingBox.max.y * (1 + 2 / resolution), boundingBox.min.y * (1 + 2 / resolution), 0.1, 1000,
|
|
43
|
+
);
|
|
44
|
+
orthoCam.position.set(0, 0, 1); orthoCam.lookAt(0, 0, 0);
|
|
45
|
+
gl.setRenderTarget(maskRenderTarget); (gl as any).render(tempMesh, orthoCam);
|
|
46
|
+
const sdf = makeSDFGenerator(resolution, resolution, gl as any)(maskRenderTarget.texture);
|
|
47
|
+
const readSdf = new Float32Array(resolution * resolution); gl.readRenderTargetPixels(sdf, 0, 0, resolution, resolution, readSdf);
|
|
48
|
+
let min = Infinity; for (let index = 0; index < readSdf.length; index++) if (readSdf[index] < min) min = readSdf[index];
|
|
49
|
+
ref.current!.size = -min; (ref.current as any).sdf = sdf.texture; gl.setRenderTarget(null);
|
|
50
|
+
}, [resolution, blur]);
|
|
51
|
+
useImperativeHandle((forwardedRef ?? null) as any, () => ref.current!);
|
|
52
|
+
const compute = useCallback<ComputeFunction>((event, state) => {
|
|
53
|
+
if (!parent.current) return false as any;
|
|
54
|
+
state.pointer.set((event.offsetX / state.size.width) * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
55
|
+
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
56
|
+
if (ref.current?.blend === 0) {
|
|
57
|
+
const [intersection] = state.raycaster.intersectObject(parent.current);
|
|
58
|
+
if (!intersection) { (state.raycaster as any).camera = undefined; return false as any; }
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
<PortalMaterialNode ref={ref} blur={blur} blend={0} resolution={[size.width * viewport.dpr, size.height * viewport.dpr]} attach="material" {...props as any}>
|
|
62
|
+
<RenderTexture attach="map" frames={visible ? Infinity : 0} eventPriority={eventPriority} renderPriority={renderPriority} compute={compute}>
|
|
63
|
+
{children}
|
|
64
|
+
<ManagePortalScene events={events} rootScene={scene} priority={priority} material={ref} parent={parent} worldUnits={worldUnits} />
|
|
65
|
+
</RenderTexture>
|
|
66
|
+
</PortalMaterialNode>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ManagePortalScene({ events = undefined, rootScene, material, parent, priority, worldUnits }: {
|
|
70
|
+
events?: boolean; rootScene: THREE.Scene; material: RefObject<PortalMaterialInstance>; parent: RefObject<THREE.Mesh<THREE.BufferGeometry>>;
|
|
71
|
+
priority: number; worldUnits: boolean;
|
|
72
|
+
}) @{
|
|
73
|
+
const scene = useThree((state) => state.scene); const setEvents = useThree((state) => state.setEvents);
|
|
74
|
+
const buffer1 = useFBO(); const buffer2 = useFBO();
|
|
75
|
+
useLayoutEffect(() => { scene.matrixAutoUpdate = false; }, []);
|
|
76
|
+
useEffect(() => { if (events !== undefined) setEvents({ enabled: events }); }, [events]);
|
|
77
|
+
const [quad, blend] = useMemo(() => {
|
|
78
|
+
const blend = { value: 0 };
|
|
79
|
+
const quad = new FullScreenQuad(new THREE.ShaderMaterial({
|
|
80
|
+
uniforms: { a: { value: buffer1.texture }, b: { value: buffer2.texture }, blend },
|
|
81
|
+
vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
|
|
82
|
+
fragmentShader: `uniform sampler2D a; uniform sampler2D b; uniform float blend; varying vec2 vUv; #include <packing>
|
|
83
|
+
void main() { vec4 ta = texture2D(a, vUv); vec4 tb = texture2D(b, vUv); gl_FragColor = mix(tb, ta, blend); #include <tonemapping_fragment> #include <colorspace_fragment> }`,
|
|
84
|
+
}) as any);
|
|
85
|
+
return [quad, blend] as const;
|
|
86
|
+
}, []);
|
|
87
|
+
useFrame((state) => {
|
|
88
|
+
if (!parent.current) return;
|
|
89
|
+
if (!worldUnits) {
|
|
90
|
+
if (priority && material.current?.blend === 1) parent.current.updateWorldMatrix(true, false);
|
|
91
|
+
scene.matrixWorld.copy(parent.current.matrixWorld);
|
|
92
|
+
} else scene.matrixWorld.identity();
|
|
93
|
+
if (priority) {
|
|
94
|
+
if (material.current!.blend > 0 && material.current!.blend < 1) {
|
|
95
|
+
blend.value = material.current!.blend;
|
|
96
|
+
state.gl.setRenderTarget(buffer1); state.gl.render(scene, state.camera);
|
|
97
|
+
state.gl.setRenderTarget(buffer2); state.gl.render(rootScene, state.camera);
|
|
98
|
+
state.gl.setRenderTarget(null); quad.render(state.gl as any);
|
|
99
|
+
} else if (material.current!.blend === 1) state.gl.render(scene, state.camera);
|
|
100
|
+
}
|
|
101
|
+
}, priority);
|
|
102
|
+
null
|
|
103
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { extend, type ThreeElements, type ThreeRef, useFrame, useInstanceHandle, useThree } from '@octanejs/three';
|
|
3
|
+
import { useCallback, useImperativeHandle, useMemo, useRef, useState } from '@octanejs/three/renderer';
|
|
4
|
+
import { DepthFormat, DepthTexture, HalfFloatType, LinearFilter, Matrix4, PerspectiveCamera, Plane, UnsignedShortType, Vector3, Vector4, WebGLRenderTarget, type Texture } from 'three';
|
|
5
|
+
import { BlurPass } from '../materials/BlurPass.js';
|
|
6
|
+
import { MeshReflectorMaterialImpl } from '../materials/MeshReflectorMaterial.js';
|
|
7
|
+
|
|
8
|
+
const MeshReflectorMaterialNode = extend(MeshReflectorMaterialImpl);
|
|
9
|
+
|
|
10
|
+
export type MeshReflectorMaterialProps = ThreeElements['meshStandardMaterial'] & {
|
|
11
|
+
resolution?: number; blur?: [number, number] | number; reflectorOffset?: number; mixBlur?: number; mixStrength?: number;
|
|
12
|
+
minDepthThreshold?: number; maxDepthThreshold?: number; depthScale?: number; depthToBlurRatioBias?: number; mirror?: number;
|
|
13
|
+
distortion?: number; distortionMap?: Texture; mixContrast?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function MeshReflectorMaterial({
|
|
17
|
+
mixBlur = 0, mixStrength = 1, resolution = 256, blur: blurInput = [0, 0], minDepthThreshold = 0.9,
|
|
18
|
+
maxDepthThreshold = 1, depthScale = 0, depthToBlurRatioBias = 0.25, mirror = 0, distortion = 1,
|
|
19
|
+
mixContrast = 1, distortionMap, reflectorOffset = 0, ref, ...props
|
|
20
|
+
}: MeshReflectorMaterialProps & { ref?: ThreeRef<MeshReflectorMaterialImpl> }) @{
|
|
21
|
+
const gl = useThree((state) => state.gl); const camera = useThree((state) => state.camera); const scene = useThree((state) => state.scene);
|
|
22
|
+
const renderer = gl as any;
|
|
23
|
+
const blur = Array.isArray(blurInput) ? blurInput : [blurInput, blurInput];
|
|
24
|
+
const [blurX, blurY] = blur; const hasBlur = blurX + blurY > 0;
|
|
25
|
+
const materialRef = useRef<MeshReflectorMaterialImpl | null>(null); const instance = useInstanceHandle(materialRef);
|
|
26
|
+
useImperativeHandle((ref ?? null) as any, () => materialRef.current!, []);
|
|
27
|
+
const [reflectorPlane] = useState(() => new Plane()); const [normal] = useState(() => new Vector3());
|
|
28
|
+
const [reflectorWorldPosition] = useState(() => new Vector3()); const [cameraWorldPosition] = useState(() => new Vector3());
|
|
29
|
+
const [rotationMatrix] = useState(() => new Matrix4()); const [lookAtPosition] = useState(() => new Vector3(0, 0, -1));
|
|
30
|
+
const [clipPlane] = useState(() => new Vector4()); const [view] = useState(() => new Vector3()); const [target] = useState(() => new Vector3());
|
|
31
|
+
const [q] = useState(() => new Vector4()); const [textureMatrix] = useState(() => new Matrix4()); const [virtualCamera] = useState(() => new PerspectiveCamera());
|
|
32
|
+
const beforeRender = useCallback(() => {
|
|
33
|
+
const parent = instance.current?.parent?.object as any; if (!parent) return;
|
|
34
|
+
reflectorWorldPosition.setFromMatrixPosition(parent.matrixWorld); cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);
|
|
35
|
+
rotationMatrix.extractRotation(parent.matrixWorld); normal.set(0, 0, 1).applyMatrix4(rotationMatrix);
|
|
36
|
+
reflectorWorldPosition.addScaledVector(normal, reflectorOffset); view.subVectors(reflectorWorldPosition, cameraWorldPosition);
|
|
37
|
+
if (view.dot(normal) > 0) return;
|
|
38
|
+
view.reflect(normal).negate().add(reflectorWorldPosition); rotationMatrix.extractRotation(camera.matrixWorld);
|
|
39
|
+
lookAtPosition.set(0, 0, -1).applyMatrix4(rotationMatrix).add(cameraWorldPosition);
|
|
40
|
+
target.subVectors(reflectorWorldPosition, lookAtPosition).reflect(normal).negate().add(reflectorWorldPosition);
|
|
41
|
+
virtualCamera.position.copy(view); virtualCamera.up.set(0, 1, 0).applyMatrix4(rotationMatrix).reflect(normal);
|
|
42
|
+
virtualCamera.lookAt(target); virtualCamera.far = camera.far; virtualCamera.updateMatrixWorld(); virtualCamera.projectionMatrix.copy(camera.projectionMatrix);
|
|
43
|
+
textureMatrix.set(0.5,0,0,0.5, 0,0.5,0,0.5, 0,0,0.5,0.5, 0,0,0,1).multiply(virtualCamera.projectionMatrix).multiply(virtualCamera.matrixWorldInverse).multiply(parent.matrixWorld);
|
|
44
|
+
reflectorPlane.setFromNormalAndCoplanarPoint(normal, reflectorWorldPosition).applyMatrix4(virtualCamera.matrixWorldInverse);
|
|
45
|
+
clipPlane.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant);
|
|
46
|
+
const projection = virtualCamera.projectionMatrix;
|
|
47
|
+
q.x = (Math.sign(clipPlane.x) + projection.elements[8]!) / projection.elements[0]!; q.y = (Math.sign(clipPlane.y) + projection.elements[9]!) / projection.elements[5]!;
|
|
48
|
+
q.z = -1; q.w = (1 + projection.elements[10]!) / projection.elements[14]!; clipPlane.multiplyScalar(2 / clipPlane.dot(q));
|
|
49
|
+
projection.elements[2] = clipPlane.x; projection.elements[6] = clipPlane.y; projection.elements[10] = clipPlane.z + 1; projection.elements[14] = clipPlane.w;
|
|
50
|
+
}, [camera, reflectorOffset]);
|
|
51
|
+
const [fbo1, fbo2, blurpass, reflectorProps] = useMemo(() => {
|
|
52
|
+
const parameters = { minFilter: LinearFilter, magFilter: LinearFilter, type: HalfFloatType };
|
|
53
|
+
const first = new WebGLRenderTarget(resolution, resolution, parameters); first.depthBuffer = true;
|
|
54
|
+
first.depthTexture = new DepthTexture(resolution, resolution); first.depthTexture.format = DepthFormat; first.depthTexture.type = UnsignedShortType;
|
|
55
|
+
const second = new WebGLRenderTarget(resolution, resolution, parameters);
|
|
56
|
+
const pass = new BlurPass({ gl: renderer, resolution, width: blurX, height: blurY, minDepthThreshold, maxDepthThreshold, depthScale, depthToBlurRatioBias });
|
|
57
|
+
return [first, second, pass, { mirror, textureMatrix, mixBlur, tDiffuse: first.texture, tDepth: first.depthTexture, tDiffuseBlur: second.texture, hasBlur, mixStrength, minDepthThreshold, maxDepthThreshold, depthScale, depthToBlurRatioBias, distortion, distortionMap, mixContrast,
|
|
58
|
+
'defines-USE_BLUR': hasBlur ? '' : undefined, 'defines-USE_DEPTH': depthScale > 0 ? '' : undefined, 'defines-USE_DISTORTION': distortionMap ? '' : undefined } as const];
|
|
59
|
+
}, [gl, blurX, blurY, textureMatrix, resolution, mirror, hasBlur, mixBlur, mixStrength, minDepthThreshold, maxDepthThreshold, depthScale, depthToBlurRatioBias, distortion, distortionMap, mixContrast]);
|
|
60
|
+
useFrame(() => {
|
|
61
|
+
const parent = instance.current?.parent?.object as any; if (!parent) return;
|
|
62
|
+
parent.visible = false; const xr = renderer.xr.enabled; const shadow = renderer.shadowMap.autoUpdate; beforeRender(); renderer.xr.enabled = false; renderer.shadowMap.autoUpdate = false;
|
|
63
|
+
renderer.setRenderTarget(fbo1); renderer.state.buffers.depth.setMask(true); if (!renderer.autoClear) renderer.clear(); renderer.render(scene, virtualCamera);
|
|
64
|
+
if (hasBlur) blurpass.render(renderer, fbo1, fbo2); renderer.xr.enabled = xr; renderer.shadowMap.autoUpdate = shadow; parent.visible = true; renderer.setRenderTarget(null);
|
|
65
|
+
});
|
|
66
|
+
<MeshReflectorMaterialNode attach="material" key={`key${reflectorProps['defines-USE_BLUR']}${reflectorProps['defines-USE_DEPTH']}${reflectorProps['defines-USE_DISTORTION']}`} ref={materialRef} {...(reflectorProps as any)} {...(props as any)} />
|
|
67
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { extend, type ThreeElements, useFrame, useInstanceHandle, useThree } from '@octanejs/three';
|
|
3
|
+
import { useLayoutEffect, useMemo, useRef } from '@octanejs/three/renderer';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { MeshBVH, MeshBVHUniformStruct, SAH } from 'three-mesh-bvh';
|
|
6
|
+
import { MeshRefractionMaterialImpl } from '../materials/MeshRefractionMaterial.js';
|
|
7
|
+
|
|
8
|
+
const MeshRefractionMaterialNode = extend(MeshRefractionMaterialImpl);
|
|
9
|
+
|
|
10
|
+
export type MeshRefractionMaterialProps = ThreeElements['shaderMaterial'] & {
|
|
11
|
+
envMap: THREE.CubeTexture | THREE.Texture;
|
|
12
|
+
bounces?: number;
|
|
13
|
+
ior?: number;
|
|
14
|
+
fresnel?: number;
|
|
15
|
+
aberrationStrength?: number;
|
|
16
|
+
color?: THREE.ColorRepresentation;
|
|
17
|
+
fastChroma?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const isCubeTexture = (texture: THREE.CubeTexture | THREE.Texture): texture is THREE.CubeTexture =>
|
|
21
|
+
Boolean((texture as THREE.CubeTexture).isCubeTexture);
|
|
22
|
+
|
|
23
|
+
export function MeshRefractionMaterial({
|
|
24
|
+
aberrationStrength = 0,
|
|
25
|
+
fastChroma = true,
|
|
26
|
+
envMap,
|
|
27
|
+
...props
|
|
28
|
+
}: MeshRefractionMaterialProps) @{
|
|
29
|
+
const material = useRef<InstanceType<typeof MeshRefractionMaterialImpl> | null>(null);
|
|
30
|
+
const instance = useInstanceHandle(material);
|
|
31
|
+
const size = useThree((state) => state.size);
|
|
32
|
+
const defines = useMemo(() => {
|
|
33
|
+
const result: Record<string, string> = {};
|
|
34
|
+
const cubeMap = isCubeTexture(envMap);
|
|
35
|
+
const image = cubeMap ? envMap.image[0] : envMap.image;
|
|
36
|
+
const width = image?.width ?? 1024;
|
|
37
|
+
const cubeSize = width / 4;
|
|
38
|
+
const lodMax = Math.floor(Math.log2(cubeSize));
|
|
39
|
+
const normalizedCubeSize = Math.pow(2, lodMax);
|
|
40
|
+
const packedWidth = 3 * Math.max(normalizedCubeSize, 16 * 7);
|
|
41
|
+
const packedHeight = 4 * normalizedCubeSize;
|
|
42
|
+
if (cubeMap) result.ENVMAP_TYPE_CUBEM = '';
|
|
43
|
+
result.CUBEUV_TEXEL_WIDTH = `${1 / packedWidth}`;
|
|
44
|
+
result.CUBEUV_TEXEL_HEIGHT = `${1 / packedHeight}`;
|
|
45
|
+
result.CUBEUV_MAX_MIP = `${lodMax}.0`;
|
|
46
|
+
if (aberrationStrength > 0) result.CHROMATIC_ABERRATIONS = '';
|
|
47
|
+
if (fastChroma) result.FAST_CHROMA = '';
|
|
48
|
+
return result;
|
|
49
|
+
}, [aberrationStrength, fastChroma]);
|
|
50
|
+
useLayoutEffect(() => {
|
|
51
|
+
const geometry = (instance.current?.parent?.object as THREE.Mesh | undefined)?.geometry;
|
|
52
|
+
if (!geometry || !material.current) return;
|
|
53
|
+
material.current.bvh = new MeshBVHUniformStruct();
|
|
54
|
+
material.current.bvh.updateFrom(new MeshBVH(geometry.clone().toNonIndexed() as any, { strategy: SAH }));
|
|
55
|
+
}, []);
|
|
56
|
+
useFrame(({ camera }) => {
|
|
57
|
+
if (!material.current) return;
|
|
58
|
+
material.current.viewMatrixInverse = camera.matrixWorld;
|
|
59
|
+
material.current.projectionMatrixInverse = camera.projectionMatrixInverse;
|
|
60
|
+
});
|
|
61
|
+
<MeshRefractionMaterialNode
|
|
62
|
+
key={JSON.stringify(defines)}
|
|
63
|
+
defines={defines}
|
|
64
|
+
ref={material}
|
|
65
|
+
resolution={[size.width, size.height]}
|
|
66
|
+
aberrationStrength={aberrationStrength}
|
|
67
|
+
envMap={envMap}
|
|
68
|
+
{...(props as any)}
|
|
69
|
+
/>
|
|
70
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { extend, type ThreeElements, type ThreeRef, useFrame, useInstanceHandle } from '@octanejs/three';
|
|
3
|
+
import { useImperativeHandle, useRef, useState } from '@octanejs/three/renderer';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { MeshTransmissionMaterialImpl } from '../materials/MeshTransmissionMaterial.js';
|
|
6
|
+
import { useFBO } from './Fbo.three.tsrx';
|
|
7
|
+
import { DiscardMaterialImpl } from './simple-materials.three.tsrx';
|
|
8
|
+
|
|
9
|
+
type MeshTransmissionMaterialType = Omit<ThreeElements['meshPhysicalMaterial'], 'args' | 'roughness' | 'thickness' | 'transmission'> & {
|
|
10
|
+
transmission?: number; thickness?: number; roughness?: number; chromaticAberration?: number; anisotropy?: number;
|
|
11
|
+
anisotropicBlur?: number; distortion?: number; distortionScale?: number; temporalDistortion?: number;
|
|
12
|
+
buffer?: THREE.Texture; time?: number; args?: [samples: number, transmissionSampler: boolean];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type MeshTransmissionMaterialProps = Omit<MeshTransmissionMaterialType, 'ref' | 'args'> & {
|
|
16
|
+
transmissionSampler?: boolean; backside?: boolean; backsideThickness?: number; backsideEnvMapIntensity?: number;
|
|
17
|
+
resolution?: number; backsideResolution?: number; samples?: number; background?: THREE.Texture | THREE.Color;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type MaterialInstance = MeshTransmissionMaterialImpl & {
|
|
21
|
+
time: number; buffer: THREE.Texture; _transmission: number; anisotropicBlur: number; chromaticAberration: number;
|
|
22
|
+
distortion: number; distortionScale: number; temporalDistortion: number;
|
|
23
|
+
};
|
|
24
|
+
const MeshTransmissionMaterialNode = extend(MeshTransmissionMaterialImpl);
|
|
25
|
+
|
|
26
|
+
export function MeshTransmissionMaterial({
|
|
27
|
+
buffer, transmissionSampler = false, backside = false, side = THREE.FrontSide, transmission = 1, thickness = 0,
|
|
28
|
+
backsideThickness = 0, backsideEnvMapIntensity = 1, samples = 10, resolution, backsideResolution, background,
|
|
29
|
+
anisotropy, anisotropicBlur, ref: forwardedRef, ...props
|
|
30
|
+
}: MeshTransmissionMaterialProps & { ref?: ThreeRef<MaterialInstance> }) @{
|
|
31
|
+
const ref = useRef<MaterialInstance | null>(null);
|
|
32
|
+
const instance = useInstanceHandle(ref);
|
|
33
|
+
const [discardMaterial] = useState(() => new DiscardMaterialImpl());
|
|
34
|
+
const fboBack = useFBO(backsideResolution || resolution);
|
|
35
|
+
const fboMain = useFBO(resolution);
|
|
36
|
+
useFrame((state) => {
|
|
37
|
+
ref.current!.time = state.clock.elapsedTime;
|
|
38
|
+
if (ref.current!.buffer !== fboMain.texture || transmissionSampler) return;
|
|
39
|
+
const parent = instance.current?.parent?.object as THREE.Mesh<THREE.BufferGeometry, MaterialInstance | THREE.ShaderMaterial> | undefined;
|
|
40
|
+
if (!parent) return;
|
|
41
|
+
const oldTone = state.gl.toneMapping;
|
|
42
|
+
const oldBackground = state.scene.background;
|
|
43
|
+
const oldEnvMapIntensity = ref.current!.envMapIntensity;
|
|
44
|
+
state.gl.toneMapping = THREE.NoToneMapping;
|
|
45
|
+
if (background) state.scene.background = background;
|
|
46
|
+
parent.material = discardMaterial;
|
|
47
|
+
if (backside) {
|
|
48
|
+
state.gl.setRenderTarget(fboBack); state.gl.render(state.scene, state.camera);
|
|
49
|
+
parent.material = ref.current!; ref.current!.buffer = fboBack.texture; ref.current!.thickness = backsideThickness;
|
|
50
|
+
ref.current!.side = THREE.BackSide; ref.current!.envMapIntensity = backsideEnvMapIntensity;
|
|
51
|
+
}
|
|
52
|
+
state.gl.setRenderTarget(fboMain); state.gl.render(state.scene, state.camera);
|
|
53
|
+
parent.material = ref.current!; ref.current!.thickness = thickness; ref.current!.side = side;
|
|
54
|
+
ref.current!.buffer = fboMain.texture; ref.current!.envMapIntensity = oldEnvMapIntensity;
|
|
55
|
+
state.scene.background = oldBackground; state.gl.setRenderTarget(null); state.gl.toneMapping = oldTone;
|
|
56
|
+
});
|
|
57
|
+
useImperativeHandle((forwardedRef ?? null) as any, () => ref.current!, [samples, transmissionSampler]);
|
|
58
|
+
<MeshTransmissionMaterialNode
|
|
59
|
+
attach="material"
|
|
60
|
+
key={`${samples}:${transmissionSampler}`}
|
|
61
|
+
args={[samples, transmissionSampler]}
|
|
62
|
+
ref={ref}
|
|
63
|
+
{...(props as any)}
|
|
64
|
+
buffer={(buffer || fboMain.texture) as any}
|
|
65
|
+
_transmission={transmission}
|
|
66
|
+
anisotropicBlur={anisotropicBlur ?? anisotropy}
|
|
67
|
+
transmission={transmissionSampler ? transmission : 0}
|
|
68
|
+
thickness={thickness}
|
|
69
|
+
side={side}
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type ThreeElements, type ThreeRef, useFrame, useInstanceHandle, useThree } from '@octanejs/three';
|
|
3
|
+
import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, type RefObject } from '@octanejs/three/renderer';
|
|
4
|
+
import { createContext, type OctaneNode, useContext } from 'octane';
|
|
5
|
+
import { easing, misc } from 'maath';
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
|
|
8
|
+
export type MotionPathProps = ThreeElements['group'] & {
|
|
9
|
+
curves?: THREE.Curve<THREE.Vector3>[];
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
debugColor?: THREE.ColorRepresentation;
|
|
12
|
+
object?: RefObject<THREE.Object3D>;
|
|
13
|
+
focus?: [x: number, y: number, z: number] | RefObject<THREE.Object3D>;
|
|
14
|
+
loop?: boolean;
|
|
15
|
+
offset?: number;
|
|
16
|
+
smooth?: boolean | number;
|
|
17
|
+
eps?: number;
|
|
18
|
+
damping?: number;
|
|
19
|
+
focusDamping?: number;
|
|
20
|
+
maxSpeed?: number;
|
|
21
|
+
children?: OctaneNode;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type MotionState = {
|
|
25
|
+
current: number;
|
|
26
|
+
path: THREE.CurvePath<THREE.Vector3>;
|
|
27
|
+
focus: RefObject<THREE.Object3D> | [number, number, number] | undefined;
|
|
28
|
+
object: RefObject<THREE.Object3D>;
|
|
29
|
+
offset: number;
|
|
30
|
+
point: THREE.Vector3;
|
|
31
|
+
tangent: THREE.Vector3;
|
|
32
|
+
next: THREE.Vector3;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type MotionPathRef = THREE.Group & { motion: MotionState };
|
|
36
|
+
|
|
37
|
+
function isObject3DRef(ref: unknown): ref is RefObject<THREE.Object3D> {
|
|
38
|
+
return (ref as RefObject<THREE.Object3D> | undefined)?.current instanceof THREE.Object3D;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MotionContext = createContext<MotionState>(null!);
|
|
42
|
+
|
|
43
|
+
export function useMotion() {
|
|
44
|
+
const context = useContext(MotionContext);
|
|
45
|
+
if (!context) throw new Error('useMotion hook must be used in a MotionPathControls component.');
|
|
46
|
+
return context;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Debug({ points = 50, color = 'black' }: { points?: number; color?: THREE.ColorRepresentation }) @{
|
|
50
|
+
const { path } = useMotion();
|
|
51
|
+
const [dots, setDots] = useState<THREE.Vector3[]>([]);
|
|
52
|
+
const material = useMemo(() => new THREE.MeshBasicMaterial({ color }), [color]);
|
|
53
|
+
const geometry = useMemo(() => new THREE.SphereGeometry(0.025, 16, 16), []);
|
|
54
|
+
const last = useRef<THREE.Curve<THREE.Vector3>[]>([]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (path.curves !== last.current) {
|
|
57
|
+
setDots(path.getPoints(points));
|
|
58
|
+
last.current = path.curves;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
<>
|
|
62
|
+
@for (const dot of dots; key dots.indexOf(dot)) {
|
|
63
|
+
<mesh material={material} geometry={geometry} position={[dot.x, dot.y, dot.z]} />
|
|
64
|
+
}
|
|
65
|
+
</>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function MotionPathControls({
|
|
69
|
+
children,
|
|
70
|
+
curves = [],
|
|
71
|
+
debug = false,
|
|
72
|
+
debugColor = 'black',
|
|
73
|
+
object,
|
|
74
|
+
focus,
|
|
75
|
+
loop = true,
|
|
76
|
+
offset,
|
|
77
|
+
smooth = false,
|
|
78
|
+
eps = 0.00001,
|
|
79
|
+
damping = 0.1,
|
|
80
|
+
focusDamping = 0.1,
|
|
81
|
+
maxSpeed = Infinity,
|
|
82
|
+
ref,
|
|
83
|
+
...props
|
|
84
|
+
}: MotionPathProps & { ref?: ThreeRef<MotionPathRef> }) @{
|
|
85
|
+
const { camera } = useThree();
|
|
86
|
+
const group = useRef<MotionPathRef | null>(null);
|
|
87
|
+
const position = useRef(offset ?? 0);
|
|
88
|
+
const path = useMemo(() => new THREE.CurvePath<THREE.Vector3>(), []);
|
|
89
|
+
const state = useMemo<MotionState>(() => ({
|
|
90
|
+
focus,
|
|
91
|
+
object: isObject3DRef(object) ? object : { current: camera },
|
|
92
|
+
path,
|
|
93
|
+
current: position.current,
|
|
94
|
+
offset: position.current,
|
|
95
|
+
point: new THREE.Vector3(),
|
|
96
|
+
tangent: new THREE.Vector3(),
|
|
97
|
+
next: new THREE.Vector3(),
|
|
98
|
+
}), [focus, object]);
|
|
99
|
+
const instance = useInstanceHandle(group as any);
|
|
100
|
+
useLayoutEffect(() => {
|
|
101
|
+
path.curves = [];
|
|
102
|
+
const nextCurves = curves.length > 0
|
|
103
|
+
? curves
|
|
104
|
+
: instance.current!.children.map((child) => child.object as unknown as THREE.Curve<THREE.Vector3>);
|
|
105
|
+
for (const curve of nextCurves) path.add(curve);
|
|
106
|
+
if (smooth) {
|
|
107
|
+
const points = path.getPoints(typeof smooth === 'number' ? smooth : 1);
|
|
108
|
+
path.curves = [new THREE.CatmullRomCurve3(points)];
|
|
109
|
+
}
|
|
110
|
+
path.updateArcLengths();
|
|
111
|
+
});
|
|
112
|
+
useImperativeHandle((ref ?? null) as any, () => Object.assign(group.current!, { motion: state }), [state]);
|
|
113
|
+
useLayoutEffect(() => {
|
|
114
|
+
position.current = misc.repeat(position.current, 1);
|
|
115
|
+
}, [offset]);
|
|
116
|
+
const vector = useMemo(() => new THREE.Vector3(), []);
|
|
117
|
+
useFrame((_frameState, delta) => {
|
|
118
|
+
const lastOffset = state.offset;
|
|
119
|
+
easing.damp(position, 'current', offset !== undefined ? offset : state.current, damping, delta, maxSpeed, undefined, eps);
|
|
120
|
+
state.offset = loop ? misc.repeat(position.current, 1) : misc.clamp(position.current, 0, 1);
|
|
121
|
+
if (path.getCurveLengths().length === 0) return;
|
|
122
|
+
path.getPointAt(state.offset, state.point);
|
|
123
|
+
path.getTangentAt(state.offset, state.tangent).normalize();
|
|
124
|
+
path.getPointAt(misc.repeat(position.current - (lastOffset - state.offset), 1), state.next);
|
|
125
|
+
const target = isObject3DRef(object) ? object.current! : camera;
|
|
126
|
+
target.position.copy(state.point);
|
|
127
|
+
if (focus) {
|
|
128
|
+
easing.dampLookAt(target, isObject3DRef(focus) ? focus.current!.getWorldPosition(vector) : focus, focusDamping, delta, maxSpeed, undefined, eps);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
<group ref={group as any} {...props}>
|
|
132
|
+
<MotionContext.Provider value={state}>
|
|
133
|
+
{children}
|
|
134
|
+
@if (debug) {
|
|
135
|
+
<Debug color={debugColor} />
|
|
136
|
+
}
|
|
137
|
+
</MotionContext.Provider>
|
|
138
|
+
</group>
|
|
139
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type ThreeElements, useInstanceHandle } from '@octanejs/three';
|
|
3
|
+
import { useLayoutEffect, useRef } from '@octanejs/three/renderer';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
export type MultiMaterialProps = Omit<ThreeElements['group'], 'ref'>;
|
|
7
|
+
|
|
8
|
+
export function MultiMaterial(props: MultiMaterialProps) @{
|
|
9
|
+
const group = useRef<THREE.Group | null>(null);
|
|
10
|
+
const instance = useInstanceHandle(group);
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
const parent = group.current?.parent as THREE.Mesh<
|
|
13
|
+
THREE.BufferGeometry,
|
|
14
|
+
THREE.Material[]
|
|
15
|
+
> | undefined;
|
|
16
|
+
const geometry = parent?.geometry;
|
|
17
|
+
if (!parent || !geometry || !instance.current) return;
|
|
18
|
+
const oldMaterial = parent.material;
|
|
19
|
+
parent.material = instance.current.children.map((child) => child.object as THREE.Material);
|
|
20
|
+
const oldGroups = [...geometry.groups];
|
|
21
|
+
geometry.clearGroups();
|
|
22
|
+
parent.material.forEach((material, index) => {
|
|
23
|
+
if (index < parent.material.length - 1) material.depthWrite = false;
|
|
24
|
+
geometry.addGroup(0, Infinity, index);
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
parent.material = oldMaterial;
|
|
28
|
+
geometry.groups = oldGroups;
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
<group ref={group} {...props} />
|
|
32
|
+
}
|