@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,314 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import {
3
+ applyProps,
4
+ extend,
5
+ type Color as ColorProp,
6
+ type ThreeElements,
7
+ type ThreeRef,
8
+ type Vector3 as Vector3Prop,
9
+ useFrame,
10
+ } from '@octanejs/three';
11
+ import {
12
+ useId,
13
+ useLayoutEffect,
14
+ useMemo,
15
+ useRef,
16
+ useState,
17
+ } from '@octanejs/three/renderer';
18
+ import { createContext, type OctaneNode, useContext } from 'octane';
19
+ import {
20
+ BufferAttribute,
21
+ Color,
22
+ DynamicDrawUsage,
23
+ Group,
24
+ InstancedMesh,
25
+ Material,
26
+ Matrix4,
27
+ MeshLambertMaterial,
28
+ Quaternion,
29
+ REVISION,
30
+ type Texture,
31
+ Vector3,
32
+ } from 'three';
33
+ import { useTexture } from './Texture.three.tsrx';
34
+
35
+ const CLOUD_URL = 'https://rawcdn.githack.com/pmndrs/drei-assets/9225a9f1fbd449d9411125c2f419b843d0308c9f/cloud.png';
36
+
37
+ type CloudState = {
38
+ uuid: string;
39
+ index: number;
40
+ segments: number;
41
+ dist: number;
42
+ matrix: Matrix4;
43
+ bounds: Vector3;
44
+ position: Vector3;
45
+ volume: number;
46
+ length: number;
47
+ ref: { current: Group | null };
48
+ speed: number;
49
+ growth: number;
50
+ opacity: number;
51
+ fade: number;
52
+ density: number;
53
+ rotation: number;
54
+ rotationFactor: number;
55
+ color: Color;
56
+ };
57
+
58
+ export type CloudsProps = Omit<ThreeElements['group'], 'ref'> & {
59
+ texture?: string;
60
+ limit?: number;
61
+ range?: number;
62
+ material?: typeof Material;
63
+ frustumCulled?: boolean;
64
+ children?: OctaneNode;
65
+ };
66
+
67
+ export type CloudProps = Omit<ThreeElements['group'], 'ref'> & {
68
+ seed?: number;
69
+ segments?: number;
70
+ bounds?: Vector3Prop;
71
+ concentrate?: 'random' | 'inside' | 'outside';
72
+ scale?: Vector3Prop;
73
+ volume?: number;
74
+ smallestVolume?: number;
75
+ distribute?: ((cloud: CloudState, index: number) => { point: Vector3; volume?: number }) | null;
76
+ growth?: number;
77
+ speed?: number;
78
+ fade?: number;
79
+ opacity?: number;
80
+ color?: ColorProp;
81
+ };
82
+
83
+ const parentMatrix = new Matrix4();
84
+ const translation = new Vector3();
85
+ const rotation = new Quaternion();
86
+ const cameraPosition = new Vector3();
87
+ const cameraQuaternion = new Quaternion();
88
+ const workScale = new Vector3();
89
+ const CloudsContext = createContext<{ current: CloudState[] } | null>(null);
90
+
91
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
92
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
93
+ else if (typeof ref === 'function') ref(value);
94
+ else if (ref && 'current' in ref) ref.current = value;
95
+ }
96
+
97
+ export function Clouds({
98
+ children,
99
+ material = MeshLambertMaterial,
100
+ texture = CLOUD_URL,
101
+ range,
102
+ limit = 200,
103
+ frustumCulled,
104
+ ref,
105
+ ...props
106
+ }: CloudsProps & { ref?: ThreeRef<Group> }) @{
107
+ const CloudMaterialElement = useMemo(() => {
108
+ const CloudMaterial = class extends (material as typeof MeshLambertMaterial) {
109
+ constructor() {
110
+ super();
111
+ const opaqueFragment = Number.parseInt(REVISION.replace(/\D+/g, ''), 10) >= 154
112
+ ? 'opaque_fragment'
113
+ : 'output_fragment';
114
+ this.onBeforeCompile = (shader) => {
115
+ shader.vertexShader = `attribute float cloudOpacity;
116
+ varying float vOpacity;
117
+ ` + shader.vertexShader.replace(
118
+ '#include <fog_vertex>',
119
+ `#include <fog_vertex>
120
+ vOpacity = cloudOpacity;
121
+ `,
122
+ );
123
+ shader.fragmentShader = `varying float vOpacity;
124
+ ` + shader.fragmentShader.replace(
125
+ `#include <${opaqueFragment}>`,
126
+ `#include <${opaqueFragment}>
127
+ gl_FragColor = vec4(outgoingLight, diffuseColor.a * vOpacity);
128
+ `,
129
+ );
130
+ };
131
+ }
132
+ };
133
+ return extend(CloudMaterial);
134
+ }, [material]);
135
+ const instance = useRef<InstancedMesh | null>(null);
136
+ const [clouds] = useState<CloudState[]>([]);
137
+ const cloudsRef = useMemo(() => ({ current: clouds }), [clouds]);
138
+ const opacities = useMemo(() => Float32Array.from({ length: limit }, () => 1), [limit]);
139
+ const colors = useMemo(() => Float32Array.from({ length: limit * 3 }, () => 1), [limit]);
140
+ const cloudTexture = useTexture(texture, undefined) as Texture;
141
+ const quaternion = new Quaternion();
142
+ const direction = new Vector3(0, 0, 1);
143
+ const position = new Vector3();
144
+ const mergedRef = useMemo(
145
+ () => (value: Group | null) => assignRef(ref, value),
146
+ [ref],
147
+ );
148
+
149
+ useFrame((state, delta) => {
150
+ const mesh = instance.current;
151
+ if (!mesh) return;
152
+ parentMatrix.copy(mesh.matrixWorld).invert();
153
+ state.camera.matrixWorld.decompose(cameraPosition, cameraQuaternion, workScale);
154
+ for (const config of clouds) {
155
+ if (!config.ref.current) continue;
156
+ config.ref.current.matrixWorld.decompose(translation, rotation, workScale);
157
+ translation.add(position.copy(config.position).applyQuaternion(rotation).multiply(workScale));
158
+ rotation.copy(cameraQuaternion).multiply(
159
+ quaternion.setFromAxisAngle(direction, (config.rotation += delta * config.rotationFactor)),
160
+ );
161
+ workScale.multiplyScalar(
162
+ config.volume + ((1 + Math.sin(state.clock.elapsedTime * config.density * config.speed)) / 2) * config.growth,
163
+ );
164
+ config.matrix.compose(translation, rotation, workScale).premultiply(parentMatrix);
165
+ config.dist = translation.distanceTo(cameraPosition);
166
+ }
167
+ clouds.sort((left, right) => right.dist - left.dist);
168
+ for (let index = 0; index < clouds.length; index++) {
169
+ const config = clouds[index]!;
170
+ opacities[index] = config.opacity * (config.dist < config.fade - 1 ? config.dist / config.fade : 1);
171
+ mesh.setMatrixAt(index, config.matrix);
172
+ mesh.setColorAt(index, config.color);
173
+ }
174
+ mesh.geometry.attributes.cloudOpacity!.needsUpdate = true;
175
+ mesh.instanceMatrix.needsUpdate = true;
176
+ if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
177
+ });
178
+
179
+ useLayoutEffect(() => {
180
+ const mesh = instance.current;
181
+ if (!mesh) return;
182
+ const count = Math.min(limit, range ?? limit, clouds.length);
183
+ mesh.count = count;
184
+ mesh.instanceMatrix.updateRanges[0] = { start: 0, count: count * 16 };
185
+ if (mesh.instanceColor) mesh.instanceColor.updateRanges[0] = { start: 0, count: count * 3 };
186
+ (mesh.geometry.attributes.cloudOpacity as BufferAttribute).updateRanges[0] = { start: 0, count };
187
+ });
188
+
189
+ let imageBounds = [cloudTexture.image?.width ?? 1, cloudTexture.image?.height ?? 1];
190
+ const maximum = Math.max(imageBounds[0]!, imageBounds[1]!);
191
+ imageBounds = [imageBounds[0]! / maximum, imageBounds[1]! / maximum];
192
+ <group ref={mergedRef} {...props}>
193
+ <CloudsContext.Provider value={cloudsRef}>
194
+ {children}
195
+ <instancedMesh
196
+ matrixAutoUpdate={false}
197
+ ref={instance}
198
+ args={[null as any, null as any, limit]}
199
+ frustumCulled={frustumCulled}
200
+ >
201
+ <instancedBufferAttribute usage={DynamicDrawUsage} attach="instanceColor" args={[colors, 3]} />
202
+ <planeGeometry args={imageBounds as [number, number]}>
203
+ <instancedBufferAttribute usage={DynamicDrawUsage} attach="attributes-cloudOpacity" args={[opacities, 1]} />
204
+ </planeGeometry>
205
+ <CloudMaterialElement map={cloudTexture} transparent={true} depthWrite={false} />
206
+ </instancedMesh>
207
+ </CloudsContext.Provider>
208
+ </group>
209
+ }
210
+
211
+ export function CloudInstance({
212
+ opacity = 1,
213
+ speed = 0,
214
+ bounds = [5, 1, 1],
215
+ segments = 20,
216
+ color = '#ffffff',
217
+ fade = 10,
218
+ volume = 6,
219
+ smallestVolume = 0.25,
220
+ distribute = null,
221
+ growth = 4,
222
+ concentrate = 'inside',
223
+ seed = Math.random(),
224
+ ref,
225
+ ...props
226
+ }: CloudProps & { ref?: ThreeRef<Group> }) @{
227
+ const random = () => {
228
+ const value = Math.sin(seed++) * 10000;
229
+ return value - Math.floor(value);
230
+ };
231
+ const parent = useContext(CloudsContext);
232
+ if (!parent) throw new Error('CloudInstance must be used inside Clouds component.');
233
+ const group = useRef<Group | null>(null);
234
+ const uuid = useId();
235
+ const clouds = useMemo<CloudState[]>(() => Array.from({ length: segments }, (_, index) => ({
236
+ segments,
237
+ bounds: new Vector3(1, 1, 1),
238
+ position: new Vector3(),
239
+ uuid,
240
+ index,
241
+ ref: group,
242
+ dist: 0,
243
+ matrix: new Matrix4(),
244
+ color: new Color(),
245
+ rotation: index * (Math.PI / segments),
246
+ volume: 0,
247
+ length: 0,
248
+ speed: 0,
249
+ growth: 0,
250
+ opacity: 0,
251
+ fade: 0,
252
+ density: 0,
253
+ rotationFactor: 0,
254
+ })), [segments, uuid]);
255
+ const mergedRef = useMemo(
256
+ () => (value: Group | null) => { group.current = value; assignRef(ref, value); },
257
+ [ref],
258
+ );
259
+
260
+ useLayoutEffect(() => {
261
+ for (let index = 0; index < clouds.length; index++) {
262
+ const cloud = clouds[index]!;
263
+ applyProps(cloud as any, {
264
+ volume,
265
+ color,
266
+ speed,
267
+ growth,
268
+ opacity,
269
+ fade,
270
+ bounds,
271
+ density: Math.max(0.5, random()),
272
+ rotationFactor: Math.max(0.2, 0.5 * random()) * speed,
273
+ });
274
+ const distributed = distribute?.(cloud, index);
275
+ if (distributed || segments > 1) {
276
+ cloud.position.copy(cloud.bounds).multiply(distributed?.point ?? new Vector3(
277
+ random() * 2 - 1,
278
+ random() * 2 - 1,
279
+ random() * 2 - 1,
280
+ ));
281
+ }
282
+ const xDifference = Math.abs(cloud.position.x);
283
+ const yDifference = Math.abs(cloud.position.y);
284
+ const zDifference = Math.abs(cloud.position.z);
285
+ const maximum = Math.max(xDifference, yDifference, zDifference);
286
+ cloud.length = 1;
287
+ if (xDifference === maximum) cloud.length -= xDifference / cloud.bounds.x;
288
+ if (yDifference === maximum) cloud.length -= yDifference / cloud.bounds.y;
289
+ if (zDifference === maximum) cloud.length -= zDifference / cloud.bounds.z;
290
+ cloud.volume = (distributed?.volume !== undefined
291
+ ? distributed.volume
292
+ : Math.max(
293
+ Math.max(0, smallestVolume),
294
+ concentrate === 'random' ? random() : concentrate === 'inside' ? cloud.length : 1 - cloud.length,
295
+ )) * volume;
296
+ }
297
+ }, [concentrate, bounds, fade, color, opacity, growth, volume, seed, segments, speed]);
298
+ useLayoutEffect(() => {
299
+ parent.current.push(...clouds);
300
+ return () => {
301
+ parent.current = parent.current.filter((item) => item.uuid !== uuid);
302
+ };
303
+ }, [clouds, parent, uuid]);
304
+ <group ref={mergedRef} {...props} />
305
+ }
306
+
307
+ export function Cloud(props: CloudProps & { ref?: ThreeRef<Group> }) @{
308
+ const parent = useContext(CloudsContext);
309
+ @if (parent) {
310
+ <CloudInstance {...props} />
311
+ } @else {
312
+ <Clouds><CloudInstance {...props} /></Clouds>
313
+ }
314
+ }
@@ -0,0 +1,27 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements, useInstanceHandle } from '@octanejs/three';
3
+ import { useLayoutEffect, useRef, useState } from '@octanejs/three/renderer';
4
+ import { BufferAttribute, type BufferGeometry } from 'three';
5
+
6
+ export type ComputedAttributeProps =
7
+ Omit<ThreeElements['bufferAttribute'], 'args'> & {
8
+ compute: (geometry: BufferGeometry) => BufferAttribute;
9
+ name: string;
10
+ };
11
+
12
+ export function ComputedAttribute({ compute, name, ...props }: ComputedAttributeProps) @{
13
+ const [bufferAttribute] = useState(() => new BufferAttribute(new Float32Array(0), 1));
14
+ const primitive = useRef<BufferAttribute | null>(null);
15
+ const instance = useInstanceHandle(primitive);
16
+ useLayoutEffect(() => {
17
+ if (primitive.current && instance.current?.parent?.object) {
18
+ primitive.current.copy(compute(instance.current.parent.object as BufferGeometry));
19
+ }
20
+ }, [compute]);
21
+ <primitive
22
+ ref={primitive}
23
+ object={bufferAttribute}
24
+ attach={`attributes-${name}`}
25
+ {...props as any}
26
+ />
27
+ }
@@ -0,0 +1,106 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements, type ThreeRef, useFrame, useThree } from '@octanejs/three';
3
+ import { useImperativeHandle, useMemo, useRef } from '@octanejs/three/renderer';
4
+ import * as THREE from 'three';
5
+ import { HorizontalBlurShader, VerticalBlurShader } from 'three-stdlib';
6
+
7
+ export type ContactShadowsProps = Omit<ThreeElements['group'], 'ref' | 'scale'> & {
8
+ opacity?: number;
9
+ width?: number;
10
+ height?: number;
11
+ blur?: number;
12
+ near?: number;
13
+ far?: number;
14
+ smooth?: boolean;
15
+ resolution?: number;
16
+ frames?: number;
17
+ scale?: number | [x: number, y: number];
18
+ color?: THREE.ColorRepresentation;
19
+ depthWrite?: boolean;
20
+ };
21
+
22
+ export function ContactShadows({
23
+ scale = 10,
24
+ frames = Infinity,
25
+ opacity = 1,
26
+ width: widthFactor = 1,
27
+ height: heightFactor = 1,
28
+ blur = 1,
29
+ near = 0,
30
+ far = 10,
31
+ resolution = 512,
32
+ smooth = true,
33
+ color = '#000000',
34
+ depthWrite = false,
35
+ renderOrder,
36
+ ref,
37
+ ...props
38
+ }: ContactShadowsProps & { ref?: ThreeRef<THREE.Group> }) @{
39
+ const group = useRef<THREE.Group | null>(null);
40
+ const scene = useThree((state) => state.scene);
41
+ const gl = useThree((state) => state.gl);
42
+ const shadowCamera = useRef<THREE.OrthographicCamera | null>(null);
43
+ const width = widthFactor * (Array.isArray(scale) ? scale[0] : scale || 1);
44
+ const height = heightFactor * (Array.isArray(scale) ? scale[1] : scale || 1);
45
+ const resources = useMemo(() => {
46
+ const renderTarget = new THREE.WebGLRenderTarget(resolution, resolution);
47
+ const renderTargetBlur = new THREE.WebGLRenderTarget(resolution, resolution);
48
+ renderTargetBlur.texture.generateMipmaps = renderTarget.texture.generateMipmaps = false;
49
+ const planeGeometry = new THREE.PlaneGeometry(width, height).rotateX(Math.PI / 2);
50
+ const blurPlane = new THREE.Mesh(planeGeometry);
51
+ const depthMaterial = new THREE.MeshDepthMaterial();
52
+ depthMaterial.depthTest = depthMaterial.depthWrite = false;
53
+ depthMaterial.onBeforeCompile = (shader) => {
54
+ shader.uniforms = { ...shader.uniforms, ucolor: { value: new THREE.Color(color) } };
55
+ shader.fragmentShader = shader.fragmentShader.replace('void main() {', 'uniform vec3 ucolor;\nvoid main() {');
56
+ shader.fragmentShader = shader.fragmentShader.replace(
57
+ 'vec4( vec3( 1.0 - fragCoordZ ), opacity );',
58
+ 'vec4( ucolor * fragCoordZ * 2.0, ( 1.0 - fragCoordZ ) * 1.0 );',
59
+ );
60
+ };
61
+ const horizontalBlurMaterial = new THREE.ShaderMaterial(HorizontalBlurShader);
62
+ const verticalBlurMaterial = new THREE.ShaderMaterial(VerticalBlurShader);
63
+ verticalBlurMaterial.depthTest = horizontalBlurMaterial.depthTest = false;
64
+ return { renderTarget, renderTargetBlur, planeGeometry, blurPlane, depthMaterial, horizontalBlurMaterial, verticalBlurMaterial };
65
+ }, [resolution, width, height, scale, color]);
66
+ const blurShadows = (amount: number) => {
67
+ const { blurPlane, horizontalBlurMaterial, verticalBlurMaterial, renderTarget, renderTargetBlur } = resources;
68
+ blurPlane.visible = true;
69
+ blurPlane.material = horizontalBlurMaterial;
70
+ horizontalBlurMaterial.uniforms.tDiffuse.value = renderTarget.texture;
71
+ horizontalBlurMaterial.uniforms.h.value = amount / 256;
72
+ gl.setRenderTarget(renderTargetBlur);
73
+ gl.render(blurPlane as any, shadowCamera.current!);
74
+ blurPlane.material = verticalBlurMaterial;
75
+ verticalBlurMaterial.uniforms.tDiffuse.value = renderTargetBlur.texture;
76
+ verticalBlurMaterial.uniforms.v.value = amount / 256;
77
+ gl.setRenderTarget(renderTarget);
78
+ gl.render(blurPlane as any, shadowCamera.current!);
79
+ blurPlane.visible = false;
80
+ };
81
+ let count = 0;
82
+ useFrame(() => {
83
+ if (!shadowCamera.current || !group.current || (frames !== Infinity && count >= frames)) return;
84
+ count++;
85
+ const initialBackground = scene.background;
86
+ const initialOverrideMaterial = scene.overrideMaterial;
87
+ group.current.visible = false;
88
+ scene.background = null;
89
+ scene.overrideMaterial = resources.depthMaterial;
90
+ gl.setRenderTarget(resources.renderTarget);
91
+ gl.render(scene, shadowCamera.current);
92
+ blurShadows(blur);
93
+ if (smooth) blurShadows(blur * 0.4);
94
+ gl.setRenderTarget(null);
95
+ group.current.visible = true;
96
+ scene.overrideMaterial = initialOverrideMaterial;
97
+ scene.background = initialBackground;
98
+ });
99
+ useImperativeHandle((ref ?? null) as any, () => group.current!, []);
100
+ <group rotation-x={Math.PI / 2} {...props} ref={group}>
101
+ <mesh renderOrder={renderOrder} geometry={resources.planeGeometry as any} scale={[1, -1, 1]} rotation={[-Math.PI / 2, 0, 0]}>
102
+ <meshBasicMaterial transparent map={resources.renderTarget.texture} opacity={opacity} depthWrite={depthWrite} />
103
+ </mesh>
104
+ <orthographicCamera ref={shadowCamera} args={[-width / 2, width / 2, height / 2, -height / 2, near, far]} />
105
+ </group>
106
+ }
@@ -0,0 +1,32 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { useLoader } from '@octanejs/three';
3
+ import { CubeTexture as ThreeCubeTexture, CubeTextureLoader, type Texture } from 'three';
4
+ import { resolveRenderChildren, type RenderChildren } from './octane-children.js';
5
+
6
+ export interface CubeTextureOptions {
7
+ path: string;
8
+ }
9
+
10
+ export function useCubeTexture(files: string[], { path }: CubeTextureOptions): ThreeCubeTexture {
11
+ const [cubeTexture] = useLoader(CubeTextureLoader, [files], (loader) => loader.setPath(path));
12
+ return cubeTexture;
13
+ }
14
+
15
+ useCubeTexture.preload = (files: string[], { path }: CubeTextureOptions) => useLoader.preload(
16
+ CubeTextureLoader,
17
+ [files],
18
+ (loader) => loader.setPath(path),
19
+ );
20
+
21
+ export type CubeTextureProps =
22
+ CubeTextureOptions & {
23
+ children?: RenderChildren<Texture>;
24
+ files: Parameters<typeof useCubeTexture>[0];
25
+ };
26
+
27
+ export function CubeTexture({ children, files, ...options }: CubeTextureProps) @{
28
+ const texture = useCubeTexture(files, options);
29
+ <>
30
+ {resolveRenderChildren(children, texture)}
31
+ </>
32
+ }
@@ -0,0 +1,37 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { createPortal, type ThreeRef } from '@octanejs/three';
3
+ import { useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from '@octanejs/three/renderer';
4
+ import type { UniversalRenderable } from 'octane/universal';
5
+ import * as THREE from 'three';
6
+ import { Flow } from 'three-stdlib';
7
+
8
+ export interface CurveModifierProps {
9
+ children: UniversalRenderable;
10
+ curve?: THREE.Curve<THREE.Vector3>;
11
+ }
12
+
13
+ export type CurveModifierRef = Flow;
14
+
15
+ export function CurveModifier({ children, curve, ref }: CurveModifierProps & {
16
+ ref?: ThreeRef<CurveModifierRef>;
17
+ }) @{
18
+ const [scene] = useState(() => new THREE.Scene());
19
+ const [object, setObject] = useState<THREE.Object3D | undefined>(undefined);
20
+ const modifier = useRef<Flow | null>(null);
21
+ const source = useRef<THREE.Object3D | null>(null);
22
+ useLayoutEffect(() => {
23
+ if (!scene.children[0] || source.current === scene.children[0]) return;
24
+ source.current = scene.children[0];
25
+ const FlowConstructor = Flow as unknown as new (mesh: THREE.Mesh) => Flow;
26
+ modifier.current = new FlowConstructor(scene.children[0] as THREE.Mesh);
27
+ setObject(modifier.current.object3D as unknown as THREE.Object3D);
28
+ });
29
+ useEffect(() => {
30
+ if (curve) modifier.current?.updateCurve(0, curve as any);
31
+ }, [curve]);
32
+ useImperativeHandle(ref as any, () => modifier.current!, [modifier.current]);
33
+ <>
34
+ {createPortal(children, scene)}
35
+ {object && <primitive object={object} />}
36
+ </>
37
+ }
@@ -0,0 +1,132 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import {
3
+ applyProps,
4
+ type Euler as EulerProp,
5
+ type RefObject,
6
+ type ThreeElements,
7
+ type ThreeRef,
8
+ type Vector3 as Vector3Prop,
9
+ } from '@octanejs/three';
10
+ import { useImperativeHandle, useLayoutEffect, useMemo, useRef } from '@octanejs/three/renderer';
11
+ import * as THREE from 'three';
12
+ import { DecalGeometry } from 'three-stdlib';
13
+
14
+ export type DecalProps =
15
+ Omit<ThreeElements['mesh'], 'ref' | 'children'> & {
16
+ debug?: boolean;
17
+ mesh?: RefObject<THREE.Mesh>;
18
+ position?: Vector3Prop;
19
+ rotation?: EulerProp | number;
20
+ scale?: Vector3Prop;
21
+ map?: THREE.Texture;
22
+ children?: ThreeElements['mesh']['children'];
23
+ polygonOffsetFactor?: number;
24
+ depthTest?: boolean;
25
+ };
26
+
27
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
28
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
29
+ else if (typeof ref === 'function') ref(value);
30
+ else if (ref && 'current' in ref) ref.current = value;
31
+ }
32
+
33
+ function vecToArray(vector: number[] | Vector3Prop | EulerProp | number = [0, 0, 0]): number[] {
34
+ if (Array.isArray(vector)) return [Number(vector[0]), Number(vector[1]), Number(vector[2])];
35
+ if (vector instanceof THREE.Vector3 || vector instanceof THREE.Euler) return [
36
+ vector.x,
37
+ vector.y,
38
+ vector.z,
39
+ ];
40
+ const scalar = vector as number;
41
+ return [scalar, scalar, scalar];
42
+ }
43
+
44
+ export function Decal({
45
+ debug,
46
+ depthTest = false,
47
+ polygonOffsetFactor = -10,
48
+ map,
49
+ mesh,
50
+ children,
51
+ position,
52
+ rotation,
53
+ scale,
54
+ ref,
55
+ ...props
56
+ }: DecalProps & { ref?: ThreeRef<THREE.Mesh> }) @{
57
+ const localRef = useRef<THREE.Mesh | null>(null);
58
+ const imperativeRef = useMemo(
59
+ () => (value: THREE.Mesh | null | undefined) => assignRef(ref, value ?? null),
60
+ [ref],
61
+ );
62
+ useImperativeHandle(imperativeRef, () => localRef.current!, []);
63
+ const helper = useRef<THREE.Mesh | null>(null);
64
+ const state = useRef({
65
+ position: new THREE.Vector3(),
66
+ rotation: new THREE.Euler(),
67
+ scale: new THREE.Vector3(1, 1, 1),
68
+ });
69
+ useLayoutEffect(() => {
70
+ if (!localRef.current) return;
71
+ const parent = mesh?.current || localRef.current.parent;
72
+ const target = localRef.current;
73
+ if (
74
+ !(parent instanceof THREE.Mesh)
75
+ ) throw new Error('Decal must have a Mesh as parent or specify its "mesh" prop');
76
+ applyProps(state.current, { position, scale });
77
+ const matrixWorld = parent.matrixWorld.clone();
78
+ parent.matrixWorld.identity();
79
+ if (!rotation || typeof rotation === 'number') {
80
+ const object = new THREE.Object3D();
81
+ object.position.copy(state.current.position);
82
+ const vertices = parent.geometry.attributes.position.array;
83
+ if (parent.geometry.attributes.normal === undefined) parent.geometry.computeVertexNormals();
84
+ const normal = parent.geometry.attributes.normal.array;
85
+ let distance = Infinity;
86
+ const closestNormal = new THREE.Vector3();
87
+ let chosenIndex = -1;
88
+ for (let index = 0; index < vertices.length; index += 3) {
89
+ const xDiff = vertices[index] - object.position.x;
90
+ const yDiff = vertices[index + 1] - object.position.y;
91
+ const zDiff = vertices[index + 2] - object.position.z;
92
+ const squared = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff;
93
+ if (squared < distance) {
94
+ distance = squared;
95
+ chosenIndex = index;
96
+ }
97
+ }
98
+ closestNormal.fromArray(normal, chosenIndex);
99
+ object.lookAt(object.position.clone().add(closestNormal));
100
+ object.rotateZ(Math.PI);
101
+ object.rotateY(Math.PI);
102
+ if (typeof rotation === 'number') object.rotateZ(rotation);
103
+ applyProps(state.current, { rotation: object.rotation });
104
+ } else applyProps(state.current, { rotation });
105
+ if (helper.current) applyProps(helper.current, state.current);
106
+ const Geometry = DecalGeometry as unknown as new (mesh: THREE.Mesh, position: THREE.Vector3, rotation: THREE.Euler, scale: THREE.Vector3) => THREE.BufferGeometry;
107
+ target.geometry =
108
+ new Geometry(parent, state.current.position, state.current.rotation, state.current.scale);
109
+ parent.matrixWorld = matrixWorld;
110
+ return () => target.geometry.dispose();
111
+ }, [mesh, ...vecToArray(position), ...vecToArray(scale), ...vecToArray(rotation)]);
112
+ useLayoutEffect(() => {
113
+ if (helper.current) helper.current.traverse((child) => (child.raycast = () => null));
114
+ }, [debug]);
115
+ <mesh
116
+ ref={localRef}
117
+ material-transparent
118
+ material-polygonOffset
119
+ material-polygonOffsetFactor={polygonOffsetFactor}
120
+ material-depthTest={depthTest}
121
+ material-map={map}
122
+ {...props}
123
+ >
124
+ {children}
125
+ {debug &&
126
+ <mesh ref={helper}>
127
+ <boxGeometry />
128
+ <meshNormalMaterial wireframe />
129
+ <axesHelper />
130
+ </mesh>}
131
+ </mesh>
132
+ }
@@ -0,0 +1,28 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements, type ThreeRef, useFrame } from '@octanejs/three';
3
+ import { useLayoutEffect, useRef } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import type { LOD } from 'three';
6
+
7
+ export type DetailedProps =
8
+ Omit<ThreeElements['lOD'], 'ref' | 'children'> & {
9
+ children: OctaneNode;
10
+ hysteresis?: number;
11
+ distances: number[];
12
+ };
13
+
14
+ export function Detailed({ children, hysteresis = 0, distances, ref, ...props }: DetailedProps & {
15
+ ref?: ThreeRef<LOD>;
16
+ }) @{
17
+ const lodRef = useRef<LOD | null>(null);
18
+ useLayoutEffect(() => {
19
+ const lod = lodRef.current;
20
+ if (!lod) return;
21
+ lod.levels.length = 0;
22
+ lod.children.forEach((object, index) => {
23
+ lod.levels.push({ object, hysteresis, distance: distances[index] });
24
+ });
25
+ });
26
+ useFrame((state) => lodRef.current?.update(state.camera));
27
+ <lOD ref={[lodRef, ref ?? null]} {...props}>{children}</lOD>
28
+ }