@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,323 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { useFrame, type ThreeElements, useThree } from '@octanejs/three';
|
|
3
|
+
import { createContext, type OctaneNode, useContext } from 'octane';
|
|
4
|
+
import { useLayoutEffect, useMemo, useRef, useState } from '@octanejs/three/renderer';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
|
|
7
|
+
export type SizeProps = {
|
|
8
|
+
box: THREE.Box3;
|
|
9
|
+
size: THREE.Vector3;
|
|
10
|
+
center: THREE.Vector3;
|
|
11
|
+
distance: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type BoundsApi = {
|
|
15
|
+
getSize: () => SizeProps;
|
|
16
|
+
refresh(object?: THREE.Object3D | THREE.Box3): BoundsApi;
|
|
17
|
+
reset(): BoundsApi;
|
|
18
|
+
moveTo(position: THREE.Vector3 | [number, number, number]): BoundsApi;
|
|
19
|
+
lookAt({
|
|
20
|
+
target,
|
|
21
|
+
up,
|
|
22
|
+
}: {
|
|
23
|
+
target?: THREE.Vector3 | [number, number, number];
|
|
24
|
+
up?: THREE.Vector3 | [number, number, number];
|
|
25
|
+
}): BoundsApi;
|
|
26
|
+
to({
|
|
27
|
+
position,
|
|
28
|
+
target,
|
|
29
|
+
}: {
|
|
30
|
+
position: [number, number, number];
|
|
31
|
+
target: [number, number, number];
|
|
32
|
+
}): BoundsApi;
|
|
33
|
+
fit(): BoundsApi;
|
|
34
|
+
clip(): BoundsApi;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BoundsProps =
|
|
38
|
+
Omit<ThreeElements['group'], 'ref'> & {
|
|
39
|
+
maxDuration?: number;
|
|
40
|
+
margin?: number;
|
|
41
|
+
observe?: boolean;
|
|
42
|
+
fit?: boolean;
|
|
43
|
+
clip?: boolean;
|
|
44
|
+
interpolateFunc?: (time: number) => number;
|
|
45
|
+
onFit?: (data: SizeProps) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type Controls = {
|
|
49
|
+
update(): void;
|
|
50
|
+
target: THREE.Vector3;
|
|
51
|
+
maxDistance: number;
|
|
52
|
+
addEventListener(event: string, callback: (event: any) => void): void;
|
|
53
|
+
removeEventListener(event: string, callback: (event: any) => void): void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type Origin = { camPos: THREE.Vector3; camRot: THREE.Quaternion; camZoom: number };
|
|
57
|
+
type Goal = {
|
|
58
|
+
camPos: THREE.Vector3 | undefined;
|
|
59
|
+
camRot: THREE.Quaternion | undefined;
|
|
60
|
+
camZoom: number | undefined;
|
|
61
|
+
camUp: THREE.Vector3 | undefined;
|
|
62
|
+
target: THREE.Vector3 | undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const NONE = 0;
|
|
66
|
+
const START = 1;
|
|
67
|
+
const ACTIVE = 2;
|
|
68
|
+
|
|
69
|
+
const isOrthographic = (camera: THREE.Camera): camera is THREE.OrthographicCamera => Boolean(
|
|
70
|
+
camera && (camera as THREE.OrthographicCamera).isOrthographicCamera,
|
|
71
|
+
);
|
|
72
|
+
const isBox3 = (value: unknown): value is THREE.Box3 => Boolean(
|
|
73
|
+
value && (value as THREE.Box3).isBox3,
|
|
74
|
+
);
|
|
75
|
+
const defaultInterpolation = (time: number) => 1 - Math.exp(-5 * time) + 0.007 * time;
|
|
76
|
+
|
|
77
|
+
const BoundsContext = createContext<BoundsApi>(null!);
|
|
78
|
+
|
|
79
|
+
export function Bounds({
|
|
80
|
+
children,
|
|
81
|
+
maxDuration = 1,
|
|
82
|
+
margin = 1.2,
|
|
83
|
+
observe,
|
|
84
|
+
fit,
|
|
85
|
+
clip,
|
|
86
|
+
interpolateFunc = defaultInterpolation,
|
|
87
|
+
onFit,
|
|
88
|
+
}: BoundsProps) @{
|
|
89
|
+
const ref = useRef<THREE.Group | null>(null);
|
|
90
|
+
const state = useThree();
|
|
91
|
+
const controls = useThree((value) => value.controls as unknown as Controls | undefined);
|
|
92
|
+
const onFitRef = useRef<((data: SizeProps) => void) | undefined>(onFit);
|
|
93
|
+
onFitRef.current = onFit;
|
|
94
|
+
const origin = useRef<Origin>({
|
|
95
|
+
camPos: new THREE.Vector3(),
|
|
96
|
+
camRot: new THREE.Quaternion(),
|
|
97
|
+
camZoom: 1,
|
|
98
|
+
});
|
|
99
|
+
const goal = useRef<Goal>({
|
|
100
|
+
camPos: undefined,
|
|
101
|
+
camRot: undefined,
|
|
102
|
+
camZoom: undefined,
|
|
103
|
+
camUp: undefined,
|
|
104
|
+
target: undefined,
|
|
105
|
+
});
|
|
106
|
+
const animationState = useRef(NONE);
|
|
107
|
+
const time = useRef(0);
|
|
108
|
+
const [box] = useState(() => new THREE.Box3());
|
|
109
|
+
const api = useMemo<BoundsApi>(() => {
|
|
110
|
+
function getSize() {
|
|
111
|
+
const boxSize = box.getSize(new THREE.Vector3());
|
|
112
|
+
const center = box.getCenter(new THREE.Vector3());
|
|
113
|
+
const maxSize = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
|
114
|
+
const fitHeightDistance = isOrthographic(state.camera)
|
|
115
|
+
? maxSize * 4
|
|
116
|
+
: maxSize / (2 * Math.atan((Math.PI * state.camera.fov) / 360));
|
|
117
|
+
const fitWidthDistance = isOrthographic(state.camera)
|
|
118
|
+
? maxSize * 4
|
|
119
|
+
: fitHeightDistance / state.camera.aspect;
|
|
120
|
+
return {
|
|
121
|
+
box,
|
|
122
|
+
size: boxSize,
|
|
123
|
+
center,
|
|
124
|
+
distance: margin * Math.max(fitHeightDistance, fitWidthDistance),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
getSize,
|
|
129
|
+
refresh(object?: THREE.Object3D | THREE.Box3) {
|
|
130
|
+
if (isBox3(object)) box.copy(object);
|
|
131
|
+
else {
|
|
132
|
+
const target = object || ref.current;
|
|
133
|
+
if (!target) return this;
|
|
134
|
+
target.updateWorldMatrix(true, true);
|
|
135
|
+
box.setFromObject(target);
|
|
136
|
+
}
|
|
137
|
+
if (box.isEmpty()) {
|
|
138
|
+
const max = state.camera.position.length() || 10;
|
|
139
|
+
box.setFromCenterAndSize(new THREE.Vector3(), new THREE.Vector3(max, max, max));
|
|
140
|
+
}
|
|
141
|
+
origin.current.camPos.copy(state.camera.position);
|
|
142
|
+
origin.current.camRot.copy(state.camera.quaternion);
|
|
143
|
+
if (isOrthographic(state.camera)) origin.current.camZoom = state.camera.zoom;
|
|
144
|
+
goal.current.camPos = undefined;
|
|
145
|
+
goal.current.camRot = undefined;
|
|
146
|
+
goal.current.camZoom = undefined;
|
|
147
|
+
goal.current.camUp = undefined;
|
|
148
|
+
goal.current.target = undefined;
|
|
149
|
+
return this;
|
|
150
|
+
},
|
|
151
|
+
reset() {
|
|
152
|
+
const { center, distance } = getSize();
|
|
153
|
+
const direction = state.camera.position.clone().sub(center).normalize();
|
|
154
|
+
goal.current.camPos = center.clone().addScaledVector(direction, distance);
|
|
155
|
+
goal.current.target = center.clone();
|
|
156
|
+
const matrix = new THREE.Matrix4().lookAt(
|
|
157
|
+
goal.current.camPos,
|
|
158
|
+
goal.current.target,
|
|
159
|
+
state.camera.up,
|
|
160
|
+
);
|
|
161
|
+
goal.current.camRot = new THREE.Quaternion().setFromRotationMatrix(matrix);
|
|
162
|
+
animationState.current = START;
|
|
163
|
+
time.current = 0;
|
|
164
|
+
return this;
|
|
165
|
+
},
|
|
166
|
+
moveTo(position) {
|
|
167
|
+
goal.current.camPos = Array.isArray(position)
|
|
168
|
+
? new THREE.Vector3(...position)
|
|
169
|
+
: position.clone();
|
|
170
|
+
animationState.current = START;
|
|
171
|
+
time.current = 0;
|
|
172
|
+
return this;
|
|
173
|
+
},
|
|
174
|
+
lookAt({ target, up }) {
|
|
175
|
+
goal.current.target = Array.isArray(target)
|
|
176
|
+
? new THREE.Vector3(...target)
|
|
177
|
+
: target!.clone();
|
|
178
|
+
goal.current.camUp = up
|
|
179
|
+
? Array.isArray(up)
|
|
180
|
+
? new THREE.Vector3(...up)
|
|
181
|
+
: up.clone()
|
|
182
|
+
: state.camera.up.clone();
|
|
183
|
+
const matrix = new THREE.Matrix4().lookAt(
|
|
184
|
+
goal.current.camPos || state.camera.position,
|
|
185
|
+
goal.current.target,
|
|
186
|
+
goal.current.camUp,
|
|
187
|
+
);
|
|
188
|
+
goal.current.camRot = new THREE.Quaternion().setFromRotationMatrix(matrix);
|
|
189
|
+
animationState.current = START;
|
|
190
|
+
time.current = 0;
|
|
191
|
+
return this;
|
|
192
|
+
},
|
|
193
|
+
to({ position, target }) {
|
|
194
|
+
return this.moveTo(position).lookAt({ target });
|
|
195
|
+
},
|
|
196
|
+
fit() {
|
|
197
|
+
if (!isOrthographic(state.camera)) return this.reset();
|
|
198
|
+
let maxHeight = 0;
|
|
199
|
+
let maxWidth = 0;
|
|
200
|
+
const vertices = [
|
|
201
|
+
new THREE.Vector3(box.min.x, box.min.y, box.min.z),
|
|
202
|
+
new THREE.Vector3(box.min.x, box.max.y, box.min.z),
|
|
203
|
+
new THREE.Vector3(box.min.x, box.min.y, box.max.z),
|
|
204
|
+
new THREE.Vector3(box.min.x, box.max.y, box.max.z),
|
|
205
|
+
new THREE.Vector3(box.max.x, box.max.y, box.max.z),
|
|
206
|
+
new THREE.Vector3(box.max.x, box.max.y, box.min.z),
|
|
207
|
+
new THREE.Vector3(box.max.x, box.min.y, box.max.z),
|
|
208
|
+
new THREE.Vector3(box.max.x, box.min.y, box.min.z),
|
|
209
|
+
];
|
|
210
|
+
const position = goal.current.camPos || state.camera.position;
|
|
211
|
+
const target = goal.current.target || controls?.target;
|
|
212
|
+
const up = goal.current.camUp || state.camera.up;
|
|
213
|
+
const inverse =
|
|
214
|
+
target
|
|
215
|
+
? new THREE.Matrix4().lookAt(position, target, up).setPosition(position).invert()
|
|
216
|
+
: state.camera.matrixWorldInverse;
|
|
217
|
+
for (const vertex of vertices) {
|
|
218
|
+
vertex.applyMatrix4(inverse);
|
|
219
|
+
maxHeight = Math.max(maxHeight, Math.abs(vertex.y));
|
|
220
|
+
maxWidth = Math.max(maxWidth, Math.abs(vertex.x));
|
|
221
|
+
}
|
|
222
|
+
maxHeight *= 2;
|
|
223
|
+
maxWidth *= 2;
|
|
224
|
+
const zoomForHeight = (state.camera.top - state.camera.bottom) / maxHeight;
|
|
225
|
+
const zoomForWidth = (state.camera.right - state.camera.left) / maxWidth;
|
|
226
|
+
goal.current.camZoom = Math.min(zoomForHeight, zoomForWidth) / margin;
|
|
227
|
+
animationState.current = START;
|
|
228
|
+
time.current = 0;
|
|
229
|
+
onFitRef.current?.(this.getSize());
|
|
230
|
+
return this;
|
|
231
|
+
},
|
|
232
|
+
clip() {
|
|
233
|
+
const { distance } = getSize();
|
|
234
|
+
state.camera.near = distance / 100;
|
|
235
|
+
state.camera.far = distance * 100;
|
|
236
|
+
state.camera.updateProjectionMatrix();
|
|
237
|
+
if (controls) {
|
|
238
|
+
controls.maxDistance = distance * 10;
|
|
239
|
+
controls.update();
|
|
240
|
+
}
|
|
241
|
+
state.invalidate();
|
|
242
|
+
return this;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}, [box, state.camera, controls, margin, state.invalidate]);
|
|
246
|
+
useLayoutEffect(() => {
|
|
247
|
+
if (!controls) return;
|
|
248
|
+
const callback = () => {
|
|
249
|
+
if (goal.current.target && animationState.current !== NONE) {
|
|
250
|
+
const front = new THREE.Vector3().setFromMatrixColumn(state.camera.matrix, 2);
|
|
251
|
+
const distance0 = origin.current.camPos.distanceTo(controls.target);
|
|
252
|
+
const distance1 = (goal.current.camPos || origin.current.camPos).distanceTo(
|
|
253
|
+
goal.current.target,
|
|
254
|
+
);
|
|
255
|
+
const distance = (1 - time.current) * distance0 + time.current * distance1;
|
|
256
|
+
controls.target.copy(state.camera.position).addScaledVector(front, -distance);
|
|
257
|
+
controls.update();
|
|
258
|
+
}
|
|
259
|
+
animationState.current = NONE;
|
|
260
|
+
};
|
|
261
|
+
controls.addEventListener('start', callback);
|
|
262
|
+
return () => controls.removeEventListener('start', callback);
|
|
263
|
+
}, [controls]);
|
|
264
|
+
const count = useRef(0);
|
|
265
|
+
useLayoutEffect(() => {
|
|
266
|
+
if (observe || count.current++ === 0) {
|
|
267
|
+
api.refresh();
|
|
268
|
+
if (fit) api.reset().fit();
|
|
269
|
+
if (clip) api.clip();
|
|
270
|
+
}
|
|
271
|
+
}, [state.size, clip, fit, observe, state.camera, controls]);
|
|
272
|
+
useFrame((_, delta) => {
|
|
273
|
+
if (animationState.current === START) {
|
|
274
|
+
animationState.current = ACTIVE;
|
|
275
|
+
state.invalidate();
|
|
276
|
+
} else if (animationState.current === ACTIVE) {
|
|
277
|
+
time.current += delta / maxDuration;
|
|
278
|
+
if (time.current >= 1) {
|
|
279
|
+
if (goal.current.camPos) state.camera.position.copy(goal.current.camPos);
|
|
280
|
+
if (goal.current.camRot) state.camera.quaternion.copy(goal.current.camRot);
|
|
281
|
+
if (goal.current.camUp) state.camera.up.copy(goal.current.camUp);
|
|
282
|
+
if (goal.current.camZoom && isOrthographic(state.camera)) {
|
|
283
|
+
state.camera.zoom = goal.current.camZoom;
|
|
284
|
+
}
|
|
285
|
+
state.camera.updateMatrixWorld();
|
|
286
|
+
state.camera.updateProjectionMatrix();
|
|
287
|
+
if (controls && goal.current.target) {
|
|
288
|
+
controls.target.copy(goal.current.target);
|
|
289
|
+
controls.update();
|
|
290
|
+
}
|
|
291
|
+
animationState.current = NONE;
|
|
292
|
+
} else {
|
|
293
|
+
const amount = interpolateFunc(time.current);
|
|
294
|
+
if (goal.current.camPos) {
|
|
295
|
+
state.camera.position.lerpVectors(origin.current.camPos, goal.current.camPos, amount);
|
|
296
|
+
}
|
|
297
|
+
if (goal.current.camRot) {
|
|
298
|
+
state.camera.quaternion.slerpQuaternions(
|
|
299
|
+
origin.current.camRot,
|
|
300
|
+
goal.current.camRot,
|
|
301
|
+
amount,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (goal.current.camUp) state.camera.up.set(0, 1, 0).applyQuaternion(
|
|
305
|
+
state.camera.quaternion,
|
|
306
|
+
);
|
|
307
|
+
if (goal.current.camZoom && isOrthographic(state.camera)) {
|
|
308
|
+
state.camera.zoom = (1 - amount) * origin.current.camZoom + amount * goal.current.camZoom;
|
|
309
|
+
}
|
|
310
|
+
state.camera.updateMatrixWorld();
|
|
311
|
+
state.camera.updateProjectionMatrix();
|
|
312
|
+
}
|
|
313
|
+
state.invalidate();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
<group ref={ref}>
|
|
317
|
+
<BoundsContext.Provider value={api}>{children as OctaneNode}</BoundsContext.Provider>
|
|
318
|
+
</group>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function useBounds(): BoundsApi {
|
|
322
|
+
return useContext(BoundsContext);
|
|
323
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type RefObject, type ThreeElements, type ThreeRef, useThree } from '@octanejs/three';
|
|
3
|
+
import { useEffect, useMemo, useRef } from '@octanejs/three/renderer';
|
|
4
|
+
import { Group, Mesh } from 'three';
|
|
5
|
+
import {
|
|
6
|
+
acceleratedRaycast,
|
|
7
|
+
computeBoundsTree,
|
|
8
|
+
disposeBoundsTree,
|
|
9
|
+
SAH,
|
|
10
|
+
type SplitStrategy,
|
|
11
|
+
} from 'three-mesh-bvh';
|
|
12
|
+
import { slotDefault } from './compiler-slot.js';
|
|
13
|
+
|
|
14
|
+
const acceleratedRaycastCompat = acceleratedRaycast as unknown as Mesh['raycast'];
|
|
15
|
+
const computeBoundsTreeCompat = computeBoundsTree as any;
|
|
16
|
+
const disposeBoundsTreeCompat = disposeBoundsTree as any;
|
|
17
|
+
|
|
18
|
+
export interface BVHOptions {
|
|
19
|
+
strategy?: SplitStrategy;
|
|
20
|
+
verbose?: boolean;
|
|
21
|
+
setBoundingBox?: boolean;
|
|
22
|
+
maxDepth?: number;
|
|
23
|
+
maxLeafTris?: number;
|
|
24
|
+
indirect?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type BvhProps =
|
|
28
|
+
BVHOptions & ThreeElements['group'] & {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
firstHitOnly?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
|
|
34
|
+
if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
|
|
35
|
+
else if (typeof ref === 'function') ref(value);
|
|
36
|
+
else if (ref && 'current' in ref) ref.current = value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useBVH(mesh: RefObject<Mesh | undefined>, options?: BVHOptions) {
|
|
40
|
+
options = slotDefault(options, undefined);
|
|
41
|
+
const resolved = {
|
|
42
|
+
strategy: SAH,
|
|
43
|
+
verbose: false,
|
|
44
|
+
setBoundingBox: true,
|
|
45
|
+
maxDepth: 40,
|
|
46
|
+
maxLeafTris: 10,
|
|
47
|
+
indirect: false,
|
|
48
|
+
...options,
|
|
49
|
+
};
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (mesh.current) {
|
|
52
|
+
mesh.current.raycast = acceleratedRaycastCompat;
|
|
53
|
+
const geometry = mesh.current.geometry as any;
|
|
54
|
+
geometry.computeBoundsTree = computeBoundsTreeCompat;
|
|
55
|
+
geometry.disposeBoundsTree = disposeBoundsTreeCompat;
|
|
56
|
+
geometry.computeBoundsTree(resolved);
|
|
57
|
+
return () => {
|
|
58
|
+
if (geometry.boundsTree) geometry.disposeBoundsTree();
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}, [mesh, JSON.stringify(resolved)]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function Bvh({
|
|
65
|
+
enabled = true,
|
|
66
|
+
firstHitOnly = false,
|
|
67
|
+
children,
|
|
68
|
+
strategy = SAH,
|
|
69
|
+
verbose = false,
|
|
70
|
+
setBoundingBox = true,
|
|
71
|
+
maxDepth = 40,
|
|
72
|
+
maxLeafTris = 10,
|
|
73
|
+
indirect = false,
|
|
74
|
+
ref,
|
|
75
|
+
...props
|
|
76
|
+
}: BvhProps & { ref?: ThreeRef<Group> }) @{
|
|
77
|
+
const localRef = useRef<Group | null>(null);
|
|
78
|
+
const raycaster = useThree((state) => state.raycaster);
|
|
79
|
+
const mergedRef = useMemo(
|
|
80
|
+
() => (value: Group | null) => {
|
|
81
|
+
localRef.current = value;
|
|
82
|
+
assignRef(ref, value);
|
|
83
|
+
},
|
|
84
|
+
[ref],
|
|
85
|
+
);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (enabled && localRef.current) {
|
|
88
|
+
const options = { strategy, verbose, setBoundingBox, maxDepth, maxLeafTris, indirect };
|
|
89
|
+
const group = localRef.current;
|
|
90
|
+
const acceleratedMeshes: Mesh[] = [];
|
|
91
|
+
const bvhRaycaster = raycaster as typeof raycaster & { firstHitOnly?: boolean };
|
|
92
|
+
bvhRaycaster.firstHitOnly = firstHitOnly;
|
|
93
|
+
group.traverse((child) => {
|
|
94
|
+
if (
|
|
95
|
+
child instanceof Mesh && !child.geometry.boundsTree &&
|
|
96
|
+
child.raycast === Mesh.prototype.raycast
|
|
97
|
+
) {
|
|
98
|
+
acceleratedMeshes.push(child);
|
|
99
|
+
child.raycast = acceleratedRaycastCompat;
|
|
100
|
+
child.geometry.computeBoundsTree = computeBoundsTreeCompat;
|
|
101
|
+
child.geometry.disposeBoundsTree = disposeBoundsTreeCompat;
|
|
102
|
+
child.geometry.computeBoundsTree(options);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return () => {
|
|
106
|
+
delete bvhRaycaster.firstHitOnly;
|
|
107
|
+
for (const child of acceleratedMeshes) {
|
|
108
|
+
const geometry = child.geometry as any;
|
|
109
|
+
if (geometry.boundsTree) geometry.disposeBoundsTree();
|
|
110
|
+
child.raycast = Mesh.prototype.raycast;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
<group ref={mergedRef} {...props}>{children}</group>
|
|
116
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type ThreeElement, type ThreeRef, useFrame, useThree } from '@octanejs/three';
|
|
3
|
+
import { useEffect, useMemo } from '@octanejs/three/renderer';
|
|
4
|
+
import CameraControlsImpl from 'camera-controls';
|
|
5
|
+
import {
|
|
6
|
+
Box3,
|
|
7
|
+
MathUtils,
|
|
8
|
+
Matrix4,
|
|
9
|
+
OrthographicCamera,
|
|
10
|
+
PerspectiveCamera,
|
|
11
|
+
Quaternion,
|
|
12
|
+
Raycaster,
|
|
13
|
+
Sphere,
|
|
14
|
+
Spherical,
|
|
15
|
+
Vector2,
|
|
16
|
+
Vector3,
|
|
17
|
+
Vector4,
|
|
18
|
+
} from 'three';
|
|
19
|
+
|
|
20
|
+
type CameraControlsEvent = { type: string };
|
|
21
|
+
|
|
22
|
+
export type CameraControlsProps = Omit<
|
|
23
|
+
ThreeElement<typeof CameraControlsImpl>,
|
|
24
|
+
'ref' | 'args' | 'addEventListener' | 'removeEventListener' | 'dispatchEvent'
|
|
25
|
+
> & {
|
|
26
|
+
impl?: typeof CameraControlsImpl;
|
|
27
|
+
camera?: PerspectiveCamera | OrthographicCamera;
|
|
28
|
+
domElement?: HTMLElement;
|
|
29
|
+
makeDefault?: boolean;
|
|
30
|
+
onControlStart?: (event?: { type: 'controlstart' }) => void;
|
|
31
|
+
onControl?: (event?: { type: 'control' }) => void;
|
|
32
|
+
onControlEnd?: (event?: { type: 'controlend' }) => void;
|
|
33
|
+
onTransitionStart?: (event?: { type: 'transitionstart' }) => void;
|
|
34
|
+
onUpdate?: (event?: { type: 'update' }) => void;
|
|
35
|
+
onWake?: (event?: { type: 'wake' }) => void;
|
|
36
|
+
onRest?: (event?: { type: 'rest' }) => void;
|
|
37
|
+
onSleep?: (event?: { type: 'sleep' }) => void;
|
|
38
|
+
onStart?: (event?: { type: 'controlstart' }) => void;
|
|
39
|
+
onEnd?: (event?: { type: 'controlend' }) => void;
|
|
40
|
+
onChange?: (event?: CameraControlsEvent) => void;
|
|
41
|
+
events?: boolean;
|
|
42
|
+
regress?: boolean;
|
|
43
|
+
ref?: ThreeRef<CameraControlsImpl>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function CameraControls({
|
|
47
|
+
impl: SubclassImpl,
|
|
48
|
+
camera,
|
|
49
|
+
domElement,
|
|
50
|
+
makeDefault,
|
|
51
|
+
onControlStart,
|
|
52
|
+
onControl,
|
|
53
|
+
onControlEnd,
|
|
54
|
+
onTransitionStart,
|
|
55
|
+
onUpdate,
|
|
56
|
+
onWake,
|
|
57
|
+
onRest,
|
|
58
|
+
onSleep,
|
|
59
|
+
onStart,
|
|
60
|
+
onEnd,
|
|
61
|
+
onChange,
|
|
62
|
+
regress,
|
|
63
|
+
ref,
|
|
64
|
+
...restProps
|
|
65
|
+
}: CameraControlsProps) @{
|
|
66
|
+
const Impl = SubclassImpl ?? CameraControlsImpl;
|
|
67
|
+
useMemo(() => {
|
|
68
|
+
Impl.install({
|
|
69
|
+
THREE: {
|
|
70
|
+
Box3,
|
|
71
|
+
MathUtils: { clamp: MathUtils.clamp },
|
|
72
|
+
Matrix4,
|
|
73
|
+
Quaternion,
|
|
74
|
+
Raycaster,
|
|
75
|
+
Sphere,
|
|
76
|
+
Spherical,
|
|
77
|
+
Vector2,
|
|
78
|
+
Vector3,
|
|
79
|
+
Vector4,
|
|
80
|
+
} as any,
|
|
81
|
+
});
|
|
82
|
+
}, [Impl]);
|
|
83
|
+
|
|
84
|
+
const defaultCamera = useThree((state) => state.camera);
|
|
85
|
+
const gl = useThree((state) => state.gl);
|
|
86
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
87
|
+
const rendererEvents = useThree((state) => state.events);
|
|
88
|
+
const setEvents = useThree((state) => state.setEvents);
|
|
89
|
+
const set = useThree((state) => state.set);
|
|
90
|
+
const get = useThree((state) => state.get);
|
|
91
|
+
const performance = useThree((state) => state.performance);
|
|
92
|
+
const explicitCamera = camera || defaultCamera;
|
|
93
|
+
const explicitDomElement = (domElement || rendererEvents.connected || gl.domElement) as HTMLElement;
|
|
94
|
+
const controls = useMemo(() => new Impl(explicitCamera as any), [Impl, explicitCamera]);
|
|
95
|
+
|
|
96
|
+
useFrame((_state, delta) => {
|
|
97
|
+
controls.update(delta);
|
|
98
|
+
}, -1);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
controls.connect(explicitDomElement);
|
|
102
|
+
return () => void controls.disconnect();
|
|
103
|
+
}, [explicitDomElement, controls]);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
function invalidateAndRegress(): void {
|
|
107
|
+
invalidate();
|
|
108
|
+
if (regress) performance.regress();
|
|
109
|
+
}
|
|
110
|
+
const controlStart = (event: { type: 'controlstart' }) => {
|
|
111
|
+
invalidateAndRegress();
|
|
112
|
+
onControlStart?.(event);
|
|
113
|
+
onStart?.(event);
|
|
114
|
+
};
|
|
115
|
+
const control = (event: { type: 'control' }) => {
|
|
116
|
+
invalidateAndRegress();
|
|
117
|
+
onControl?.(event);
|
|
118
|
+
onChange?.(event);
|
|
119
|
+
};
|
|
120
|
+
const controlEnd = (event: { type: 'controlend' }) => {
|
|
121
|
+
onControlEnd?.(event);
|
|
122
|
+
onEnd?.(event);
|
|
123
|
+
};
|
|
124
|
+
const transitionStart = (event: { type: 'transitionstart' }) => {
|
|
125
|
+
invalidateAndRegress();
|
|
126
|
+
onTransitionStart?.(event);
|
|
127
|
+
onChange?.(event);
|
|
128
|
+
};
|
|
129
|
+
const update = (event: { type: 'update' }) => {
|
|
130
|
+
invalidateAndRegress();
|
|
131
|
+
onUpdate?.(event);
|
|
132
|
+
onChange?.(event);
|
|
133
|
+
};
|
|
134
|
+
const wake = (event: { type: 'wake' }) => {
|
|
135
|
+
invalidateAndRegress();
|
|
136
|
+
onWake?.(event);
|
|
137
|
+
onChange?.(event);
|
|
138
|
+
};
|
|
139
|
+
const rest = (event: { type: 'rest' }) => onRest?.(event);
|
|
140
|
+
const sleep = (event: { type: 'sleep' }) => onSleep?.(event);
|
|
141
|
+
const listeners = { controlstart: controlStart, control, controlend: controlEnd, transitionstart: transitionStart, update, wake, rest, sleep };
|
|
142
|
+
for (const [type, listener] of Object.entries(listeners)) {
|
|
143
|
+
controls.addEventListener(type as any, listener as any);
|
|
144
|
+
}
|
|
145
|
+
return () => {
|
|
146
|
+
for (const [type, listener] of Object.entries(listeners)) {
|
|
147
|
+
controls.removeEventListener(type as any, listener as any);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}, [
|
|
151
|
+
controls,
|
|
152
|
+
invalidate,
|
|
153
|
+
setEvents,
|
|
154
|
+
regress,
|
|
155
|
+
performance,
|
|
156
|
+
onControlStart,
|
|
157
|
+
onControl,
|
|
158
|
+
onControlEnd,
|
|
159
|
+
onTransitionStart,
|
|
160
|
+
onUpdate,
|
|
161
|
+
onWake,
|
|
162
|
+
onRest,
|
|
163
|
+
onSleep,
|
|
164
|
+
onChange,
|
|
165
|
+
onStart,
|
|
166
|
+
onEnd,
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (makeDefault) {
|
|
171
|
+
const old = get().controls;
|
|
172
|
+
set({ controls: controls as any });
|
|
173
|
+
return () => set({ controls: old });
|
|
174
|
+
}
|
|
175
|
+
}, [makeDefault, controls]);
|
|
176
|
+
|
|
177
|
+
<primitive ref={ref as any} object={controls} {...restProps as any} />
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type CameraControls = CameraControlsImpl;
|
|
181
|
+
export { CameraControlsImpl };
|