@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,52 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { createPortal, useFrame, useThree } from '@octanejs/three';
3
+ import { useState } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import * as THREE from 'three';
6
+
7
+ type RenderHudProps = {
8
+ defaultScene: THREE.Scene;
9
+ defaultCamera: THREE.Camera;
10
+ renderPriority?: number;
11
+ };
12
+
13
+ function RenderHud({ defaultScene, defaultCamera, renderPriority = 1 }: RenderHudProps) @{
14
+ const { gl, scene, camera } = useThree();
15
+ let oldClear: boolean;
16
+ useFrame(() => {
17
+ oldClear = gl.autoClear;
18
+ if (renderPriority === 1) {
19
+ gl.autoClear = true;
20
+ gl.render(defaultScene, defaultCamera);
21
+ }
22
+ gl.autoClear = false;
23
+ gl.clearDepth();
24
+ gl.render(scene, camera);
25
+ gl.autoClear = oldClear;
26
+ }, renderPriority);
27
+ <group onPointerOver={() => null} />
28
+ }
29
+
30
+ export type HudProps = {
31
+ children: OctaneNode;
32
+ renderPriority?: number;
33
+ };
34
+
35
+ export function Hud({ children, renderPriority = 1 }: { children?: OctaneNode; renderPriority?: number }) @{
36
+ const { scene: defaultScene, camera: defaultCamera } = useThree();
37
+ const [hudScene] = useState(() => new THREE.Scene());
38
+ <>
39
+ {createPortal(
40
+ <>
41
+ {children}
42
+ <RenderHud
43
+ defaultScene={defaultScene}
44
+ defaultCamera={defaultCamera}
45
+ renderPriority={renderPriority}
46
+ />
47
+ </>,
48
+ hudScene,
49
+ { events: { priority: renderPriority + 1 } },
50
+ )}
51
+ </>
52
+ }
@@ -0,0 +1,146 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { extend, type ThreeElements, type ThreeRef, useThree } from '@octanejs/three';
3
+ import { useImperativeHandle, useLayoutEffect, useRef } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import { universalComponent } from 'octane/universal';
6
+ import * as THREE from 'three';
7
+ import { shaderMaterial } from './shaderMaterial.js';
8
+ import { useTexture } from './Texture.three.tsrx';
9
+
10
+ type ImageSource = { texture: THREE.Texture; url?: never } | { texture?: never; url: string };
11
+
12
+ export type ImageProps = Omit<ThreeElements['mesh'], 'scale'> & {
13
+ segments?: number;
14
+ scale?: number | [number, number];
15
+ color?: ThreeElements['meshBasicMaterial']['color'];
16
+ zoom?: number;
17
+ radius?: number;
18
+ grayscale?: number;
19
+ toneMapped?: boolean;
20
+ transparent?: boolean;
21
+ opacity?: number;
22
+ side?: THREE.Side;
23
+ children?: OctaneNode;
24
+ } & ImageSource;
25
+
26
+ const ImageMaterialImpl = shaderMaterial(
27
+ {
28
+ color: new THREE.Color('white'),
29
+ scale: new THREE.Vector2(1, 1),
30
+ imageBounds: new THREE.Vector2(1, 1),
31
+ resolution: 1024,
32
+ map: null,
33
+ zoom: 1,
34
+ radius: 0,
35
+ grayscale: 0,
36
+ opacity: 1,
37
+ },
38
+ `varying vec2 vUv;
39
+ varying vec2 vPos;
40
+ void main() {
41
+ gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.);
42
+ vUv = uv;
43
+ vPos = position.xy;
44
+ }`,
45
+ `varying vec2 vUv;
46
+ varying vec2 vPos;
47
+ uniform vec2 scale;
48
+ uniform vec2 imageBounds;
49
+ uniform float resolution;
50
+ uniform vec3 color;
51
+ uniform sampler2D map;
52
+ uniform float radius;
53
+ uniform float zoom;
54
+ uniform float grayscale;
55
+ uniform float opacity;
56
+ const vec3 luma = vec3(.299, 0.587, 0.114);
57
+ vec4 toGrayscale(vec4 color, float intensity) { return vec4(mix(color.rgb, vec3(dot(color.rgb, luma)), intensity), color.a); }
58
+ vec2 aspect(vec2 size) { return size / min(size.x, size.y); }
59
+ float udRoundBox(vec2 p, vec2 b, float r) { return length(max(abs(p) - b + r, 0.0)) - r; }
60
+ void main() {
61
+ vec2 s = aspect(scale);
62
+ vec2 i = aspect(imageBounds);
63
+ float rs = s.x / s.y;
64
+ float ri = i.x / i.y;
65
+ vec2 nextSize = rs < ri ? vec2(i.x * s.y / i.y, s.y) : vec2(s.x, i.y * s.x / i.x);
66
+ vec2 offset = (rs < ri ? vec2((nextSize.x - s.x) / 2.0, 0.0) : vec2(0.0, (nextSize.y - s.y) / 2.0)) / nextSize;
67
+ vec2 uv = vUv * s / nextSize + offset;
68
+ vec2 zUv = (uv - vec2(0.5)) / zoom + vec2(0.5);
69
+ vec2 res = vec2(scale * resolution);
70
+ vec2 halfRes = 0.5 * res;
71
+ float b = udRoundBox(vUv.xy * res - halfRes, halfRes, resolution * radius);
72
+ vec3 a = mix(vec3(1.0, 0.0, 0.0), vec3(0.0), smoothstep(0.0, 1.0, b));
73
+ gl_FragColor = toGrayscale(texture2D(map, zUv) * vec4(color, opacity * a), grayscale);
74
+ #include <tonemapping_fragment>
75
+ #include <colorspace_fragment>
76
+ }`,
77
+ );
78
+ const ImageMaterialElement = extend(ImageMaterialImpl);
79
+
80
+ type ImageBaseProps = Omit<ImageProps, 'url'> & { texture: THREE.Texture };
81
+
82
+ function ImageBase({
83
+ children,
84
+ color,
85
+ segments = 1,
86
+ scale = 1,
87
+ zoom = 1,
88
+ grayscale = 0,
89
+ opacity = 1,
90
+ radius = 0,
91
+ texture,
92
+ toneMapped,
93
+ transparent,
94
+ side,
95
+ ref,
96
+ ...props
97
+ }: ImageBaseProps & { ref?: ThreeRef<THREE.Mesh> }) @{
98
+ const mesh = useRef<THREE.Mesh | null>(null);
99
+ const size = useThree((state) => state.size);
100
+ const planeBounds: [number, number] = Array.isArray(scale) ? [scale[0], scale[1]] : [scale, scale];
101
+ const image = texture.image as { width: number; height: number };
102
+ const imageBounds: [number, number] = [image.width, image.height];
103
+ const resolution = Math.max(size.width, size.height);
104
+ useImperativeHandle((ref ?? null) as any, () => mesh.current!, []);
105
+ useLayoutEffect(() => {
106
+ const geometry = mesh.current?.geometry as THREE.BufferGeometry & { parameters?: { width?: number; height?: number } };
107
+ const material = mesh.current?.material as THREE.ShaderMaterial & { scale: THREE.Vector2 };
108
+ if (geometry?.parameters) material.scale.set(
109
+ planeBounds[0] * (geometry.parameters.width ?? 1),
110
+ planeBounds[1] * (geometry.parameters.height ?? 1),
111
+ );
112
+ }, [planeBounds[0], planeBounds[1]]);
113
+ <mesh ref={mesh} scale={Array.isArray(scale) ? [...scale, 1] : scale} {...props}>
114
+ <planeGeometry args={[1, 1, segments, segments]} />
115
+ <ImageMaterialElement
116
+ color={color}
117
+ map={texture as any}
118
+ zoom={zoom}
119
+ grayscale={grayscale}
120
+ opacity={opacity}
121
+ scale={planeBounds}
122
+ imageBounds={imageBounds}
123
+ resolution={resolution}
124
+ radius={radius}
125
+ toneMapped={toneMapped}
126
+ transparent={transparent}
127
+ side={side}
128
+ />
129
+ {children}
130
+ </mesh>
131
+ }
132
+
133
+ function ImageWithUrl({ url, ...props }: ImageProps & { ref?: ThreeRef<THREE.Mesh> }) @{
134
+ const texture = useTexture(url!, undefined) as THREE.Texture;
135
+ <ImageBase {...props as any} texture={texture} />
136
+ }
137
+
138
+ function ImageWithTexture({ url: _url, ...props }: ImageProps & { ref?: ThreeRef<THREE.Mesh> }) @{
139
+ <ImageBase {...props as any} />
140
+ }
141
+
142
+ export function Image(props: ImageProps & { ref?: ThreeRef<THREE.Mesh> }) @{
143
+ const component = props.url ? ImageWithUrl : props.texture ? ImageWithTexture : null;
144
+ if (!component) throw new Error('<Image /> requires a url or texture');
145
+ <>{universalComponent('three', component as any, props as unknown as Record<string, unknown>)}</>
146
+ }
@@ -0,0 +1,332 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import {
3
+ extend,
4
+ type ThreeElement,
5
+ type ThreeElements,
6
+ type ThreeRef,
7
+ useFrame,
8
+ useInstanceHandle,
9
+ } from '@octanejs/three';
10
+ import {
11
+ useEffect,
12
+ useLayoutEffect,
13
+ useMemo,
14
+ useRef,
15
+ useState,
16
+ } from '@octanejs/three/renderer';
17
+ import {
18
+ createContext,
19
+ type Context,
20
+ type OctaneNode,
21
+ useContext,
22
+ } from 'octane';
23
+ import {
24
+ defineUniversalComponent,
25
+ universalComponent,
26
+ universalContext,
27
+ type UniversalComponent,
28
+ type UniversalRenderable,
29
+ } from 'octane/universal';
30
+ import * as THREE from 'three';
31
+ import { isOctaneChildrenBlock } from './octane-element.js';
32
+
33
+ type PositionMeshRef = { current: PositionMesh | null };
34
+ type InstancedMeshRef = { current: THREE.InstancedMesh | null };
35
+ type Api = {
36
+ getParent: () => InstancedMeshRef;
37
+ subscribe: <T extends PositionMesh>(ref: { current: T | null }) => () => void;
38
+ };
39
+
40
+ export type InstancesProps = Omit<ThreeElements['instancedMesh'], 'ref' | 'args'> & {
41
+ context?: Context<Api | null>;
42
+ range?: number;
43
+ limit?: number;
44
+ frames?: number;
45
+ children?: OctaneNode | ((Instance: InstanceComponent) => OctaneNode);
46
+ };
47
+
48
+ export type InstanceProps = Omit<ThreeElement<typeof PositionMesh>, 'ref'> & {
49
+ context?: Context<Api | null>;
50
+ };
51
+
52
+ export type InstancedAttributeProps = Omit<ThreeElements['instancedBufferAttribute'], 'ref' | 'args'> & {
53
+ name: string;
54
+ defaultValue: any;
55
+ normalized?: boolean;
56
+ usage?: number;
57
+ };
58
+
59
+ type InstanceComponent = UniversalComponent<InstanceProps & { ref?: ThreeRef<PositionMesh> }>;
60
+
61
+ const instanceLocalMatrix = new THREE.Matrix4();
62
+ const instanceWorldMatrix = new THREE.Matrix4();
63
+ const instanceIntersects: THREE.Intersection[] = [];
64
+ const raycastMesh = new THREE.Mesh<THREE.BufferGeometry, THREE.MeshBasicMaterial>();
65
+
66
+ export class PositionMesh extends THREE.Group {
67
+ color = new THREE.Color('white');
68
+ instance: InstancedMeshRef = { current: null };
69
+ instanceKey: PositionMeshRef = { current: null };
70
+
71
+ get geometry(): THREE.BufferGeometry | undefined {
72
+ return this.instance.current?.geometry;
73
+ }
74
+
75
+ override raycast(raycaster: THREE.Raycaster, intersects: THREE.Intersection[]): void {
76
+ const parent = this.instance.current;
77
+ if (!parent?.geometry || !parent.material) return;
78
+ raycastMesh.geometry = parent.geometry;
79
+ const instanceId = (parent.userData.instances as PositionMeshRef[]).indexOf(this.instanceKey);
80
+ if (instanceId === -1 || instanceId > parent.count) return;
81
+ parent.getMatrixAt(instanceId, instanceLocalMatrix);
82
+ instanceWorldMatrix.multiplyMatrices(parent.matrixWorld, instanceLocalMatrix);
83
+ raycastMesh.matrixWorld = instanceWorldMatrix;
84
+ const material = Array.isArray(parent.material) ? parent.material[0] : parent.material;
85
+ if (material) raycastMesh.material.side = material.side;
86
+ raycastMesh.raycast(raycaster, instanceIntersects);
87
+ for (const intersect of instanceIntersects) {
88
+ intersect.instanceId = instanceId;
89
+ intersect.object = this;
90
+ intersects.push(intersect);
91
+ }
92
+ instanceIntersects.length = 0;
93
+ }
94
+ }
95
+
96
+ const PositionMeshElement = extend(PositionMesh);
97
+ const globalContext = createContext<Api | null>(null);
98
+ const parentMatrix = new THREE.Matrix4();
99
+ const instanceMatrix = new THREE.Matrix4();
100
+ const tempMatrix = new THREE.Matrix4();
101
+ const translation = new THREE.Vector3();
102
+ const rotation = new THREE.Quaternion();
103
+ const scale = new THREE.Vector3();
104
+
105
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
106
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
107
+ else if (typeof ref === 'function') ref(value);
108
+ else if (ref && 'current' in ref) ref.current = value;
109
+ }
110
+
111
+ export function Instance({ context, children, ref, ...props }: InstanceProps & {
112
+ ref?: ThreeRef<PositionMesh>;
113
+ }) @{
114
+ const group = useRef<PositionMesh | null>(null);
115
+ const api = useContext(context ?? globalContext);
116
+ if (!api) throw new Error('Instance must be used inside Instances component.');
117
+ const mergedRef = useMemo(
118
+ () => (value: PositionMesh | null) => {
119
+ group.current = value;
120
+ assignRef(ref, value);
121
+ },
122
+ [ref],
123
+ );
124
+ useLayoutEffect(() => api.subscribe(group), [api]);
125
+ <PositionMeshElement
126
+ instance={api.getParent()}
127
+ instanceKey={group}
128
+ ref={mergedRef}
129
+ {...props as any}
130
+ >
131
+ {children}
132
+ </PositionMeshElement>
133
+ }
134
+
135
+ export function Instances({
136
+ context,
137
+ children,
138
+ range,
139
+ limit = 1000,
140
+ frames = Infinity,
141
+ ref,
142
+ ...props
143
+ }: InstancesProps & { ref?: ThreeRef<THREE.InstancedMesh> }) @{
144
+ const [{ localContext, LocalInstance }] = useState(() => {
145
+ const local = createContext<Api | null>(null);
146
+ const BoundInstance = defineUniversalComponent<InstanceProps & { ref?: ThreeRef<PositionMesh> }>(
147
+ 'three',
148
+ (instanceProps) => universalComponent('three', Instance as any, { ...instanceProps, context: local }),
149
+ );
150
+ return { localContext: local, LocalInstance: BoundInstance };
151
+ });
152
+ const parentRef = useRef<THREE.InstancedMesh | null>(null);
153
+ const [instances] = useState<PositionMeshRef[]>([]);
154
+ const [[matrices, colors]] = useState(() => {
155
+ const matrixArray = new Float32Array(limit * 16);
156
+ for (let index = 0; index < limit; index++) tempMatrix.identity().toArray(matrixArray, index * 16);
157
+ return [matrixArray, Float32Array.from({ length: limit * 3 }, () => 1)];
158
+ });
159
+ const mergedRef = useMemo(
160
+ () => (value: THREE.InstancedMesh | null) => {
161
+ parentRef.current = value;
162
+ assignRef(ref, value);
163
+ },
164
+ [ref],
165
+ );
166
+ const api = useMemo<Api>(() => ({
167
+ getParent: () => parentRef,
168
+ subscribe(instanceRef) {
169
+ instances.push(instanceRef);
170
+ return () => {
171
+ const index = instances.findIndex((item) => item.current === instanceRef.current);
172
+ if (index !== -1) instances.splice(index, 1);
173
+ };
174
+ },
175
+ }), [instances]);
176
+ const iterations = useRef(0);
177
+
178
+ useEffect(() => {
179
+ if (parentRef.current) parentRef.current.instanceMatrix.needsUpdate = true;
180
+ });
181
+ useFrame(() => {
182
+ const parent = parentRef.current;
183
+ if (!parent || (frames !== Infinity && iterations.current >= frames)) return;
184
+ parent.updateMatrix();
185
+ parent.updateMatrixWorld();
186
+ parentMatrix.copy(parent.matrixWorld).invert();
187
+ const count = Math.min(limit, range ?? limit, instances.length);
188
+ parent.count = count;
189
+ parent.instanceMatrix.updateRanges[0] = { start: 0, count: count * 16 };
190
+ if (parent.instanceColor) parent.instanceColor.updateRanges[0] = { start: 0, count: count * 3 };
191
+ for (let index = 0; index < instances.length; index++) {
192
+ const instance = instances[index]?.current;
193
+ if (!instance) continue;
194
+ instance.matrixWorld.decompose(translation, rotation, scale);
195
+ instanceMatrix.compose(translation, rotation, scale).premultiply(parentMatrix);
196
+ instanceMatrix.toArray(matrices, index * 16);
197
+ parent.instanceMatrix.needsUpdate = true;
198
+ instance.color.toArray(colors, index * 3);
199
+ if (parent.instanceColor) parent.instanceColor.needsUpdate = true;
200
+ }
201
+ iterations.current++;
202
+ });
203
+ const providedChildren = typeof children === 'function' && !isOctaneChildrenBlock(children)
204
+ ? universalContext(localContext, api, children(LocalInstance) as UniversalRenderable)
205
+ : universalContext(context ?? globalContext, api, children as UniversalRenderable);
206
+
207
+ <instancedMesh
208
+ userData={{ instances, limit, frames }}
209
+ matrixAutoUpdate={false}
210
+ ref={mergedRef}
211
+ args={[null as any, null as any, 0]}
212
+ raycast={() => null}
213
+ {...props as any}
214
+ >
215
+ <instancedBufferAttribute attach="instanceMatrix" args={[matrices, 16]} usage={THREE.DynamicDrawUsage} />
216
+ <instancedBufferAttribute attach="instanceColor" args={[colors, 3]} usage={THREE.DynamicDrawUsage} />
217
+ {providedChildren}
218
+ </instancedMesh>
219
+ }
220
+
221
+ export interface MergedProps extends Omit<InstancesProps, 'children'> {
222
+ meshes: THREE.Mesh[] | Record<string, THREE.Object3D>;
223
+ children: (...instances: [InstanceComponent & Record<string, InstanceComponent>, ...InstanceComponent[]]) => OctaneNode;
224
+ }
225
+
226
+ function renderRecursive(
227
+ render: (args: unknown[]) => OctaneNode,
228
+ components: Array<(children: (value: unknown) => UniversalRenderable) => UniversalRenderable>,
229
+ results: unknown[] = [],
230
+ ): UniversalRenderable {
231
+ if (!components[0]) return render(results) as UniversalRenderable;
232
+ const nextRender = (value: unknown) => renderRecursive(render, components.slice(1), [...results, value]);
233
+ return components[0](nextRender);
234
+ }
235
+
236
+ export function Merged({ meshes, children, ref, ...props }: MergedProps & {
237
+ ref?: ThreeRef<THREE.Group>;
238
+ }) @{
239
+ const isArray = Array.isArray(meshes);
240
+ if (!isArray) {
241
+ for (const key of Object.keys(meshes)) if (!(meshes[key] as any).isMesh) delete meshes[key];
242
+ }
243
+ const meshValues = (isArray ? meshes : Object.values(meshes)) as THREE.Mesh[];
244
+ const components = meshValues.map(({ geometry, material }) => (
245
+ (child: (value: unknown) => UniversalRenderable) => universalComponent(
246
+ 'three',
247
+ Instances as any,
248
+ { ...props, geometry, material, children: child },
249
+ geometry.uuid,
250
+ )
251
+ ));
252
+ const render = (args: unknown[]) => isArray
253
+ ? children(...args as [InstanceComponent & Record<string, InstanceComponent>, ...InstanceComponent[]])
254
+ : children(Object.keys(meshes).filter((key) => (meshes[key] as any).isMesh).reduce(
255
+ (accumulator, key, index) => ({ ...accumulator, [key]: args[index] }),
256
+ {},
257
+ ) as InstanceComponent & Record<string, InstanceComponent>);
258
+ const rendered = typeof children === 'function' && !isOctaneChildrenBlock(children)
259
+ ? renderRecursive(render, components)
260
+ // A compiled children block is renderer-owned even though Merged's public render-prop
261
+ // signature makes TypeScript retain the callable branch here.
262
+ : children as unknown as UniversalRenderable;
263
+ <group ref={ref}>{rendered}</group>
264
+ }
265
+
266
+ export function createInstances<T = InstanceProps>() {
267
+ const context = createContext<Api | null>(null);
268
+ const BoundInstances = defineUniversalComponent<InstancesProps & { ref?: ThreeRef<THREE.InstancedMesh> }>(
269
+ 'three',
270
+ (props) => universalComponent('three', Instances as any, { ...props, context }),
271
+ );
272
+ const BoundInstance = defineUniversalComponent<T & InstanceProps & { ref?: ThreeRef<PositionMesh & T> }>(
273
+ 'three',
274
+ (props) => universalComponent('three', Instance as any, { ...props, context }),
275
+ );
276
+ return [BoundInstances, BoundInstance] as const;
277
+ }
278
+
279
+ export function InstancedAttribute({
280
+ name,
281
+ defaultValue,
282
+ normalized,
283
+ usage = THREE.DynamicDrawUsage,
284
+ ref,
285
+ ...props
286
+ }: InstancedAttributeProps & { ref?: ThreeRef<THREE.InstancedBufferAttribute> }) @{
287
+ const attributeRef = useRef<THREE.InstancedBufferAttribute | null>(null);
288
+ const instance = useInstanceHandle(attributeRef);
289
+ const mergedRef = useMemo(
290
+ () => (value: THREE.InstancedBufferAttribute | null) => {
291
+ attributeRef.current = value;
292
+ assignRef(ref, value);
293
+ },
294
+ [ref],
295
+ );
296
+ let iterations = 0;
297
+ useLayoutEffect(() => {
298
+ const attribute = attributeRef.current;
299
+ const parent = instance.current?.parent?.object as THREE.InstancedMesh | undefined;
300
+ if (!attribute || !parent) return;
301
+ parent.geometry.attributes[name] = attribute;
302
+ const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
303
+ const array = Array.from({ length: parent.userData.limit }, () => value).flat();
304
+ attribute.array = new Float32Array(array);
305
+ attribute.itemSize = value.length;
306
+ (attribute as any).count = array.length / attribute.itemSize;
307
+ return () => { delete parent.geometry.attributes[name]; };
308
+ }, [name]);
309
+ useFrame(() => {
310
+ const attribute = attributeRef.current;
311
+ const parent = instance.current?.parent?.object as THREE.InstancedMesh | undefined;
312
+ if (!attribute || !parent || (parent.userData.frames !== Infinity && iterations >= parent.userData.frames)) return;
313
+ for (let index = 0; index < parent.userData.instances.length; index++) {
314
+ const item = parent.userData.instances[index].current as Record<string, any>;
315
+ const value = item[name];
316
+ if (value === undefined) continue;
317
+ attribute.set(
318
+ Array.isArray(value) ? value : typeof value.toArray === 'function' ? value.toArray() : [value],
319
+ index * attribute.itemSize,
320
+ );
321
+ attribute.needsUpdate = true;
322
+ }
323
+ iterations++;
324
+ });
325
+ <instancedBufferAttribute
326
+ ref={mergedRef}
327
+ args={[new Float32Array(0), 1]}
328
+ usage={usage}
329
+ normalized={normalized}
330
+ {...props as any}
331
+ />
332
+ }
@@ -0,0 +1,59 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { useLoader, useThree } from '@octanejs/three';
3
+ import { useEffect } from '@octanejs/three/renderer';
4
+ import type { Texture } from 'three';
5
+ import { KTX2Loader } from 'three-stdlib';
6
+ import { slotDefault } from './compiler-slot.js';
7
+ import { IsObject } from './Texture.three.tsrx';
8
+ import { resolveRenderChildren, type RenderChildren } from './octane-children.js';
9
+
10
+ const cdn = 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master';
11
+
12
+ export function useKTX2<Url extends string[] | string | Record<string, string>>(
13
+ input: Url,
14
+ basisPath = `${cdn}/basis/`,
15
+ ): Url extends any[] ? Texture[] : Url extends object ? { [key in keyof Url]: Texture } : Texture {
16
+ basisPath = slotDefault(basisPath, `${cdn}/basis/`);
17
+ const gl = useThree((state) => state.gl);
18
+ const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (
19
+ loader,
20
+ ) => {
21
+ loader.detectSupport(gl as unknown as Parameters<KTX2Loader['detectSupport']>[0]);
22
+ loader.setTranscoderPath(basisPath);
23
+ });
24
+
25
+ useEffect(() => {
26
+ const array = Array.isArray(textures) ? textures : [textures];
27
+ for (const texture of array) gl.initTexture(texture);
28
+ }, [gl, textures]);
29
+
30
+ if (!IsObject(input)) return textures as any;
31
+ const keys = Object.keys(input);
32
+ const keyed: Record<string, Texture> = {};
33
+ for (const [index, key] of keys.entries()) {
34
+ keyed[key] = (textures as unknown as Texture[])[index];
35
+ }
36
+ return keyed as any;
37
+ }
38
+
39
+ useKTX2.preload = (url: string[] | string, basisPath = `${cdn}/basis/`) => useLoader.preload(
40
+ KTX2Loader,
41
+ url,
42
+ (loader) => loader.setTranscoderPath(basisPath),
43
+ );
44
+ useKTX2.clear = (input: string | string[]) => useLoader.clear(KTX2Loader, input);
45
+
46
+ export function Ktx2({
47
+ children,
48
+ input,
49
+ basisPath,
50
+ }: {
51
+ children?: RenderChildren<ReturnType<typeof useKTX2>>;
52
+ input: Parameters<typeof useKTX2>[0];
53
+ basisPath?: Parameters<typeof useKTX2>[1];
54
+ }) @{
55
+ const texture = useKTX2(input, basisPath);
56
+ <>
57
+ {resolveRenderChildren(children, texture)}
58
+ </>
59
+ }
@@ -0,0 +1,75 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { applyProps, type Color, type ThreeElements, type ThreeRef } from '@octanejs/three';
3
+ import { useLayoutEffect, useMemo, useRef } from '@octanejs/three/renderer';
4
+ import * as THREE from 'three';
5
+
6
+ export type LightProps = Omit<ThreeElements['mesh'], 'ref' | 'scale' | 'args'> & {
7
+ args?: any[];
8
+ map?: THREE.Texture;
9
+ toneMapped?: boolean;
10
+ color?: Color;
11
+ form?: 'circle' | 'ring' | 'rect' | 'plane' | 'box' | any;
12
+ scale?: number | [number, number, number] | [number, number];
13
+ intensity?: number;
14
+ target?: boolean | [number, number, number] | THREE.Vector3;
15
+ light?: Partial<ThreeElements['pointLight']>;
16
+ };
17
+
18
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
19
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
20
+ else if (typeof ref === 'function') ref(value);
21
+ else if (ref && 'current' in ref) ref.current = value;
22
+ }
23
+
24
+ export function Lightformer({
25
+ light,
26
+ args,
27
+ map,
28
+ toneMapped = false,
29
+ color = 'white',
30
+ form: Form = 'rect',
31
+ intensity = 1,
32
+ scale: scaleProp = 1,
33
+ target = [0, 0, 0],
34
+ children,
35
+ ref,
36
+ ...props
37
+ }: LightProps & { ref?: ThreeRef<THREE.Mesh> }) @{
38
+ const localRef = useRef<THREE.Mesh<THREE.BufferGeometry, THREE.MeshBasicMaterial> | null>(null);
39
+ const mergedRef = useMemo(
40
+ () => (value: THREE.Mesh | null) => {
41
+ localRef.current = value as THREE.Mesh<THREE.BufferGeometry, THREE.MeshBasicMaterial> | null;
42
+ assignRef(ref, value);
43
+ },
44
+ [ref],
45
+ );
46
+ useLayoutEffect(() => {
47
+ if (!localRef.current || children || props.material) return;
48
+ applyProps(localRef.current.material, { color });
49
+ localRef.current.material.color.multiplyScalar(intensity);
50
+ }, [color, intensity, children, props.material]);
51
+ useLayoutEffect(() => {
52
+ if (!localRef.current) return;
53
+ if (!props.rotation) localRef.current.quaternion.identity();
54
+ if (target && !props.rotation) {
55
+ if (typeof target === 'boolean') localRef.current.lookAt(0, 0, 0);
56
+ else localRef.current.lookAt(Array.isArray(target) ? new THREE.Vector3(...target) : target);
57
+ }
58
+ }, [target, props.rotation]);
59
+ const scale = Array.isArray(scaleProp) && scaleProp.length === 2
60
+ ? [scaleProp[0], scaleProp[1], 1] as [number, number, number]
61
+ : scaleProp;
62
+ <mesh ref={mergedRef} scale={scale} {...props}>
63
+ {Form === 'circle'
64
+ ? <ringGeometry args={(args ?? [0, 0.5, 64]) as any} />
65
+ : Form === 'ring'
66
+ ? <ringGeometry args={(args ?? [0.25, 0.5, 64]) as any} />
67
+ : Form === 'rect' || Form === 'plane'
68
+ ? <planeGeometry args={(args ?? [1, 1]) as any} />
69
+ : Form === 'box'
70
+ ? <boxGeometry args={(args ?? [1, 1, 1]) as any} />
71
+ : <Form args={args} />}
72
+ {children || <meshBasicMaterial toneMapped={toneMapped} map={map} side={THREE.DoubleSide} />}
73
+ {light && <pointLight castShadow {...light} />}
74
+ </mesh>
75
+ }