@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,877 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import {
3
+ type ThreeElements,
4
+ type ThreeEvent,
5
+ type ThreeRef,
6
+ useFrame,
7
+ useThree,
8
+ } from '@octanejs/three';
9
+ import {
10
+ useCallback,
11
+ useContext,
12
+ useImperativeHandle,
13
+ useLayoutEffect,
14
+ useMemo,
15
+ useRef,
16
+ useState,
17
+ } from '@octanejs/three/renderer';
18
+ import type { OctaneNode } from 'octane';
19
+ import * as THREE from 'three';
20
+ import { Html } from '@octanejs/drei';
21
+ import { Line } from '../../core/lines.three.tsrx';
22
+ import { calculateScaleFactor } from '../../core/calculateScaleFactor.js';
23
+ import { PivotAnnotation } from './annotation.tsrx';
24
+ import { context, type Limits, type OnDragStartProps, type PivotContext } from './context.js';
25
+
26
+ const mL0 = new THREE.Matrix4(), mW0 = new THREE.Matrix4(), mP = new THREE.Matrix4(), mPInv = new THREE.Matrix4();
27
+ const mW = new THREE.Matrix4(), mL = new THREE.Matrix4(), mL0Inv = new THREE.Matrix4(), mdL = new THREE.Matrix4(), mG = new THREE.Matrix4();
28
+ const bb = new THREE.Box3(), bbObj = new THREE.Box3(), vCenter = new THREE.Vector3(), vSize = new THREE.Vector3();
29
+ const vAnchorOffset = new THREE.Vector3(), vPosition = new THREE.Vector3(), vScale = new THREE.Vector3();
30
+ const xDir = new THREE.Vector3(1, 0, 0), yDir = new THREE.Vector3(0, 1, 0), zDir = new THREE.Vector3(0, 0, 1);
31
+ const upV = new THREE.Vector3(0, 1, 0), scratch1 = new THREE.Vector3(), scratch2 = new THREE.Vector3();
32
+ const offsetMatrix = new THREE.Matrix4(), rotationMatrix = new THREE.Matrix4(), scaleMatrix = new THREE.Matrix4();
33
+ const ray = new THREE.Ray(), intersection = new THREE.Vector3(), scaleVector = new THREE.Vector3();
34
+
35
+ export type PivotControlsProps =
36
+ Omit<ThreeElements['group'], 'children' | 'ref' | 'scale' | 'rotation'> & {
37
+ ref?: ThreeRef<THREE.Group>;
38
+ enabled?: boolean;
39
+ scale?: number;
40
+ lineWidth?: number;
41
+ fixed?: boolean;
42
+ offset?: [number, number, number];
43
+ rotation?: [number, number, number];
44
+ matrix?: THREE.Matrix4;
45
+ anchor?: [number, number, number];
46
+ autoTransform?: boolean;
47
+ activeAxes?: [boolean, boolean, boolean];
48
+ disableAxes?: boolean;
49
+ disableSliders?: boolean;
50
+ disableRotations?: boolean;
51
+ disableScaling?: boolean;
52
+ translationLimits?: Limits;
53
+ rotationLimits?: Limits;
54
+ scaleLimits?: Limits;
55
+ axisColors?: [string | number, string | number, string | number];
56
+ hoveredColor?: string | number;
57
+ annotations?: boolean;
58
+ annotationsClass?: string;
59
+ onDragStart?: (props: OnDragStartProps) => void;
60
+ onDrag?: (local: THREE.Matrix4, deltaLocal: THREE.Matrix4, world: THREE.Matrix4, deltaWorld: THREE.Matrix4) => void;
61
+ onDragEnd?: () => void;
62
+ depthTest?: boolean;
63
+ renderOrder?: number;
64
+ opacity?: number;
65
+ visible?: boolean;
66
+ userData?: Record<string, any>;
67
+ children?: OctaneNode;
68
+ };
69
+
70
+ function captureTarget(event: ThreeEvent<PointerEvent>, capture: boolean): void {
71
+ const target = event.target as unknown as {
72
+ setPointerCapture(id: number): void;
73
+ releasePointerCapture(id: number): void;
74
+ };
75
+ if (capture) target.setPointerCapture(event.pointerId);
76
+ else target.releasePointerCapture(event.pointerId);
77
+ }
78
+
79
+ function Annotation({
80
+ position,
81
+ divRef,
82
+ className,
83
+ }: {
84
+ position: [number, number, number];
85
+ divRef: { current: HTMLDivElement | null };
86
+ className?: string;
87
+ }) @{
88
+ <Html position={position}>
89
+ <PivotAnnotation ref={divRef} className={className} />
90
+ </Html>
91
+ }
92
+
93
+ function directionMatrix(direction: THREE.Vector3): THREE.Matrix4 {
94
+ return new THREE.Matrix4().makeRotationFromQuaternion(
95
+ new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize()),
96
+ );
97
+ }
98
+
99
+ export function calculateOffset(
100
+ clickPoint: THREE.Vector3,
101
+ normal: THREE.Vector3,
102
+ rayStart: THREE.Vector3,
103
+ rayDir: THREE.Vector3,
104
+ ): number {
105
+ const e1 = normal.dot(normal), e2 = normal.dot(clickPoint) - normal.dot(rayStart), e3 =
106
+ normal.dot(rayDir);
107
+ if (e3 === 0) return -e2 / e1;
108
+ scratch1.copy(rayDir).multiplyScalar(e1 / e3).sub(normal);
109
+ scratch2.copy(rayDir).multiplyScalar(e2 / e3).add(rayStart).sub(clickPoint);
110
+ return -scratch1.dot(scratch2) / scratch1.dot(scratch1);
111
+ }
112
+
113
+ function AxisArrow({ direction, axis }: { direction: THREE.Vector3; axis: 0 | 1 | 2 }) @{
114
+ const config = useContext(context), controls = useThree((state) => state.controls) as {
115
+ enabled: boolean;
116
+ } | undefined;
117
+ const divRef = useRef<HTMLDivElement | null>(null), objectRef = useRef<THREE.Group | null>(null);
118
+ const clickInfo = useRef<{ clickPoint: THREE.Vector3; dir: THREE.Vector3 } | null>(
119
+ null,
120
+ ), offset0 = useRef(0);
121
+ const [hovered, setHovered] = useState(false);
122
+ const matrix = useMemo(() => directionMatrix(direction), [direction]);
123
+ const coneWidth = config.fixed ? (config.lineWidth / config.scale) * 1.6 : config.scale / 20;
124
+ const coneLength = config.fixed ? 0.2 : config.scale / 5;
125
+ const cylinderLength = config.fixed ? 1 - coneLength : config.scale - coneLength;
126
+ const down = useCallback((event: ThreeEvent<PointerEvent>) => {
127
+ if (config.annotations && divRef.current) {
128
+ divRef.current.innerText = config.translation.current[axis].toFixed(2);
129
+ divRef.current.style.display = 'block';
130
+ }
131
+ event.stopPropagation();
132
+ const rotation = new THREE.Matrix4().extractRotation(objectRef.current!.matrixWorld);
133
+ const origin = new THREE.Vector3().setFromMatrixPosition(objectRef.current!.matrixWorld);
134
+ const dir = direction.clone().applyMatrix4(rotation).normalize();
135
+ clickInfo.current = { clickPoint: event.point.clone(), dir };
136
+ offset0.current = config.translation.current[axis];
137
+ config.onDragStart({ component: 'Arrow', axis, origin, directions: [dir] });
138
+ if (controls) controls.enabled = false;
139
+ captureTarget(event, true);
140
+ }, [config, direction, axis, controls]);
141
+ const move = useCallback((event: ThreeEvent<PointerEvent>) => {
142
+ event.stopPropagation();
143
+ if (!hovered) setHovered(true);
144
+ if (!clickInfo.current) return;
145
+ const { clickPoint, dir } = clickInfo.current;
146
+ const [min, max] = config.translationLimits?.[axis] || [undefined, undefined];
147
+ let offset = calculateOffset(clickPoint, dir, event.ray.origin, event.ray.direction);
148
+ if (min !== undefined) offset = Math.max(offset, min - offset0.current);
149
+ if (max !== undefined) offset = Math.min(offset, max - offset0.current);
150
+ config.translation.current[axis] = offset0.current + offset;
151
+ if (config.annotations && divRef.current) divRef.current.innerText =
152
+ config.translation.current[axis].toFixed(2);
153
+ config.onDrag(offsetMatrix.makeTranslation(dir.x * offset, dir.y * offset, dir.z * offset));
154
+ }, [config, axis, hovered]);
155
+ const up = useCallback((event: ThreeEvent<PointerEvent>) => {
156
+ if (divRef.current && config.annotations) divRef.current.style.display = 'none';
157
+ event.stopPropagation();
158
+ clickInfo.current = null;
159
+ config.onDragEnd();
160
+ if (controls) controls.enabled = true;
161
+ captureTarget(event, false);
162
+ }, [config, controls]);
163
+ const color = hovered ? config.hoveredColor : config.axisColors[axis];
164
+ <group ref={objectRef}>
165
+ <group
166
+ matrix={matrix}
167
+ matrixAutoUpdate={false}
168
+ onPointerDown={down}
169
+ onPointerMove={move}
170
+ onPointerUp={up}
171
+ onPointerOut={(event) => {
172
+ event.stopPropagation();
173
+ setHovered(false);
174
+ }}
175
+ >
176
+ @if (config.annotations) {
177
+ <Annotation
178
+ position={[0, -coneLength, 0]}
179
+ divRef={divRef}
180
+ className={config.annotationsClass}
181
+ />
182
+ }
183
+ <mesh
184
+ visible={false}
185
+ position={[0, (cylinderLength + coneLength) / 2, 0]}
186
+ userData={config.userData}
187
+ >
188
+ <cylinderGeometry
189
+ args={[coneWidth * 1.4, coneWidth * 1.4, cylinderLength + coneLength, 8, 1]}
190
+ />
191
+ </mesh>
192
+ <Line
193
+ {...{ raycast: () => null } as any}
194
+ transparent
195
+ depthTest={config.depthTest}
196
+ points={[0, 0, 0, 0, cylinderLength, 0] as any}
197
+ lineWidth={config.lineWidth}
198
+ side={THREE.DoubleSide}
199
+ color={color as any}
200
+ opacity={config.opacity}
201
+ polygonOffset
202
+ renderOrder={config.renderOrder}
203
+ polygonOffsetFactor={-10}
204
+ fog={false}
205
+ />
206
+ <mesh
207
+ {...{ raycast: () => null } as any}
208
+ position={[0, cylinderLength + coneLength / 2, 0]}
209
+ renderOrder={config.renderOrder}
210
+ >
211
+ <coneGeometry args={[coneWidth, coneLength, 24, 1]} />
212
+ <meshBasicMaterial
213
+ transparent
214
+ depthTest={config.depthTest}
215
+ color={color}
216
+ opacity={config.opacity}
217
+ polygonOffset
218
+ polygonOffsetFactor={-10}
219
+ fog={false}
220
+ />
221
+ </mesh>
222
+ </group>
223
+ </group>
224
+ }
225
+
226
+ function decompose(e1: THREE.Vector3, e2: THREE.Vector3, offset: THREE.Vector3): [number, number] {
227
+ const i1 =
228
+ Math.abs(e1.x) >= Math.abs(e1.y) && Math.abs(e1.x) >= Math.abs(e1.z)
229
+ ? 0
230
+ : Math.abs(e1.y) >= Math.abs(e1.x) && Math.abs(e1.y) >= Math.abs(e1.z)
231
+ ? 1
232
+ : 2;
233
+ const order = [0, 1, 2].sort(
234
+ (a, b) => Math.abs(e2.getComponent(b)) - Math.abs(e2.getComponent(a)),
235
+ );
236
+ const i2 =
237
+ i1 === order[0] ? order[1] : order[0];
238
+ const a1 = e1.getComponent(i1), a2 = e1.getComponent(i2), b1 = e2.getComponent(i1), b2 =
239
+ e2.getComponent(i2), c1 = offset.getComponent(i1), c2 = offset.getComponent(i2);
240
+ const y = (c2 - c1 * (a2 / a1)) / (b2 - b1 * (a2 / a1));
241
+ return [(c1 - y * b1) / a1, y];
242
+ }
243
+
244
+ function PlaneSlider({
245
+ dir1,
246
+ dir2,
247
+ axis,
248
+ }: {
249
+ dir1: THREE.Vector3;
250
+ dir2: THREE.Vector3;
251
+ axis: 0 | 1 | 2;
252
+ }) @{
253
+ const config = useContext(context), controls = useThree((state) => state.controls) as {
254
+ enabled: boolean;
255
+ } | undefined;
256
+ const divRef = useRef<HTMLDivElement | null>(null), objectRef = useRef<THREE.Group | null>(
257
+ null,
258
+ ), offsetX0 = useRef(0), offsetY0 = useRef(0);
259
+ const clickInfo = useRef<
260
+ { clickPoint: THREE.Vector3; e1: THREE.Vector3; e2: THREE.Vector3; plane: THREE.Plane } | null
261
+ >(null);
262
+ const [hovered, setHovered] = useState(false);
263
+ const matrix = useMemo(() => {
264
+ const a = dir1.clone().normalize(), b = dir2.clone().normalize();
265
+ return new THREE.Matrix4().makeBasis(a, b, a.clone().cross(b));
266
+ }, [dir1, dir2]);
267
+ const pos1 = config.fixed ? 1 / 7 : config.scale / 7, length = config.fixed
268
+ ? 0.225
269
+ : config.scale * 0.225, color = hovered ? config.hoveredColor : config.axisColors[axis];
270
+ const points = useMemo(
271
+ () => [
272
+ new THREE.Vector3(),
273
+ new THREE.Vector3(0, length, 0),
274
+ new THREE.Vector3(length, length, 0),
275
+ new THREE.Vector3(length, 0, 0),
276
+ new THREE.Vector3(),
277
+ ],
278
+ [length],
279
+ );
280
+ const down = useCallback((event: ThreeEvent<PointerEvent>) => {
281
+ if (config.annotations && divRef.current) {
282
+ divRef.current.innerText = `${config.translation.current[(axis + 1) % 3].toFixed(
283
+ 2,
284
+ )}, ${config.translation.current[(axis + 2) % 3].toFixed(2)}`;
285
+ divRef.current.style.display = 'block';
286
+ }
287
+ event.stopPropagation();
288
+ const origin = new THREE.Vector3().setFromMatrixPosition(objectRef.current!.matrixWorld);
289
+ const e1 = new THREE.Vector3().setFromMatrixColumn(
290
+ objectRef.current!.matrixWorld,
291
+ 0,
292
+ ).normalize(), e2 = new THREE.Vector3().setFromMatrixColumn(
293
+ objectRef.current!.matrixWorld,
294
+ 1,
295
+ ).normalize(), normal = new THREE.Vector3().setFromMatrixColumn(
296
+ objectRef.current!.matrixWorld,
297
+ 2,
298
+ ).normalize();
299
+ clickInfo.current = {
300
+ clickPoint: event.point.clone(),
301
+ e1,
302
+ e2,
303
+ plane: new THREE.Plane().setFromNormalAndCoplanarPoint(normal, origin),
304
+ };
305
+ offsetX0.current = config.translation.current[(axis + 1) % 3];
306
+ offsetY0.current = config.translation.current[(axis + 2) % 3];
307
+ config.onDragStart({ component: 'Slider', axis, origin, directions: [e1, e2, normal] });
308
+ if (controls) controls.enabled = false;
309
+ captureTarget(event, true);
310
+ }, [config, controls, axis]);
311
+ const move = useCallback((event: ThreeEvent<PointerEvent>) => {
312
+ event.stopPropagation();
313
+ if (!hovered) setHovered(true);
314
+ if (!clickInfo.current) return;
315
+ const { clickPoint, e1, e2, plane } = clickInfo.current;
316
+ ray.copy(event.ray);
317
+ if (ray.intersectPlane(plane, intersection) === null) {
318
+ ray.direction.negate();
319
+ ray.intersectPlane(plane, intersection);
320
+ }
321
+ intersection.sub(clickPoint);
322
+ let [x, y] = decompose(e1, e2, intersection);
323
+ const [minX, maxX] =
324
+ config.translationLimits?.[(axis + 1) % 3] || [undefined, undefined], [minY, maxY] =
325
+ config.translationLimits?.[(axis + 2) % 3] || [undefined, undefined];
326
+ if (minX !== undefined) x = Math.max(x, minX - offsetX0.current);
327
+ if (maxX !== undefined) x = Math.min(x, maxX - offsetX0.current);
328
+ if (minY !== undefined) y = Math.max(y, minY - offsetY0.current);
329
+ if (maxY !== undefined) y = Math.min(y, maxY - offsetY0.current);
330
+ config.translation.current[(axis + 1) % 3] = offsetX0.current + x;
331
+ config.translation.current[(axis + 2) % 3] = offsetY0.current + y;
332
+ if (config.annotations && divRef.current) divRef.current.innerText =
333
+ `${config.translation.current[(axis + 1) % 3].toFixed(
334
+ 2,
335
+ )}, ${config.translation.current[(axis + 2) % 3].toFixed(2)}`;
336
+ config.onDrag(
337
+ offsetMatrix.makeTranslation(x * e1.x + y * e2.x, x * e1.y + y * e2.y, x * e1.z + y * e2.z),
338
+ );
339
+ }, [config, axis, hovered]);
340
+ const up = useCallback((event: ThreeEvent<PointerEvent>) => {
341
+ if (config.annotations && divRef.current) divRef.current.style.display = 'none';
342
+ event.stopPropagation();
343
+ clickInfo.current = null;
344
+ config.onDragEnd();
345
+ if (controls) controls.enabled = true;
346
+ captureTarget(event, false);
347
+ }, [config, controls]);
348
+ <group ref={objectRef} matrix={matrix} matrixAutoUpdate={false}>
349
+ @if (config.annotations) {
350
+ <Annotation position={[0, 0, 0]} divRef={divRef} className={config.annotationsClass} />
351
+ }
352
+ <group position={[pos1 * 1.7, pos1 * 1.7, 0]}>
353
+ <mesh
354
+ visible
355
+ onPointerDown={down}
356
+ onPointerMove={move}
357
+ onPointerUp={up}
358
+ onPointerOut={(event) => {
359
+ event.stopPropagation();
360
+ setHovered(false);
361
+ }}
362
+ scale={length}
363
+ userData={config.userData}
364
+ renderOrder={config.renderOrder}
365
+ >
366
+ <planeGeometry />
367
+ <meshBasicMaterial
368
+ transparent
369
+ depthTest={config.depthTest}
370
+ color={color}
371
+ polygonOffset
372
+ polygonOffsetFactor={-10}
373
+ side={THREE.DoubleSide}
374
+ fog={false}
375
+ />
376
+ </mesh>
377
+ <Line
378
+ position={[-length / 2, -length / 2, 0]}
379
+ transparent
380
+ depthTest={config.depthTest}
381
+ points={points}
382
+ lineWidth={config.lineWidth}
383
+ color={color as any}
384
+ opacity={config.opacity}
385
+ polygonOffset
386
+ polygonOffsetFactor={-10}
387
+ userData={config.userData}
388
+ fog={false}
389
+ renderOrder={config.renderOrder}
390
+ />
391
+ </group>
392
+ </group>
393
+ }
394
+
395
+ function angleBetween(
396
+ clickPoint: THREE.Vector3,
397
+ hit: THREE.Vector3,
398
+ origin: THREE.Vector3,
399
+ e1: THREE.Vector3,
400
+ e2: THREE.Vector3,
401
+ ): number {
402
+ const click = scratch1.copy(clickPoint).sub(origin), current = scratch2.copy(hit).sub(origin);
403
+ return Math.atan2(current.dot(e2) / e2.dot(e2), current.dot(e1) / e1.dot(e1)) -
404
+ Math.atan2(click.dot(e2) / e2.dot(e2), click.dot(e1) / e1.dot(e1));
405
+ }
406
+ function minimizeAngle(value: number): number {
407
+ let result =
408
+ value -
409
+ (Math.floor(value / (2 * Math.PI)) + (Math.floor(value / (2 * Math.PI)) < 0 ? 1 : 0)) * 2 *
410
+ Math.PI;
411
+ if (Math.abs(result) < 1e-6) return 0;
412
+ if (result < 0) result += 2 * Math.PI;
413
+ return result;
414
+ }
415
+
416
+ function AxisRotator({
417
+ dir1,
418
+ dir2,
419
+ axis,
420
+ }: {
421
+ dir1: THREE.Vector3;
422
+ dir2: THREE.Vector3;
423
+ axis: 0 | 1 | 2;
424
+ }) @{
425
+ const config = useContext(context), controls = useThree((state) => state.controls) as {
426
+ enabled: boolean;
427
+ } | undefined;
428
+ const divRef = useRef<HTMLDivElement | null>(null), objectRef = useRef<THREE.Group | null>(
429
+ null,
430
+ ), angle0 = useRef(0), angle = useRef(0);
431
+ const clickInfo = useRef<
432
+ | {
433
+ clickPoint: THREE.Vector3;
434
+ origin: THREE.Vector3;
435
+ e1: THREE.Vector3;
436
+ e2: THREE.Vector3;
437
+ normal: THREE.Vector3;
438
+ plane: THREE.Plane;
439
+ }
440
+ | null
441
+ >(null);
442
+ const [hovered, setHovered] = useState(false);
443
+ const matrix = useMemo(() => {
444
+ const a = dir1.clone().normalize(), b = dir2.clone().normalize();
445
+ return new THREE.Matrix4().makeBasis(a, b, a.clone().cross(b));
446
+ }, [dir1, dir2]);
447
+ const r = config.fixed ? 0.65 : config.scale * 0.65;
448
+ const arc = useMemo(
449
+ () => Array.from(
450
+ { length: 33 },
451
+ (_, index) => new THREE.Vector3(Math.cos(index * Math.PI / 64) * r, Math.sin(
452
+ index * Math.PI / 64,
453
+ ) *
454
+ r, 0),
455
+ ),
456
+ [r],
457
+ );
458
+ const down = useCallback((event: ThreeEvent<PointerEvent>) => {
459
+ if (config.annotations && divRef.current) {
460
+ divRef.current.innerText = `${(angle.current * 180 / Math.PI).toFixed(0)}º`;
461
+ divRef.current.style.display = 'block';
462
+ }
463
+ event.stopPropagation();
464
+ const origin = new THREE.Vector3().setFromMatrixPosition(objectRef.current!.matrixWorld), e1 =
465
+ new THREE.Vector3().setFromMatrixColumn(objectRef.current!.matrixWorld, 0).normalize(), e2 =
466
+ new THREE.Vector3().setFromMatrixColumn(
467
+ objectRef.current!.matrixWorld,
468
+ 1,
469
+ ).normalize(), normal = new THREE.Vector3().setFromMatrixColumn(
470
+ objectRef.current!.matrixWorld,
471
+ 2,
472
+ ).normalize();
473
+ clickInfo.current = {
474
+ clickPoint: event.point.clone(),
475
+ origin,
476
+ e1,
477
+ e2,
478
+ normal,
479
+ plane: new THREE.Plane().setFromNormalAndCoplanarPoint(normal, origin),
480
+ };
481
+ config.onDragStart({ component: 'Rotator', axis, origin, directions: [e1, e2, normal] });
482
+ if (controls) controls.enabled = false;
483
+ captureTarget(event, true);
484
+ }, [config, controls, axis]);
485
+ const move = useCallback((event: ThreeEvent<PointerEvent>) => {
486
+ event.stopPropagation();
487
+ if (!hovered) setHovered(true);
488
+ if (!clickInfo.current) return;
489
+ const info = clickInfo.current;
490
+ ray.copy(event.ray);
491
+ if (ray.intersectPlane(info.plane, intersection) === null) {
492
+ ray.direction.negate();
493
+ ray.intersectPlane(info.plane, intersection);
494
+ }
495
+ let delta = angleBetween(info.clickPoint, intersection, info.origin, info.e1, info.e2);
496
+ if (event.shiftKey) delta = Math.round(delta * 180 / Math.PI / 10) * 10 * Math.PI / 180;
497
+ const [min, max] = config.rotationLimits?.[axis] || [undefined, undefined];
498
+ if (min !== undefined && max !== undefined && max - min < 2 * Math.PI) {
499
+ delta = minimizeAngle(delta);
500
+ if (delta > Math.PI) delta -= 2 * Math.PI;
501
+ delta = THREE.MathUtils.clamp(delta, min - angle0.current, max - angle0.current);
502
+ angle.current = angle0.current + delta;
503
+ } else {
504
+ angle.current = minimizeAngle(angle0.current + delta);
505
+ if (angle.current > Math.PI) angle.current -= 2 * Math.PI;
506
+ }
507
+ if (config.annotations && divRef.current) divRef.current.innerText = `${(angle.current * 180 /
508
+ Math.PI).toFixed(0)}º`;
509
+ rotationMatrix.makeRotationAxis(info.normal, delta);
510
+ rotationMatrix.setPosition(
511
+ scratch1.copy(info.origin).applyMatrix4(rotationMatrix).sub(info.origin).negate(),
512
+ );
513
+ config.onDrag(rotationMatrix);
514
+ }, [config, axis, hovered]);
515
+ const up = useCallback((event: ThreeEvent<PointerEvent>) => {
516
+ if (config.annotations && divRef.current) divRef.current.style.display = 'none';
517
+ event.stopPropagation();
518
+ angle0.current = angle.current;
519
+ clickInfo.current = null;
520
+ config.onDragEnd();
521
+ if (controls) controls.enabled = true;
522
+ captureTarget(event, false);
523
+ }, [config, controls]);
524
+ <group
525
+ ref={objectRef}
526
+ onPointerDown={down}
527
+ onPointerMove={move}
528
+ onPointerUp={up}
529
+ onPointerOut={(event) => {
530
+ event.stopPropagation();
531
+ setHovered(false);
532
+ }}
533
+ matrix={matrix}
534
+ matrixAutoUpdate={false}
535
+ >
536
+ @if (config.annotations) {
537
+ <Annotation position={[r, r, 0]} divRef={divRef} className={config.annotationsClass} />
538
+ }
539
+ <Line
540
+ points={arc}
541
+ lineWidth={config.lineWidth * 4}
542
+ visible={false}
543
+ userData={config.userData}
544
+ />
545
+ <Line
546
+ {...{ raycast: () => null } as any}
547
+ transparent
548
+ depthTest={config.depthTest}
549
+ points={arc}
550
+ lineWidth={config.lineWidth}
551
+ side={THREE.DoubleSide}
552
+ color={(hovered ? config.hoveredColor : config.axisColors[axis]) as any}
553
+ opacity={config.opacity}
554
+ polygonOffset
555
+ polygonOffsetFactor={-10}
556
+ renderOrder={config.renderOrder}
557
+ fog={false}
558
+ />
559
+ </group>
560
+ }
561
+
562
+ function ScalingSphere({ direction, axis }: { direction: THREE.Vector3; axis: 0 | 1 | 2 }) @{
563
+ const config = useContext(context), size = useThree((state) => state.size), controls = useThree(
564
+ (state) => state.controls,
565
+ ) as { enabled: boolean } | undefined;
566
+ const divRef = useRef<HTMLDivElement | null>(null), objectRef = useRef<THREE.Group | null>(
567
+ null,
568
+ ), meshRef = useRef<THREE.Mesh | null>(null), scale0 = useRef(1), scaleCurrent = useRef(1);
569
+ const clickInfo = useRef<
570
+ | {
571
+ clickPoint: THREE.Vector3;
572
+ dir: THREE.Vector3;
573
+ localToWorld: THREE.Matrix4;
574
+ worldToLocal: THREE.Matrix4;
575
+ multiplier: number;
576
+ }
577
+ | null
578
+ >(null);
579
+ const [hovered, setHovered] = useState(false);
580
+ const position = config.fixed ? 1.2 : 1.2 * config.scale;
581
+ const radius = config.fixed ? (config.lineWidth / config.scale) * 1.8 : config.scale / 22.5;
582
+ const matrix = useMemo(() => directionMatrix(direction), [direction]);
583
+ const down = useCallback((event: ThreeEvent<PointerEvent>) => {
584
+ if (config.annotations && divRef.current) {
585
+ divRef.current.innerText = scaleCurrent.current.toFixed(2);
586
+ divRef.current.style.display = 'block';
587
+ }
588
+ event.stopPropagation();
589
+ const rotation = new THREE.Matrix4().extractRotation(objectRef.current!.matrixWorld), origin =
590
+ new THREE.Vector3().setFromMatrixPosition(objectRef.current!.matrixWorld), dir =
591
+ direction.clone().applyMatrix4(rotation).normalize(), localToWorld =
592
+ objectRef.current!.matrixWorld.clone();
593
+ clickInfo.current = {
594
+ clickPoint: event.point.clone(),
595
+ dir,
596
+ localToWorld,
597
+ worldToLocal: localToWorld.clone().invert(),
598
+ multiplier: config.fixed
599
+ ? 1 /
600
+ calculateScaleFactor(
601
+ objectRef.current!.getWorldPosition(scratch1),
602
+ config.scale,
603
+ event.camera,
604
+ size,
605
+ )
606
+ : 1,
607
+ };
608
+ config.onDragStart({ component: 'Sphere', axis, origin, directions: [dir] });
609
+ if (controls) controls.enabled = false;
610
+ captureTarget(event, true);
611
+ }, [config, controls, direction, axis, size]);
612
+ const move = useCallback((event: ThreeEvent<PointerEvent>) => {
613
+ event.stopPropagation();
614
+ if (!hovered) setHovered(true);
615
+ if (!clickInfo.current) return;
616
+ const info = clickInfo.current, offsetLocal =
617
+ calculateOffset(info.clickPoint, info.dir, event.ray.origin, event.ray.direction) *
618
+ info.multiplier, offset = config.fixed ? offsetLocal : offsetLocal / config.scale;
619
+ let upscale = Math.pow(2, offset * 0.2);
620
+ if (event.shiftKey) upscale = Math.round(upscale * 10) / 10;
621
+ const [min, max] = config.scaleLimits?.[axis] || [1e-5, undefined];
622
+ upscale = Math.max(upscale, min / scale0.current);
623
+ if (max !== undefined) upscale = Math.min(upscale, max / scale0.current);
624
+ scaleCurrent.current = scale0.current * upscale;
625
+ meshRef.current!.position.set(0, position + offsetLocal, 0);
626
+ if (config.annotations && divRef.current) divRef.current.innerText =
627
+ scaleCurrent.current.toFixed(2);
628
+ scaleVector.set(1, 1, 1).setComponent(axis, upscale);
629
+ config.onDrag(
630
+ scaleMatrix.makeScale(scaleVector.x, scaleVector.y, scaleVector.z).premultiply(
631
+ info.localToWorld,
632
+ ).multiply(info.worldToLocal),
633
+ );
634
+ }, [config, axis, hovered, position]);
635
+ const up = useCallback((event: ThreeEvent<PointerEvent>) => {
636
+ if (config.annotations && divRef.current) divRef.current.style.display = 'none';
637
+ event.stopPropagation();
638
+ scale0.current = scaleCurrent.current;
639
+ clickInfo.current = null;
640
+ meshRef.current!.position.set(0, position, 0);
641
+ config.onDragEnd();
642
+ if (controls) controls.enabled = true;
643
+ captureTarget(event, false);
644
+ }, [config, controls, position]);
645
+ <group ref={objectRef}>
646
+ <group
647
+ matrix={matrix}
648
+ matrixAutoUpdate={false}
649
+ onPointerDown={down}
650
+ onPointerMove={move}
651
+ onPointerUp={up}
652
+ onPointerOut={(event) => {
653
+ event.stopPropagation();
654
+ setHovered(false);
655
+ }}
656
+ >
657
+ @if (config.annotations) {
658
+ <Annotation
659
+ position={[0, position / 2, 0]}
660
+ divRef={divRef}
661
+ className={config.annotationsClass}
662
+ />
663
+ }
664
+ <mesh
665
+ ref={meshRef}
666
+ position={[0, position, 0]}
667
+ renderOrder={config.renderOrder}
668
+ userData={config.userData}
669
+ >
670
+ <sphereGeometry args={[radius, 12, 12]} />
671
+ <meshBasicMaterial
672
+ transparent
673
+ depthTest={config.depthTest}
674
+ color={hovered ? config.hoveredColor : config.axisColors[axis]}
675
+ opacity={config.opacity}
676
+ polygonOffset
677
+ polygonOffsetFactor={-10}
678
+ />
679
+ </mesh>
680
+ </group>
681
+ </group>
682
+ }
683
+
684
+ export function PivotControls({
685
+ enabled = true,
686
+ matrix,
687
+ onDragStart,
688
+ onDrag,
689
+ onDragEnd,
690
+ autoTransform = true,
691
+ anchor,
692
+ disableAxes = false,
693
+ disableSliders = false,
694
+ disableRotations = false,
695
+ disableScaling = false,
696
+ activeAxes = [true, true, true],
697
+ offset = [0, 0, 0],
698
+ rotation = [0, 0, 0],
699
+ scale = 1,
700
+ lineWidth = 4,
701
+ fixed = false,
702
+ translationLimits,
703
+ rotationLimits,
704
+ scaleLimits,
705
+ depthTest = true,
706
+ renderOrder = 500,
707
+ axisColors = ['#ff2060', '#20df80', '#2080ff'],
708
+ hoveredColor = '#ffff40',
709
+ annotations = false,
710
+ annotationsClass,
711
+ opacity = 1,
712
+ visible = true,
713
+ userData,
714
+ children,
715
+ ref: forwardedRef,
716
+ ...props
717
+ }: PivotControlsProps) @{
718
+ const invalidate = useThree((state) => state.invalidate);
719
+ const parentRef = useRef<THREE.Group | null>(null), ref = useRef<THREE.Group | null>(
720
+ null,
721
+ ), gizmoRef = useRef<THREE.Group | null>(null), childrenRef = useRef<THREE.Group | null>(null);
722
+ const translation = useRef<[number, number, number]>([0, 0, 0]), cameraScale = useRef(
723
+ new THREE.Vector3(1, 1, 1),
724
+ ), gizmoScale = useRef(new THREE.Vector3(1, 1, 1));
725
+ useLayoutEffect(() => {
726
+ if (!anchor) return;
727
+ childrenRef.current!.updateWorldMatrix(true, true);
728
+ mPInv.copy(childrenRef.current!.matrixWorld).invert();
729
+ bb.makeEmpty();
730
+ childrenRef.current!.traverse((object: any) => {
731
+ if (!object.geometry) return;
732
+ if (!object.geometry.boundingBox) object.geometry.computeBoundingBox();
733
+ mL.copy(object.matrixWorld).premultiply(mPInv);
734
+ bbObj.copy(object.geometry.boundingBox).applyMatrix4(mL);
735
+ bb.union(bbObj);
736
+ });
737
+ vCenter.copy(bb.max).add(bb.min).multiplyScalar(0.5);
738
+ vSize.copy(bb.max).sub(bb.min).multiplyScalar(0.5);
739
+ vAnchorOffset.copy(vSize).multiply(new THREE.Vector3(...anchor)).add(vCenter);
740
+ vPosition.set(...offset).add(vAnchorOffset);
741
+ gizmoRef.current!.position.copy(vPosition);
742
+ invalidate();
743
+ });
744
+ const config = useMemo<PivotContext>(
745
+ () => ({
746
+ onDragStart: (info) => {
747
+ mL0.copy(ref.current!.matrix);
748
+ mW0.copy(ref.current!.matrixWorld);
749
+ onDragStart?.(info);
750
+ invalidate();
751
+ },
752
+ onDrag: (deltaWorld) => {
753
+ mP.copy(parentRef.current!.matrixWorld);
754
+ mPInv.copy(mP).invert();
755
+ mW.copy(mW0).premultiply(deltaWorld);
756
+ mL.copy(mW).premultiply(mPInv);
757
+ mL0Inv.copy(mL0).invert();
758
+ mdL.copy(mL).multiply(mL0Inv);
759
+ if (autoTransform) ref.current!.matrix.copy(mL);
760
+ onDrag?.(mL, mdL, mW, deltaWorld);
761
+ invalidate();
762
+ },
763
+ onDragEnd: () => {
764
+ onDragEnd?.();
765
+ invalidate();
766
+ },
767
+ translation,
768
+ translationLimits,
769
+ rotationLimits,
770
+ // Pinned parity: Drei 10.7.7 accepts scaleLimits but omits it from this provider.
771
+ axisColors,
772
+ hoveredColor,
773
+ opacity,
774
+ scale,
775
+ lineWidth,
776
+ fixed,
777
+ depthTest,
778
+ renderOrder,
779
+ userData,
780
+ annotations,
781
+ annotationsClass,
782
+ }),
783
+ [
784
+ onDragStart,
785
+ onDrag,
786
+ onDragEnd,
787
+ translationLimits,
788
+ rotationLimits,
789
+ scaleLimits,
790
+ depthTest,
791
+ scale,
792
+ lineWidth,
793
+ fixed,
794
+ ...axisColors,
795
+ hoveredColor,
796
+ opacity,
797
+ userData,
798
+ autoTransform,
799
+ annotations,
800
+ annotationsClass,
801
+ renderOrder,
802
+ ],
803
+ );
804
+ useFrame((state) => {
805
+ if (fixed) cameraScale.current.setScalar(
806
+ calculateScaleFactor(
807
+ gizmoRef.current!.getWorldPosition(scratch1),
808
+ scale,
809
+ state.camera,
810
+ state.size,
811
+ ),
812
+ );
813
+ if (matrix instanceof THREE.Matrix4) ref.current!.matrix = matrix;
814
+ ref.current!.updateWorldMatrix(true, true);
815
+ mG.makeRotationFromEuler(gizmoRef.current!.rotation).setPosition(
816
+ gizmoRef.current!.position,
817
+ ).premultiply(ref.current!.matrixWorld);
818
+ gizmoScale.current.setFromMatrixScale(mG);
819
+ vScale.copy(cameraScale.current).divide(gizmoScale.current);
820
+ if (
821
+ Math.abs(gizmoRef.current!.scale.x - vScale.x) > 1e-4 ||
822
+ Math.abs(gizmoRef.current!.scale.y - vScale.y) > 1e-4 ||
823
+ Math.abs(gizmoRef.current!.scale.z - vScale.z) > 1e-4
824
+ ) {
825
+ gizmoRef.current!.scale.copy(vScale);
826
+ state.invalidate();
827
+ }
828
+ });
829
+ useImperativeHandle((forwardedRef ?? null) as any, () => ref.current!, []);
830
+ <context.Provider value={config}>
831
+ <group ref={parentRef}>
832
+ <group ref={ref} matrix={matrix} matrixAutoUpdate={false} {...props}>
833
+ <group visible={visible} ref={gizmoRef} position={offset} rotation={rotation}>
834
+ @if (enabled && !disableAxes && activeAxes[0]) {
835
+ <AxisArrow axis={0} direction={xDir} />
836
+ }
837
+ @if (enabled && !disableAxes && activeAxes[1]) {
838
+ <AxisArrow axis={1} direction={yDir} />
839
+ }
840
+ @if (enabled && !disableAxes && activeAxes[2]) {
841
+ <AxisArrow axis={2} direction={zDir} />
842
+ }
843
+ @if (enabled && !disableSliders && activeAxes[0] && activeAxes[1]) {
844
+ <PlaneSlider axis={2} dir1={xDir} dir2={yDir} />
845
+ }
846
+ @if (enabled && !disableSliders && activeAxes[0] && activeAxes[2]) {
847
+ <PlaneSlider axis={1} dir1={zDir} dir2={xDir} />
848
+ }
849
+ @if (enabled && !disableSliders && activeAxes[2] && activeAxes[1]) {
850
+ <PlaneSlider axis={0} dir1={yDir} dir2={zDir} />
851
+ }
852
+ @if (enabled && !disableRotations && activeAxes[0] && activeAxes[1]) {
853
+ <AxisRotator axis={2} dir1={xDir} dir2={yDir} />
854
+ }
855
+ @if (enabled && !disableRotations && activeAxes[0] && activeAxes[2]) {
856
+ <AxisRotator axis={1} dir1={zDir} dir2={xDir} />
857
+ }
858
+ @if (enabled && !disableRotations && activeAxes[2] && activeAxes[1]) {
859
+ <AxisRotator axis={0} dir1={yDir} dir2={zDir} />
860
+ }
861
+ @if (enabled && !disableScaling && activeAxes[0]) {
862
+ <ScalingSphere axis={0} direction={xDir} />
863
+ }
864
+ @if (enabled && !disableScaling && activeAxes[1]) {
865
+ <ScalingSphere axis={1} direction={yDir} />
866
+ }
867
+ @if (enabled && !disableScaling && activeAxes[2]) {
868
+ <ScalingSphere axis={2} direction={zDir} />
869
+ }
870
+ </group>
871
+ <group ref={childrenRef}>{children}</group>
872
+ </group>
873
+ </group>
874
+ </context.Provider>
875
+ }
876
+
877
+ export type { OnDragStartProps, PivotContext } from './context.js';