@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,162 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { applyProps, type ThreeElements, useThree } from '@octanejs/three';
3
+ import { useEffect, useLayoutEffect, useRef, useState } from '@octanejs/three/renderer';
4
+ import * as THREE from 'three';
5
+ import { toCreasedNormals } from 'three-stdlib';
6
+ import { shaderMaterial } from './shaderMaterial.js';
7
+
8
+ const OutlinesMaterial = shaderMaterial(
9
+ {
10
+ screenspace: false,
11
+ color: new THREE.Color('black'),
12
+ opacity: 1,
13
+ thickness: 0.05,
14
+ size: new THREE.Vector2(),
15
+ },
16
+ `#include <common>
17
+ #include <morphtarget_pars_vertex>
18
+ #include <skinning_pars_vertex>
19
+ #include <clipping_planes_pars_vertex>
20
+ uniform float thickness;
21
+ uniform bool screenspace;
22
+ uniform vec2 size;
23
+ void main() {
24
+ #if defined (USE_SKINNING)
25
+ #include <beginnormal_vertex>
26
+ #include <morphnormal_vertex>
27
+ #include <skinbase_vertex>
28
+ #include <skinnormal_vertex>
29
+ #include <defaultnormal_vertex>
30
+ #endif
31
+ #include <begin_vertex>
32
+ #include <morphtarget_vertex>
33
+ #include <skinning_vertex>
34
+ #include <project_vertex>
35
+ #include <clipping_planes_vertex>
36
+ vec4 tNormal = vec4(normal, 0.0);
37
+ vec4 tPosition = vec4(transformed, 1.0);
38
+ #ifdef USE_INSTANCING
39
+ tNormal = instanceMatrix * tNormal;
40
+ tPosition = instanceMatrix * tPosition;
41
+ #endif
42
+ if (screenspace) {
43
+ vec3 newPosition = tPosition.xyz + tNormal.xyz * thickness;
44
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
45
+ } else {
46
+ vec4 clipPosition = projectionMatrix * modelViewMatrix * tPosition;
47
+ vec4 clipNormal = projectionMatrix * modelViewMatrix * tNormal;
48
+ vec2 offset = normalize(clipNormal.xy) * thickness / size * clipPosition.w * 2.0;
49
+ clipPosition.xy += offset;
50
+ gl_Position = clipPosition;
51
+ }
52
+ }`,
53
+ `uniform vec3 color;
54
+ uniform float opacity;
55
+ #include <clipping_planes_pars_fragment>
56
+ void main(){
57
+ #include <clipping_planes_fragment>
58
+ gl_FragColor = vec4(color, opacity);
59
+ #include <tonemapping_fragment>
60
+ #include <colorspace_fragment>
61
+ }`,
62
+ );
63
+
64
+ export type OutlinesProps = Omit<ThreeElements['group'], 'ref'> & {
65
+ color?: ThreeElements['meshBasicMaterial']['color'];
66
+ screenspace?: boolean;
67
+ opacity?: number;
68
+ transparent?: boolean;
69
+ thickness?: number;
70
+ angle?: number;
71
+ clippingPlanes?: THREE.Plane[];
72
+ toneMapped?: boolean;
73
+ polygonOffset?: boolean;
74
+ polygonOffsetFactor?: number;
75
+ renderOrder?: number;
76
+ };
77
+
78
+ export function Outlines({
79
+ color = 'black',
80
+ opacity = 1,
81
+ transparent = false,
82
+ screenspace = false,
83
+ toneMapped = true,
84
+ polygonOffset = false,
85
+ polygonOffsetFactor = 0,
86
+ renderOrder = 0,
87
+ thickness = 0.05,
88
+ angle = Math.PI,
89
+ clippingPlanes,
90
+ ...props
91
+ }: OutlinesProps) @{
92
+ const groupRef = useRef<THREE.Group | null>(null);
93
+ const [material] = useState(() => new OutlinesMaterial({ side: THREE.BackSide }));
94
+ const gl = useThree((state) => state.gl);
95
+ const size = useThree((state) => state.size);
96
+ const contextSize = typeof (gl as any).getDrawingBufferSize === 'function'
97
+ ? (gl as any).getDrawingBufferSize(new THREE.Vector2())
98
+ : new THREE.Vector2(size.width, size.height);
99
+ const oldAngle = useRef(0);
100
+ const oldGeometry = useRef<THREE.BufferGeometry | null>(null);
101
+ useLayoutEffect(() => {
102
+ const group = groupRef.current;
103
+ if (!group) return;
104
+ const parent = group.parent as THREE.Mesh & THREE.SkinnedMesh & THREE.InstancedMesh;
105
+ if (!parent?.geometry || (oldAngle.current === angle && oldGeometry.current === parent.geometry)) return;
106
+ oldAngle.current = angle;
107
+ oldGeometry.current = parent.geometry;
108
+ let mesh = group.children[0] as any;
109
+ if (mesh) {
110
+ if (angle) mesh.geometry.dispose();
111
+ group.remove(mesh);
112
+ }
113
+ if (parent.skeleton) {
114
+ mesh = new THREE.SkinnedMesh();
115
+ mesh.material = material;
116
+ mesh.bind(parent.skeleton, parent.bindMatrix);
117
+ } else if (parent.isInstancedMesh) {
118
+ mesh = new THREE.InstancedMesh(parent.geometry, material, parent.count);
119
+ mesh.instanceMatrix = parent.instanceMatrix;
120
+ } else {
121
+ mesh = new THREE.Mesh();
122
+ mesh.material = material;
123
+ }
124
+ group.add(mesh);
125
+ mesh.geometry = angle ? toCreasedNormals(parent.geometry as any, angle) as any : parent.geometry;
126
+ mesh.morphTargetInfluences = parent.morphTargetInfluences;
127
+ mesh.morphTargetDictionary = parent.morphTargetDictionary;
128
+ });
129
+ useLayoutEffect(() => {
130
+ const group = groupRef.current;
131
+ const mesh = group?.children[0] as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined;
132
+ if (!group || !mesh) return;
133
+ mesh.renderOrder = renderOrder;
134
+ const parent = group.parent as THREE.Mesh;
135
+ applyProps(mesh as any, {
136
+ morphTargetInfluences: parent.morphTargetInfluences,
137
+ morphTargetDictionary: parent.morphTargetDictionary,
138
+ });
139
+ applyProps(mesh.material as any, {
140
+ transparent,
141
+ thickness,
142
+ color,
143
+ opacity,
144
+ size: contextSize,
145
+ screenspace,
146
+ toneMapped,
147
+ polygonOffset,
148
+ polygonOffsetFactor,
149
+ clippingPlanes,
150
+ clipping: Boolean(clippingPlanes?.length),
151
+ });
152
+ });
153
+ useEffect(() => () => {
154
+ const group = groupRef.current;
155
+ const mesh = group?.children[0] as THREE.Mesh | undefined;
156
+ if (group && mesh) {
157
+ if (angle) mesh.geometry.dispose();
158
+ group.remove(mesh);
159
+ }
160
+ }, []);
161
+ <group ref={groupRef} {...props} />
162
+ }
@@ -0,0 +1,144 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { useFrame } from '@octanejs/three';
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ } from '@octanejs/three/renderer';
10
+ import type { OctaneNode } from 'octane';
11
+
12
+ type PerformanceMonitorHookApi = {
13
+ onIncline: (api: PerformanceMonitorApi) => void;
14
+ onDecline: (api: PerformanceMonitorApi) => void;
15
+ onChange: (api: PerformanceMonitorApi) => void;
16
+ onFallback: (api: PerformanceMonitorApi) => void;
17
+ };
18
+
19
+ export type PerformanceMonitorApi = {
20
+ fps: number;
21
+ factor: number;
22
+ refreshrate: number;
23
+ frames: number[];
24
+ averages: number[];
25
+ index: number;
26
+ flipped: number;
27
+ fallback: boolean;
28
+ subscriptions: Map<symbol, Partial<PerformanceMonitorHookApi>>;
29
+ subscribe: (ref: { current: Partial<PerformanceMonitorHookApi> }) => () => void;
30
+ };
31
+
32
+ export type PerformanceMonitorProps = {
33
+ ms?: number;
34
+ iterations?: number;
35
+ threshold?: number;
36
+ bounds?: (refreshrate: number) => [lower: number, upper: number];
37
+ flipflops?: number;
38
+ factor?: number;
39
+ step?: number;
40
+ onIncline?: (api: PerformanceMonitorApi) => void;
41
+ onDecline?: (api: PerformanceMonitorApi) => void;
42
+ onChange?: (api: PerformanceMonitorApi) => void;
43
+ onFallback?: (api: PerformanceMonitorApi) => void;
44
+ children?: OctaneNode;
45
+ };
46
+
47
+ const context = createContext<PerformanceMonitorApi>(null!);
48
+
49
+ export function PerformanceMonitor({
50
+ iterations = 10,
51
+ ms = 250,
52
+ threshold = 0.75,
53
+ step = 0.1,
54
+ factor: initialFactor = 0.5,
55
+ flipflops = Infinity,
56
+ bounds = (refreshrate) => (refreshrate > 100 ? [60, 100] : [40, 60]),
57
+ onIncline,
58
+ onDecline,
59
+ onChange,
60
+ onFallback,
61
+ children,
62
+ }: PerformanceMonitorProps) @{
63
+ const decimalPlacesRatio = Math.pow(10, 0);
64
+ const [api] = useState<PerformanceMonitorApi>(() => ({
65
+ fps: 0,
66
+ index: 0,
67
+ factor: initialFactor,
68
+ flipped: 0,
69
+ refreshrate: 0,
70
+ fallback: false,
71
+ frames: [],
72
+ averages: [],
73
+ subscriptions: new Map(),
74
+ subscribe: (ref) => {
75
+ const key = Symbol();
76
+ api.subscriptions.set(key, ref.current);
77
+ return () => void api.subscriptions.delete(key);
78
+ },
79
+ }));
80
+
81
+ let lastFactor = 0;
82
+ useFrame(() => {
83
+ const { frames, averages } = api;
84
+ if (api.fallback) return;
85
+ if (averages.length < iterations) {
86
+ frames.push(performance.now());
87
+ const msPassed = frames[frames.length - 1] - frames[0];
88
+ if (msPassed >= ms) {
89
+ api.fps =
90
+ Math.round((frames.length / msPassed) * 1000 * decimalPlacesRatio) /
91
+ decimalPlacesRatio;
92
+ api.refreshrate = Math.max(api.refreshrate, api.fps);
93
+ averages[api.index++ % iterations] = api.fps;
94
+ if (averages.length === iterations) {
95
+ const [lower, upper] = bounds(api.refreshrate);
96
+ const upperBounds = averages.filter((value) => value >= upper);
97
+ const lowerBounds = averages.filter((value) => value < lower);
98
+ if (upperBounds.length > iterations * threshold) {
99
+ api.factor = Math.min(1, api.factor + step);
100
+ api.flipped++;
101
+ onIncline?.(api);
102
+ api.subscriptions.forEach((value) => value.onIncline?.(api));
103
+ }
104
+ if (lowerBounds.length > iterations * threshold) {
105
+ api.factor = Math.max(0, api.factor - step);
106
+ api.flipped++;
107
+ onDecline?.(api);
108
+ api.subscriptions.forEach((value) => value.onDecline?.(api));
109
+ }
110
+ if (lastFactor !== api.factor) {
111
+ lastFactor = api.factor;
112
+ onChange?.(api);
113
+ api.subscriptions.forEach((value) => value.onChange?.(api));
114
+ }
115
+ if (api.flipped > flipflops && !api.fallback) {
116
+ api.fallback = true;
117
+ onFallback?.(api);
118
+ api.subscriptions.forEach((value) => value.onFallback?.(api));
119
+ }
120
+ api.averages = [];
121
+ }
122
+ api.frames = [];
123
+ }
124
+ }
125
+ });
126
+ <context.Provider value={api}>{children}</context.Provider>
127
+ }
128
+
129
+ export function usePerformanceMonitor({
130
+ onIncline,
131
+ onDecline,
132
+ onChange,
133
+ onFallback,
134
+ }: Partial<PerformanceMonitorHookApi>): void {
135
+ const api = useContext(context);
136
+ const ref = useRef({ onIncline, onDecline, onChange, onFallback });
137
+ useLayoutEffect(() => {
138
+ ref.current.onIncline = onIncline;
139
+ ref.current.onDecline = onDecline;
140
+ ref.current.onChange = onChange;
141
+ ref.current.onFallback = onFallback;
142
+ }, [onIncline, onDecline, onChange, onFallback]);
143
+ useLayoutEffect(() => api.subscribe(ref), [api]);
144
+ }
@@ -0,0 +1,84 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type RootState, type ThreeElement, type ThreeRef, useThree } from '@octanejs/three';
3
+ import { useEffect, useMemo } from '@octanejs/three/renderer';
4
+ import type * as THREE from 'three';
5
+ import { PointerLockControls as PointerLockControlsImpl } from 'three-stdlib';
6
+
7
+ export type PointerLockControlsProps =
8
+ Omit<ThreeElement<typeof PointerLockControlsImpl>, 'ref' | 'args'> & {
9
+ domElement?: HTMLElement;
10
+ selector?: string;
11
+ enabled?: boolean;
12
+ camera?: THREE.Camera;
13
+ onChange?: (event?: THREE.Event) => void;
14
+ onLock?: (event?: THREE.Event) => void;
15
+ onUnlock?: (event?: THREE.Event) => void;
16
+ makeDefault?: boolean;
17
+ };
18
+
19
+ export function PointerLockControls({
20
+ domElement,
21
+ selector,
22
+ onChange,
23
+ onLock,
24
+ onUnlock,
25
+ enabled = true,
26
+ makeDefault,
27
+ camera,
28
+ ref,
29
+ ...props
30
+ }: PointerLockControlsProps & { ref?: ThreeRef<PointerLockControlsImpl> }) @{
31
+ const state = useThree();
32
+ const explicitCamera = camera || state.camera;
33
+ const explicitElement = (domElement || state.events.connected ||
34
+ state.gl.domElement) as HTMLElement;
35
+ const controls = useMemo(() => new PointerLockControlsImpl(explicitCamera as any), [
36
+ explicitCamera,
37
+ ]);
38
+ useEffect(() => {
39
+ if (!enabled) return;
40
+ controls.connect(explicitElement);
41
+ const oldCompute = state.get().events.compute;
42
+ state.setEvents({
43
+ compute(_event: unknown, rootState: RootState) {
44
+ const offsetX = rootState.size.width / 2;
45
+ const offsetY = rootState.size.height / 2;
46
+ rootState.pointer.set(
47
+ (offsetX / rootState.size.width) * 2 - 1,
48
+ -(offsetY / rootState.size.height) * 2 + 1,
49
+ );
50
+ rootState.raycaster.setFromCamera(rootState.pointer, rootState.camera);
51
+ },
52
+ });
53
+ return () => {
54
+ controls.disconnect();
55
+ state.setEvents({ compute: oldCompute });
56
+ };
57
+ }, [enabled, controls]);
58
+ useEffect(() => {
59
+ const change = (event: any) => {
60
+ state.invalidate();
61
+ onChange?.(event);
62
+ };
63
+ controls.addEventListener('change', change);
64
+ if (onLock) controls.addEventListener('lock', onLock as any);
65
+ if (onUnlock) controls.addEventListener('unlock', onUnlock as any);
66
+ const handler = () => controls.lock();
67
+ const elements =
68
+ selector ? Array.from(document.querySelectorAll(selector)) : [document];
69
+ for (const element of elements) element?.addEventListener('click', handler);
70
+ return () => {
71
+ controls.removeEventListener('change', change);
72
+ if (onLock) controls.removeEventListener('lock', onLock as any);
73
+ if (onUnlock) controls.removeEventListener('unlock', onUnlock as any);
74
+ for (const element of elements) element?.removeEventListener('click', handler);
75
+ };
76
+ }, [onChange, onLock, onUnlock, selector, controls, state.invalidate]);
77
+ useEffect(() => {
78
+ if (!makeDefault) return;
79
+ const old = state.get().controls;
80
+ state.set({ controls } as any);
81
+ return () => state.set({ controls: old } as any);
82
+ }, [makeDefault, controls]);
83
+ <primitive ref={ref as any} object={controls} {...props as any} />
84
+ }
@@ -0,0 +1,242 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import {
3
+ extend,
4
+ type ThreeElement,
5
+ type ThreeElements,
6
+ type ThreeRef,
7
+ useFrame,
8
+ } from '@octanejs/three';
9
+ import {
10
+ useEffect,
11
+ useLayoutEffect,
12
+ useMemo,
13
+ useRef,
14
+ useState,
15
+ } from '@octanejs/three/renderer';
16
+ import { createContext, type OctaneNode, useContext } from 'octane';
17
+ import * as THREE from 'three';
18
+
19
+ type PointRef = { current: PositionPoint | null };
20
+ type PointsRef = { current: THREE.Points | null };
21
+
22
+ type Api = {
23
+ getParent: () => PointsRef;
24
+ subscribe: (ref: PointRef) => () => void;
25
+ };
26
+
27
+ export type PointsInstancesProps = Omit<ThreeElements['points'], 'ref'> & {
28
+ range?: number;
29
+ limit?: number;
30
+ children?: OctaneNode;
31
+ };
32
+
33
+ const inverseMatrix = new THREE.Matrix4();
34
+ const ray = new THREE.Ray();
35
+ const sphere = new THREE.Sphere();
36
+ const worldPosition = new THREE.Vector3();
37
+
38
+ export class PositionPoint extends THREE.Group {
39
+ size = 0;
40
+ color = new THREE.Color('white');
41
+ instance: PointsRef = { current: null };
42
+ instanceKey: PointRef = { current: null };
43
+
44
+ get geometry(): THREE.BufferGeometry | undefined {
45
+ return this.instance.current?.geometry;
46
+ }
47
+
48
+ override raycast(raycaster: THREE.Raycaster, intersects: THREE.Intersection[]): void {
49
+ const parent = this.instance.current;
50
+ if (!parent?.geometry) return;
51
+ const instances = parent.userData.instances as PointRef[];
52
+ const instanceId = instances.indexOf(this.instanceKey);
53
+ if (instanceId === -1 || instanceId > parent.geometry.drawRange.count) return;
54
+
55
+ const threshold = raycaster.params.Points?.threshold ?? 1;
56
+ sphere.set(this.getWorldPosition(worldPosition), threshold);
57
+ if (!raycaster.ray.intersectsSphere(sphere)) return;
58
+
59
+ inverseMatrix.copy(parent.matrixWorld).invert();
60
+ ray.copy(raycaster.ray).applyMatrix4(inverseMatrix);
61
+
62
+ const localThreshold = threshold / ((this.scale.x + this.scale.y + this.scale.z) / 3);
63
+ const rayPointDistanceSq = ray.distanceSqToPoint(this.position);
64
+ if (rayPointDistanceSq < localThreshold * localThreshold) {
65
+ const intersectPoint = new THREE.Vector3();
66
+ ray.closestPointToPoint(this.position, intersectPoint);
67
+ intersectPoint.applyMatrix4(this.matrixWorld);
68
+ const distance = raycaster.ray.origin.distanceTo(intersectPoint);
69
+ if (distance < raycaster.near || distance > raycaster.far) return;
70
+ intersects.push({
71
+ distance,
72
+ distanceToRay: Math.sqrt(rayPointDistanceSq),
73
+ point: intersectPoint,
74
+ index: instanceId,
75
+ face: null,
76
+ object: this,
77
+ });
78
+ }
79
+ }
80
+ }
81
+
82
+ const PositionPointElement = extend(PositionPoint);
83
+ const PointsContext = createContext<Api | null>(null);
84
+ const parentMatrix = new THREE.Matrix4();
85
+ const localPosition = new THREE.Vector3();
86
+
87
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
88
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
89
+ else if (typeof ref === 'function') ref(value);
90
+ else if (ref && 'current' in ref) ref.current = value;
91
+ }
92
+
93
+ function PointsInstances({
94
+ children,
95
+ range,
96
+ limit = 1000,
97
+ ref,
98
+ ...props
99
+ }: PointsInstancesProps & { ref?: ThreeRef<THREE.Points> }) @{
100
+ const parentRef = useRef<THREE.Points | null>(null);
101
+ const [refs, setRefs] = useState<PointRef[]>([]);
102
+ const [[positions, colors, sizes]] = useState(() => [
103
+ new Float32Array(limit * 3),
104
+ Float32Array.from({ length: limit * 3 }, () => 1),
105
+ Float32Array.from({ length: limit }, () => 1),
106
+ ]);
107
+ const mergedRef = useMemo(
108
+ () => (value: THREE.Points | null) => {
109
+ parentRef.current = value;
110
+ assignRef(ref, value);
111
+ },
112
+ [ref],
113
+ );
114
+ const api = useMemo<Api>(() => ({
115
+ getParent: () => parentRef,
116
+ subscribe(pointRef) {
117
+ setRefs((current) => [...current, pointRef]);
118
+ return () => setRefs((current) => current.filter((item) => item.current !== pointRef.current));
119
+ },
120
+ }), []);
121
+
122
+ useEffect(() => {
123
+ const position = parentRef.current?.geometry.attributes.position;
124
+ if (position) position.needsUpdate = true;
125
+ });
126
+ useFrame(() => {
127
+ const parent = parentRef.current;
128
+ if (!parent) return;
129
+ parent.updateMatrix();
130
+ parent.updateMatrixWorld();
131
+ parentMatrix.copy(parent.matrixWorld).invert();
132
+ parent.geometry.drawRange.count = Math.min(limit, range ?? limit, refs.length);
133
+ for (let index = 0; index < refs.length; index++) {
134
+ const point = refs[index]?.current;
135
+ if (!point) continue;
136
+ point.getWorldPosition(localPosition).applyMatrix4(parentMatrix);
137
+ localPosition.toArray(positions, index * 3);
138
+ parent.geometry.attributes.position!.needsUpdate = true;
139
+ point.matrixWorldNeedsUpdate = true;
140
+ point.color.toArray(colors, index * 3);
141
+ parent.geometry.attributes.color!.needsUpdate = true;
142
+ sizes.set([point.size], index);
143
+ parent.geometry.attributes.size!.needsUpdate = true;
144
+ }
145
+ });
146
+
147
+ <points
148
+ userData={{ instances: refs }}
149
+ matrixAutoUpdate={false}
150
+ ref={mergedRef}
151
+ raycast={() => null}
152
+ {...props as any}
153
+ >
154
+ <bufferGeometry>
155
+ <bufferAttribute attach="attributes-position" args={[positions, 3]} usage={THREE.DynamicDrawUsage} />
156
+ <bufferAttribute attach="attributes-color" args={[colors, 3]} usage={THREE.DynamicDrawUsage} />
157
+ <bufferAttribute attach="attributes-size" args={[sizes, 1]} usage={THREE.DynamicDrawUsage} />
158
+ </bufferGeometry>
159
+ <PointsContext.Provider value={api}>{children}</PointsContext.Provider>
160
+ </points>
161
+ }
162
+
163
+ type PointProps = ThreeElement<typeof PositionPoint>;
164
+
165
+ export function Point({ children, ref, ...props }: PointProps & {
166
+ ref?: ThreeRef<PositionPoint>;
167
+ }) @{
168
+ const group = useRef<PositionPoint | null>(null);
169
+ const api = useContext(PointsContext);
170
+ if (!api) throw new Error('Point must be used inside Points component.');
171
+ const mergedRef = useMemo(
172
+ () => (value: PositionPoint | null) => {
173
+ group.current = value;
174
+ assignRef(ref, value);
175
+ },
176
+ [ref],
177
+ );
178
+ useLayoutEffect(() => api.subscribe(group), [api]);
179
+ <PositionPointElement
180
+ instance={api.getParent()}
181
+ instanceKey={group}
182
+ ref={mergedRef}
183
+ {...props as any}
184
+ >
185
+ {children}
186
+ </PositionPointElement>
187
+ }
188
+
189
+ export type PointsBuffersProps = Omit<ThreeElements['points'], 'ref'> & {
190
+ positions: Float32Array;
191
+ colors?: Float32Array;
192
+ sizes?: Float32Array;
193
+ stride?: 2 | 3;
194
+ };
195
+
196
+ export function PointsBuffer({
197
+ children,
198
+ positions,
199
+ colors,
200
+ sizes,
201
+ stride = 3,
202
+ ref,
203
+ ...props
204
+ }: PointsBuffersProps & { ref?: ThreeRef<THREE.Points> }) @{
205
+ const pointsRef = useRef<THREE.Points | null>(null);
206
+ const mergedRef = useMemo(
207
+ () => (value: THREE.Points | null) => {
208
+ pointsRef.current = value;
209
+ assignRef(ref, value);
210
+ },
211
+ [ref],
212
+ );
213
+ useFrame(() => {
214
+ const attributes = pointsRef.current?.geometry.attributes;
215
+ if (!attributes) return;
216
+ attributes.position!.needsUpdate = true;
217
+ if (colors) attributes.color!.needsUpdate = true;
218
+ if (sizes) attributes.size!.needsUpdate = true;
219
+ });
220
+ <points ref={mergedRef} {...props as any}>
221
+ <bufferGeometry>
222
+ <bufferAttribute attach="attributes-position" args={[positions, stride]} usage={THREE.DynamicDrawUsage} />
223
+ @if (colors) {
224
+ <bufferAttribute attach="attributes-color" args={[colors, stride]} count={colors.length / stride} usage={THREE.DynamicDrawUsage} />
225
+ }
226
+ @if (sizes) {
227
+ <bufferAttribute attach="attributes-size" args={[sizes, 1]} count={sizes.length / stride} usage={THREE.DynamicDrawUsage} />
228
+ }
229
+ </bufferGeometry>
230
+ {children}
231
+ </points>
232
+ }
233
+
234
+ export function Points(props: (PointsBuffersProps | PointsInstancesProps) & {
235
+ ref?: ThreeRef<THREE.Points>;
236
+ }) @{
237
+ @if ((props as PointsBuffersProps).positions instanceof Float32Array) {
238
+ <PointsBuffer {...props as PointsBuffersProps} />
239
+ } @else {
240
+ <PointsInstances {...props as PointsInstancesProps} />
241
+ }
242
+ }
@@ -0,0 +1,76 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElement, type ThreeRef, useLoader, useThree } from '@octanejs/three';
3
+ import {
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from '@octanejs/three/renderer';
9
+ import {
10
+ AudioListener,
11
+ AudioLoader,
12
+ PositionalAudio as PositionalAudioImpl,
13
+ } from 'three';
14
+
15
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
16
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
17
+ else if (typeof ref === 'function') ref(value);
18
+ else if (ref && 'current' in ref) ref.current = value;
19
+ }
20
+
21
+ export type PositionalAudioProps = Omit<
22
+ ThreeElement<typeof PositionalAudioImpl>,
23
+ 'ref' | 'args'
24
+ > & {
25
+ url: string;
26
+ distance?: number;
27
+ loop?: boolean;
28
+ ref?: ThreeRef<PositionalAudioImpl>;
29
+ };
30
+
31
+ export function PositionalAudio({
32
+ url,
33
+ distance = 1,
34
+ loop = true,
35
+ autoplay,
36
+ ref,
37
+ ...props
38
+ }: PositionalAudioProps) @{
39
+ const sound = useRef<PositionalAudioImpl | null>(null);
40
+ const mergedRef = useMemo(
41
+ () => (value: PositionalAudioImpl | null) => {
42
+ sound.current = value;
43
+ assignRef(ref, value);
44
+ },
45
+ [ref],
46
+ );
47
+ const camera = useThree((state) => state.camera);
48
+ const [listener] = useState(() => new AudioListener());
49
+ const buffer = useLoader(AudioLoader, url);
50
+
51
+ useEffect(() => {
52
+ const current = sound.current;
53
+ if (current) {
54
+ current.setBuffer(buffer);
55
+ current.setRefDistance(distance);
56
+ current.setLoop(loop);
57
+ if (autoplay && !current.isPlaying) current.play();
58
+ }
59
+ }, [buffer, camera, distance, loop]);
60
+
61
+ useEffect(() => {
62
+ const current = sound.current;
63
+ camera.add(listener);
64
+ return () => {
65
+ camera.remove(listener);
66
+ if (current) {
67
+ if (current.isPlaying) current.stop();
68
+ if (current.source && (current.source as { _connected?: boolean })._connected) {
69
+ current.disconnect();
70
+ }
71
+ }
72
+ };
73
+ }, []);
74
+
75
+ <positionalAudio ref={mergedRef} args={[listener]} autoplay={autoplay} {...props as any} />
76
+ }