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