@octanejs/drei 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/UPSTREAM.md +92 -0
  4. package/package.json +96 -0
  5. package/src/config.ts +14 -0
  6. package/src/core/AccumulativeShadows.three.tsrx +313 -0
  7. package/src/core/AdaptivePerformance.three.tsrx +49 -0
  8. package/src/core/AsciiRenderer.three.tsrx +61 -0
  9. package/src/core/Bounds.three.tsrx +323 -0
  10. package/src/core/Bvh.three.tsrx +116 -0
  11. package/src/core/CameraControls.three.tsrx +181 -0
  12. package/src/core/CameraShake.three.tsrx +104 -0
  13. package/src/core/Caustics.three.tsrx +107 -0
  14. package/src/core/Center.three.tsrx +145 -0
  15. package/src/core/Clone.three.tsrx +167 -0
  16. package/src/core/Cloud.three.tsrx +314 -0
  17. package/src/core/ComputedAttribute.three.tsrx +27 -0
  18. package/src/core/ContactShadows.three.tsrx +106 -0
  19. package/src/core/CubeTexture.three.tsrx +32 -0
  20. package/src/core/CurveModifier.three.tsrx +37 -0
  21. package/src/core/Decal.three.tsrx +132 -0
  22. package/src/core/Detailed.three.tsrx +28 -0
  23. package/src/core/DetectGPU.three.tsrx +22 -0
  24. package/src/core/Effects.three.tsrx +123 -0
  25. package/src/core/Environment.three.tsrx +220 -0
  26. package/src/core/Example.three.tsrx +56 -0
  27. package/src/core/Fbo.three.tsrx +70 -0
  28. package/src/core/Fbx.three.tsrx +25 -0
  29. package/src/core/Fisheye.three.tsrx +82 -0
  30. package/src/core/GizmoHelper.three.tsrx +115 -0
  31. package/src/core/GizmoViewcube.three.tsrx +82 -0
  32. package/src/core/GizmoViewport.three.tsrx +122 -0
  33. package/src/core/Gltf.three.tsrx +62 -0
  34. package/src/core/GradientTexture.three.tsrx +66 -0
  35. package/src/core/Grid.three.tsrx +170 -0
  36. package/src/core/Helper.three.tsrx +56 -0
  37. package/src/core/Hud.three.tsrx +52 -0
  38. package/src/core/Image.three.tsrx +146 -0
  39. package/src/core/Instances.three.tsrx +332 -0
  40. package/src/core/Ktx2.three.tsrx +59 -0
  41. package/src/core/Lightformer.three.tsrx +75 -0
  42. package/src/core/MarchingCubes.three.tsrx +125 -0
  43. package/src/core/MeshPortalMaterial.three.tsrx +103 -0
  44. package/src/core/MeshReflectorMaterial.three.tsrx +67 -0
  45. package/src/core/MeshRefractionMaterial.three.tsrx +70 -0
  46. package/src/core/MeshTransmissionMaterial.three.tsrx +71 -0
  47. package/src/core/MotionPathControls.three.tsrx +139 -0
  48. package/src/core/MultiMaterial.three.tsrx +32 -0
  49. package/src/core/Outlines.three.tsrx +162 -0
  50. package/src/core/PerformanceMonitor.three.tsrx +144 -0
  51. package/src/core/PointerLockControls.three.tsrx +84 -0
  52. package/src/core/Points.three.tsrx +242 -0
  53. package/src/core/PositionalAudio.three.tsrx +76 -0
  54. package/src/core/Preload.three.tsrx +40 -0
  55. package/src/core/Progress.three.tsrx +47 -0
  56. package/src/core/RenderCubeTexture.three.tsrx +111 -0
  57. package/src/core/RenderTexture.three.tsrx +102 -0
  58. package/src/core/Resize.three.tsrx +59 -0
  59. package/src/core/RoundedBox.three.tsrx +106 -0
  60. package/src/core/Sampler.three.tsrx +116 -0
  61. package/src/core/ScreenSizer.three.tsrx +43 -0
  62. package/src/core/ScreenSpace.three.tsrx +21 -0
  63. package/src/core/Segments.three.tsrx +119 -0
  64. package/src/core/Sky.three.tsrx +63 -0
  65. package/src/core/Sparkles.three.tsrx +145 -0
  66. package/src/core/Splat.three.tsrx +52 -0
  67. package/src/core/SpotLight.three.tsrx +350 -0
  68. package/src/core/SpriteAnimator.three.tsrx +338 -0
  69. package/src/core/Stage.three.tsrx +112 -0
  70. package/src/core/Stars.three.tsrx +103 -0
  71. package/src/core/Stats.three.tsrx +35 -0
  72. package/src/core/StatsGl.three.tsrx +60 -0
  73. package/src/core/Svg.three.tsrx +82 -0
  74. package/src/core/Text.three.tsrx +96 -0
  75. package/src/core/Text3D.three.tsrx +90 -0
  76. package/src/core/Texture.three.tsrx +72 -0
  77. package/src/core/Trail.three.tsrx +167 -0
  78. package/src/core/TrailTexture.three.tsrx +171 -0
  79. package/src/core/TransformControls.three.tsrx +155 -0
  80. package/src/core/VideoTexture.three.tsrx +116 -0
  81. package/src/core/Wireframe.three.tsrx +92 -0
  82. package/src/core/basic-controls.three.tsrx +353 -0
  83. package/src/core/calculateScaleFactor.ts +42 -0
  84. package/src/core/cameras.three.tsrx +230 -0
  85. package/src/core/catalog-resource.ts +12 -0
  86. package/src/core/catalog-textures.three.tsrx +105 -0
  87. package/src/core/compiler-slot.ts +9 -0
  88. package/src/core/effects-utils.ts +8 -0
  89. package/src/core/environment-loader.ts +87 -0
  90. package/src/core/font-resource.ts +31 -0
  91. package/src/core/gltf-loader.ts +33 -0
  92. package/src/core/gradient-types.ts +4 -0
  93. package/src/core/index.ts +295 -0
  94. package/src/core/lines.three.tsrx +329 -0
  95. package/src/core/meshBounds.ts +26 -0
  96. package/src/core/motion-materials.three.tsrx +93 -0
  97. package/src/core/octane-children.ts +18 -0
  98. package/src/core/octane-element.ts +16 -0
  99. package/src/core/resource.ts +29 -0
  100. package/src/core/scene-motion.three.tsrx +183 -0
  101. package/src/core/shaderMaterial.ts +50 -0
  102. package/src/core/shadows.three.tsrx +164 -0
  103. package/src/core/shapes.three.tsrx +170 -0
  104. package/src/core/simple-materials.three.tsrx +124 -0
  105. package/src/core/softShadows.three.tsrx +151 -0
  106. package/src/core/splat-resource.ts +598 -0
  107. package/src/core/useAnimations.three.tsrx +57 -0
  108. package/src/core/useAspect.three.tsrx +10 -0
  109. package/src/core/useBoxProjectedEnv.three.tsrx +113 -0
  110. package/src/core/useCamera.three.tsrx +26 -0
  111. package/src/core/useContextBridge.three.tsrx +25 -0
  112. package/src/core/useDepthBuffer.three.tsrx +35 -0
  113. package/src/core/useEnvironment.three.tsrx +81 -0
  114. package/src/core/useFont.three.tsrx +12 -0
  115. package/src/core/useIntersect.three.tsrx +38 -0
  116. package/src/core/useSpriteLoader.three.tsrx +249 -0
  117. package/src/core/video-texture-resource.ts +14 -0
  118. package/src/index.ts +2 -0
  119. package/src/materials/BlurPass.ts +94 -0
  120. package/src/materials/CausticsMaterials.ts +197 -0
  121. package/src/materials/ConvolutionMaterial.ts +65 -0
  122. package/src/materials/MeshReflectorMaterial.ts +180 -0
  123. package/src/materials/MeshRefractionMaterial.ts +131 -0
  124. package/src/materials/MeshTransmissionMaterial.ts +182 -0
  125. package/src/materials/PortalMaterial.ts +38 -0
  126. package/src/materials/WireframeMaterial.ts +241 -0
  127. package/src/materials/makeSDFGenerator.ts +155 -0
  128. package/src/troika-three-text.d.ts +19 -0
  129. package/src/web/CycleRaycast.three.tsrx +81 -0
  130. package/src/web/DragControls.three.tsrx +82 -0
  131. package/src/web/FaceControls.three.tsrx +178 -0
  132. package/src/web/FaceLandmarker.three.tsrx +48 -0
  133. package/src/web/Facemesh.three.tsrx +464 -0
  134. package/src/web/Html.three.tsrx +390 -0
  135. package/src/web/KeyboardControls.three.tsrx +98 -0
  136. package/src/web/Loader.tsrx +61 -0
  137. package/src/web/PresentationControls.three.tsrx +63 -0
  138. package/src/web/ScrollControls.three.tsrx +120 -0
  139. package/src/web/Select.three.tsrx +155 -0
  140. package/src/web/View.three.tsrx +46 -0
  141. package/src/web/face-landmarker-resource.ts +18 -0
  142. package/src/web/facemesh-data.ts +54 -0
  143. package/src/web/index.ts +41 -0
  144. package/src/web/keyboard-controls-types.ts +33 -0
  145. package/src/web/media-textures.three.tsrx +50 -0
  146. package/src/web/pivotControls/annotation.tsrx +17 -0
  147. package/src/web/pivotControls/context.ts +38 -0
  148. package/src/web/pivotControls/index.three.tsrx +877 -0
  149. package/src/web/scroll-html-root.tsrx +27 -0
  150. package/src/web/useCursor.three.tsrx +19 -0
  151. package/src/web/useGesture.ts +27 -0
  152. package/status.json +14 -0
@@ -0,0 +1,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 };