@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,167 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import {
3
+ createPortal,
4
+ extend,
5
+ type ThreeElement,
6
+ type ThreeRef,
7
+ useFrame,
8
+ useInstanceHandle,
9
+ useThree,
10
+ } from '@octanejs/three';
11
+ import {
12
+ useEffect,
13
+ useLayoutEffect,
14
+ useMemo,
15
+ useRef,
16
+ useState,
17
+ type RefObject,
18
+ } from '@octanejs/three/renderer';
19
+ import type { OctaneNode } from 'octane';
20
+ import { MeshLineGeometry as MeshLineGeometryImpl, MeshLineMaterial } from 'meshline';
21
+ import {
22
+ type ColorRepresentation,
23
+ Group,
24
+ Mesh,
25
+ Object3D,
26
+ Vector2,
27
+ Vector3,
28
+ } from 'three';
29
+
30
+ declare module '@octanejs/three' {
31
+ interface ThreeElements {
32
+ meshLineMaterial: ThreeElement<typeof MeshLineMaterial>;
33
+ }
34
+ }
35
+
36
+ extend({ MeshLineMaterial });
37
+
38
+ type Settings = {
39
+ width: number;
40
+ length: number;
41
+ decay: number;
42
+ local: boolean;
43
+ stride: number;
44
+ interval: number;
45
+ };
46
+
47
+ export type TrailProps = {
48
+ color?: ColorRepresentation;
49
+ attenuation?: (width: number) => number;
50
+ target?: RefObject<Object3D>;
51
+ children?: OctaneNode;
52
+ } & Partial<Settings>;
53
+
54
+ const defaults: Partial<Settings> = {
55
+ width: 0.2,
56
+ length: 1,
57
+ decay: 1,
58
+ local: false,
59
+ stride: 0,
60
+ interval: 1,
61
+ };
62
+
63
+ const shiftLeft = (collection: Float32Array, steps = 1): Float32Array => {
64
+ collection.set(collection.subarray(steps));
65
+ collection.fill(-Infinity, -steps);
66
+ return collection;
67
+ };
68
+
69
+ export function useTrail(
70
+ target: Object3D | null,
71
+ settings: Partial<Settings>,
72
+ ): RefObject<Float32Array | null> {
73
+ const { length, local, decay, interval, stride } = { ...defaults, ...settings } as Settings;
74
+ const points = useRef<Float32Array | null>(null);
75
+ const [worldPosition] = useState(() => new Vector3());
76
+ const previousPosition = useRef(new Vector3());
77
+ const frameCount = useRef(0);
78
+
79
+ useLayoutEffect(() => {
80
+ if (target) {
81
+ points.current = Float32Array.from(
82
+ { length: length * 10 * 3 },
83
+ (_, index) => target.position.getComponent(index % 3),
84
+ );
85
+ }
86
+ }, [length, target]);
87
+ useFrame(() => {
88
+ if (!target || !points.current) return;
89
+ if (frameCount.current === 0) {
90
+ const newPosition = local ? target.position : target.getWorldPosition(worldPosition);
91
+ for (let index = 0; index < decay; index++) {
92
+ if (newPosition.distanceTo(previousPosition.current) < stride) continue;
93
+ shiftLeft(points.current, 3);
94
+ points.current.set(newPosition.toArray(), points.current.length - 3);
95
+ }
96
+ previousPosition.current.copy(newPosition);
97
+ }
98
+ frameCount.current++;
99
+ frameCount.current %= interval;
100
+ });
101
+ return points;
102
+ }
103
+
104
+ export type MeshLineGeometry = Mesh & MeshLineGeometryImpl;
105
+
106
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
107
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
108
+ else if (typeof ref === 'function') ref(value);
109
+ else if (ref && 'current' in ref) ref.current = value;
110
+ }
111
+
112
+ export function Trail({
113
+ children,
114
+ width = 0.2,
115
+ length = 1,
116
+ decay = 1,
117
+ local = false,
118
+ stride = 0,
119
+ interval = 1,
120
+ color = 'hotpink',
121
+ attenuation,
122
+ target,
123
+ ref,
124
+ }: TrailProps & { ref?: ThreeRef<MeshLineGeometry> }) @{
125
+ const size = useThree((state) => state.size);
126
+ const scene = useThree((state) => state.scene);
127
+ const anchorGroup = useRef<Group | null>(null);
128
+ const anchorHandle = useInstanceHandle(anchorGroup);
129
+ const [anchor, setAnchor] = useState<Object3D | null>(null);
130
+ const points = useTrail(anchor, { length, decay, local, stride, interval });
131
+ const [geometry] = useState(() => new MeshLineGeometryImpl());
132
+ const material = useMemo(() => new MeshLineMaterial({
133
+ lineWidth: 0.1 * width,
134
+ color,
135
+ sizeAttenuation: 1,
136
+ resolution: new Vector2(size.width, size.height),
137
+ } as any), [width, color, size]);
138
+ const mergedRef = useMemo(
139
+ () => (value: Mesh | null) => assignRef(ref, value as MeshLineGeometry | null),
140
+ [ref],
141
+ );
142
+
143
+ useEffect(() => {
144
+ const next = target?.current ?? anchorGroup.current?.children.find((object) => object instanceof Object3D);
145
+ if (next) setAnchor(next);
146
+ }, [points, target]);
147
+ useLayoutEffect(() => {
148
+ const override = anchorHandle.current?.children.find(
149
+ (child) => (child.object as any)?.isMeshLineMaterial === true || child.object instanceof MeshLineMaterial,
150
+ );
151
+ if (override) material.setValues(override.props as any);
152
+ }, [anchorHandle, material, children]);
153
+ useEffect(() => {
154
+ material.uniforms.resolution.value.set(size.width, size.height);
155
+ }, [material, size]);
156
+ useFrame(() => {
157
+ if (points.current) geometry.setPoints(points.current as any, attenuation);
158
+ });
159
+
160
+ <group>
161
+ {createPortal(
162
+ <mesh ref={mergedRef} geometry={geometry as any} material={material as any} />,
163
+ scene,
164
+ )}
165
+ <group ref={anchorGroup}>{children}</group>
166
+ </group>
167
+ }
@@ -0,0 +1,171 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { useFrame } from '@octanejs/three';
3
+ import { useCallback, useMemo } from '@octanejs/three/renderer';
4
+ import { Texture } from 'three';
5
+ import { slotDefault } from './compiler-slot.js';
6
+ import { resolveRenderChildren, type RenderChildren } from './octane-children.js';
7
+
8
+ type Point = { x: number; y: number; age: number; force: number };
9
+
10
+ type TrailConfig = {
11
+ size?: number;
12
+ maxAge?: number;
13
+ radius?: number;
14
+ intensity?: number;
15
+ interpolate?: number;
16
+ smoothing?: number;
17
+ minForce?: number;
18
+ blend?: CanvasRenderingContext2D['globalCompositeOperation'];
19
+ ease?: (value: number) => number;
20
+ };
21
+
22
+ const smoothAverage = (current: number, measurement: number, smoothing = 0.9) =>
23
+ measurement * smoothing + current * (1 - smoothing);
24
+ const easeCircleOut = (value: number) => Math.sqrt(1 - Math.pow(value - 1, 2));
25
+
26
+ class TrailTextureImpl {
27
+ trail: Point[] = [];
28
+ canvas!: HTMLCanvasElement;
29
+ ctx!: CanvasRenderingContext2D;
30
+ texture!: Texture;
31
+ force = 0;
32
+ size: number;
33
+ maxAge: number;
34
+ radius: number;
35
+ intensity: number;
36
+ ease: (value: number) => number;
37
+ minForce: number;
38
+ interpolate: number;
39
+ smoothing: number;
40
+ blend: CanvasRenderingContext2D['globalCompositeOperation'];
41
+
42
+ constructor({
43
+ size = 256,
44
+ maxAge = 750,
45
+ radius = 0.3,
46
+ intensity = 0.2,
47
+ interpolate = 0,
48
+ smoothing = 0,
49
+ minForce = 0.3,
50
+ blend = 'screen',
51
+ ease = easeCircleOut,
52
+ }: TrailConfig = {}) {
53
+ this.size = size;
54
+ this.maxAge = maxAge;
55
+ this.radius = radius;
56
+ this.intensity = intensity;
57
+ this.ease = ease;
58
+ this.interpolate = interpolate;
59
+ this.smoothing = smoothing;
60
+ this.minForce = minForce;
61
+ this.blend = blend;
62
+ this.initTexture();
63
+ }
64
+
65
+ initTexture(): void {
66
+ this.canvas = document.createElement('canvas');
67
+ this.canvas.width = this.canvas.height = this.size;
68
+ const context = this.canvas.getContext('2d');
69
+ if (context === null) throw new Error('2D not available');
70
+ this.ctx = context;
71
+ this.ctx.fillStyle = 'black';
72
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
73
+ this.texture = new Texture(this.canvas);
74
+ this.canvas.id = 'touchTexture';
75
+ this.canvas.style.width = this.canvas.style.height = `${this.canvas.width}px`;
76
+ }
77
+
78
+ update(delta: number): void {
79
+ this.clear();
80
+ this.trail.forEach((point, index) => {
81
+ point.age += delta * 1000;
82
+ if (point.age > this.maxAge) this.trail.splice(index, 1);
83
+ });
84
+ if (!this.trail.length) this.force = 0;
85
+ this.trail.forEach((point) => this.drawTouch(point));
86
+ this.texture.needsUpdate = true;
87
+ }
88
+
89
+ clear(): void {
90
+ this.ctx.globalCompositeOperation = 'source-over';
91
+ this.ctx.fillStyle = 'black';
92
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
93
+ }
94
+
95
+ addTouch(point: { x: number; y: number }): void {
96
+ const last = this.trail[this.trail.length - 1];
97
+ if (last) {
98
+ const dx = last.x - point.x;
99
+ const dy = last.y - point.y;
100
+ const distance = dx * dx + dy * dy;
101
+ const force = Math.max(this.minForce, Math.min(distance * 10_000, 1));
102
+ this.force = smoothAverage(force, this.force, this.smoothing);
103
+ if (this.interpolate) {
104
+ const lines = Math.ceil(distance / Math.pow((this.radius * 0.5) / this.interpolate, 2));
105
+ if (lines > 1) {
106
+ for (let index = 1; index < lines; index++) {
107
+ this.trail.push({
108
+ x: last.x - (dx / lines) * index,
109
+ y: last.y - (dy / lines) * index,
110
+ age: 0,
111
+ force,
112
+ });
113
+ }
114
+ }
115
+ }
116
+ }
117
+ this.trail.push({ x: point.x, y: point.y, age: 0, force: this.force });
118
+ }
119
+
120
+ drawTouch(point: Point): void {
121
+ const position = { x: point.x * this.size, y: (1 - point.y) * this.size };
122
+ let intensity = 1;
123
+ if (point.age < this.maxAge * 0.3) {
124
+ intensity = this.ease(point.age / (this.maxAge * 0.3));
125
+ } else {
126
+ intensity = this.ease(1 - (point.age - this.maxAge * 0.3) / (this.maxAge * 0.7));
127
+ }
128
+ intensity *= point.force;
129
+ this.ctx.globalCompositeOperation = this.blend;
130
+ const radius = this.size * this.radius * intensity;
131
+ const gradient = this.ctx.createRadialGradient(
132
+ position.x,
133
+ position.y,
134
+ Math.max(0, radius * 0.25),
135
+ position.x,
136
+ position.y,
137
+ Math.max(0, radius),
138
+ );
139
+ gradient.addColorStop(0, `rgba(255, 255, 255, ${this.intensity})`);
140
+ gradient.addColorStop(1, 'rgba(0, 0, 0, 0.0)');
141
+ this.ctx.beginPath();
142
+ this.ctx.fillStyle = gradient;
143
+ this.ctx.arc(position.x, position.y, Math.max(0, radius), 0, Math.PI * 2);
144
+ this.ctx.fill();
145
+ }
146
+ }
147
+
148
+ export function useTrailTexture(
149
+ config: Partial<TrailConfig> = {},
150
+ ): [Texture, (event: { uv: { x: number; y: number } }) => void] {
151
+ config = slotDefault(config, {});
152
+ const { size, maxAge, radius, intensity, interpolate, smoothing, minForce, blend, ease } = config;
153
+ const trail = useMemo(
154
+ () => new TrailTextureImpl(config),
155
+ [size, maxAge, radius, intensity, interpolate, smoothing, minForce, blend, ease],
156
+ );
157
+ useFrame((_state, delta) => void trail.update(delta));
158
+ const onMove = useCallback((event: { uv: { x: number; y: number } }) => trail.addTouch(event.uv), [trail]);
159
+ return [trail.texture, onMove];
160
+ }
161
+
162
+ export type TrailTextureProps = {
163
+ children?: RenderChildren<ReturnType<typeof useTrailTexture>>;
164
+ } & TrailConfig;
165
+
166
+ export function TrailTexture({ children, ...config }: TrailTextureProps) @{
167
+ const result = useTrailTexture(config);
168
+ <>
169
+ {resolveRenderChildren(children, result)}
170
+ </>
171
+ }
@@ -0,0 +1,155 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElement, type ThreeRef, useThree } from '@octanejs/three';
3
+ import {
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ } from '@octanejs/three/renderer';
9
+ import type { OctaneNode } from 'octane';
10
+ import * as THREE from 'three';
11
+ import { TransformControls as TransformControlsImpl } from 'three-stdlib';
12
+
13
+ type ControlsProto = { enabled: boolean };
14
+
15
+ export type TransformControlsProps = Omit<
16
+ ThreeElement<typeof TransformControlsImpl>,
17
+ 'ref' | 'args'
18
+ > &
19
+ Omit<ThreeElement<typeof THREE.Group>, 'ref'> & {
20
+ object?: THREE.Object3D | { current: THREE.Object3D | null };
21
+ enabled?: boolean;
22
+ axis?: string | null;
23
+ domElement?: HTMLElement;
24
+ mode?: 'translate' | 'rotate' | 'scale';
25
+ translationSnap?: number | null;
26
+ rotationSnap?: number | null;
27
+ scaleSnap?: number | null;
28
+ space?: 'world' | 'local';
29
+ size?: number;
30
+ showX?: boolean;
31
+ showY?: boolean;
32
+ showZ?: boolean;
33
+ children?: OctaneNode;
34
+ camera?: THREE.Camera;
35
+ onChange?: (event?: THREE.Event) => void;
36
+ onMouseDown?: (event?: THREE.Event) => void;
37
+ onMouseUp?: (event?: THREE.Event) => void;
38
+ onObjectChange?: (event?: THREE.Event) => void;
39
+ makeDefault?: boolean;
40
+ ref?: ThreeRef<TransformControlsImpl>;
41
+ };
42
+
43
+ export function TransformControls({
44
+ children,
45
+ domElement,
46
+ onChange,
47
+ onMouseDown,
48
+ onMouseUp,
49
+ onObjectChange,
50
+ object,
51
+ makeDefault,
52
+ camera,
53
+ enabled,
54
+ axis,
55
+ mode,
56
+ translationSnap,
57
+ rotationSnap,
58
+ scaleSnap,
59
+ space,
60
+ size,
61
+ showX,
62
+ showY,
63
+ showZ,
64
+ ref,
65
+ ...props
66
+ }: TransformControlsProps) @{
67
+ const defaultControls = useThree((state) => state.controls) as ControlsProto | undefined;
68
+ const gl = useThree((state) => state.gl);
69
+ const events = useThree((state) => state.events);
70
+ const defaultCamera = useThree((state) => state.camera);
71
+ const invalidate = useThree((state) => state.invalidate);
72
+ const get = useThree((state) => state.get);
73
+ const set = useThree((state) => state.set);
74
+ const explicitCamera = camera || defaultCamera;
75
+ const explicitDomElement = (domElement || events.connected || gl.domElement) as HTMLElement;
76
+ const controls = useMemo(
77
+ () => new TransformControlsImpl(explicitCamera as any, explicitDomElement),
78
+ [explicitCamera, explicitDomElement],
79
+ );
80
+ const group = useRef<THREE.Group | null>(null);
81
+
82
+ useLayoutEffect(() => {
83
+ if (object) {
84
+ controls.attach(object instanceof THREE.Object3D ? (object as any) : (object.current as any));
85
+ } else if (group.current instanceof THREE.Object3D) {
86
+ controls.attach(group.current as any);
87
+ }
88
+ return () => void controls.detach();
89
+ }, [object, children, controls]);
90
+
91
+ useEffect(() => {
92
+ if (defaultControls) {
93
+ const callback = (event: { value: boolean }) => (defaultControls.enabled = !event.value);
94
+ (controls as any).addEventListener('dragging-changed', callback);
95
+ return () => (controls as any).removeEventListener('dragging-changed', callback);
96
+ }
97
+ }, [controls, defaultControls]);
98
+
99
+ const onChangeRef = useRef(onChange);
100
+ const onMouseDownRef = useRef(onMouseDown);
101
+ const onMouseUpRef = useRef(onMouseUp);
102
+ const onObjectChangeRef = useRef(onObjectChange);
103
+ useLayoutEffect(() => void (onChangeRef.current = onChange), [onChange]);
104
+ useLayoutEffect(() => void (onMouseDownRef.current = onMouseDown), [onMouseDown]);
105
+ useLayoutEffect(() => void (onMouseUpRef.current = onMouseUp), [onMouseUp]);
106
+ useLayoutEffect(() => void (onObjectChangeRef.current = onObjectChange), [onObjectChange]);
107
+
108
+ useEffect(() => {
109
+ const change = (event: THREE.Event) => {
110
+ invalidate();
111
+ onChangeRef.current?.(event);
112
+ };
113
+ const mouseDown = (event: THREE.Event) => onMouseDownRef.current?.(event);
114
+ const mouseUp = (event: THREE.Event) => onMouseUpRef.current?.(event);
115
+ const objectChange = (event: THREE.Event) => onObjectChangeRef.current?.(event);
116
+ (controls as any).addEventListener('change', change);
117
+ (controls as any).addEventListener('mouseDown', mouseDown);
118
+ (controls as any).addEventListener('mouseUp', mouseUp);
119
+ (controls as any).addEventListener('objectChange', objectChange);
120
+ return () => {
121
+ (controls as any).removeEventListener('change', change);
122
+ (controls as any).removeEventListener('mouseDown', mouseDown);
123
+ (controls as any).removeEventListener('mouseUp', mouseUp);
124
+ (controls as any).removeEventListener('objectChange', objectChange);
125
+ };
126
+ }, [invalidate, controls]);
127
+
128
+ useEffect(() => {
129
+ if (makeDefault) {
130
+ const old = get().controls;
131
+ set({ controls });
132
+ return () => set({ controls: old });
133
+ }
134
+ }, [makeDefault, controls]);
135
+
136
+ <>
137
+ <primitive
138
+ ref={ref as any}
139
+ object={controls}
140
+ enabled={enabled}
141
+ mode={mode}
142
+ translationSnap={translationSnap}
143
+ rotationSnap={rotationSnap}
144
+ scaleSnap={scaleSnap}
145
+ space={space}
146
+ size={size}
147
+ showX={showX}
148
+ showY={showY}
149
+ showZ={showZ}
150
+ />
151
+ <group ref={group} {...props as any}>
152
+ {children}
153
+ </group>
154
+ </>
155
+ }
@@ -0,0 +1,116 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeRef, useThree } from '@octanejs/three';
3
+ import { use, useEffect, useLayoutEffect, useRef } from '@octanejs/three/renderer';
4
+ import type Hls from 'hls.js';
5
+ import { Events } from 'hls.js';
6
+ import * as THREE from 'three';
7
+ import { slotDefault } from './compiler-slot.js';
8
+ import { resolveRenderChildren, type RenderChildren } from './octane-children.js';
9
+ import { resourcePromise } from './resource.js';
10
+ import { getHls, IS_BROWSER } from './video-texture-resource.js';
11
+
12
+ export type UseVideoTextureOptions = {
13
+ unsuspend?: keyof HTMLVideoElementEventMap;
14
+ start?: boolean;
15
+ hls?: Parameters<typeof getHls>[0];
16
+ onVideoFrame?: VideoFrameRequestCallback;
17
+ } & Partial<Omit<HTMLVideoElement, 'children' | 'src' | 'srcObject'>>;
18
+
19
+ function useVideoFrame(video: HTMLVideoElement, callbackValue?: VideoFrameRequestCallback): void {
20
+ useEffect(() => {
21
+ if (!callbackValue || !video.requestVideoFrameCallback) return;
22
+ let handle: ReturnType<HTMLVideoElement['requestVideoFrameCallback']>;
23
+ const callback: VideoFrameRequestCallback = (...args) => {
24
+ callbackValue(...args);
25
+ handle = video.requestVideoFrameCallback(callback);
26
+ };
27
+ video.requestVideoFrameCallback(callback);
28
+ return () => video.cancelVideoFrameCallback(handle);
29
+ }, [video, callbackValue]);
30
+ }
31
+
32
+ export function useVideoTexture(
33
+ srcOrSrcObject: HTMLVideoElement['src' | 'srcObject'],
34
+ options: UseVideoTextureOptions = {},
35
+ ): THREE.VideoTexture {
36
+ const {
37
+ unsuspend = 'loadedmetadata',
38
+ start = true,
39
+ hls: hlsConfig = {},
40
+ crossOrigin = 'anonymous',
41
+ muted = true,
42
+ loop = true,
43
+ playsInline = true,
44
+ onVideoFrame,
45
+ ...videoProps
46
+ } = slotDefault(options, {});
47
+ const gl = useThree((state) => state.gl);
48
+ const hlsRef = useRef<Hls | null>(null);
49
+ const texture = use(
50
+ resourcePromise(
51
+ () =>
52
+ new Promise<THREE.VideoTexture>(async (resolve) => {
53
+ let src: string | undefined;
54
+ let srcObject: MediaProvider | null | undefined;
55
+ if (typeof srcOrSrcObject === 'string') src = srcOrSrcObject;
56
+ else srcObject = srcOrSrcObject;
57
+ const video = Object.assign(document.createElement('video'), {
58
+ src,
59
+ srcObject,
60
+ crossOrigin,
61
+ loop,
62
+ muted,
63
+ playsInline,
64
+ ...videoProps,
65
+ });
66
+ if (src && IS_BROWSER && src.endsWith('.m3u8')) {
67
+ const hls = (hlsRef.current = await getHls(hlsConfig));
68
+ if (hls) {
69
+ hls.on(Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
70
+ hls.attachMedia(video);
71
+ }
72
+ }
73
+ const value = new THREE.VideoTexture(video);
74
+ value.colorSpace = gl.outputColorSpace as THREE.ColorSpace;
75
+ video.addEventListener(unsuspend, () => resolve(value));
76
+ }),
77
+ [srcOrSrcObject],
78
+ ),
79
+ );
80
+ const video = texture.source.data as HTMLVideoElement;
81
+ useVideoFrame(video, onVideoFrame);
82
+ useEffect(() => {
83
+ if (start) void texture.image.play();
84
+ return () => {
85
+ if (hlsRef.current) {
86
+ hlsRef.current.destroy();
87
+ hlsRef.current = null;
88
+ }
89
+ };
90
+ }, [texture, start]);
91
+ return texture;
92
+ }
93
+
94
+ export type VideoTextureProps = {
95
+ children?: RenderChildren<THREE.VideoTexture>;
96
+ src: Parameters<typeof useVideoTexture>[0];
97
+ ref?: ThreeRef<THREE.VideoTexture>;
98
+ } & Parameters<typeof useVideoTexture>[1];
99
+
100
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
101
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
102
+ else if (typeof ref === 'function') ref(value);
103
+ else if (ref && 'current' in ref) ref.current = value;
104
+ }
105
+
106
+ export function VideoTexture({ children, src, ref, ...config }: VideoTextureProps) @{
107
+ const texture = useVideoTexture(src, config);
108
+ useEffect(() => () => void texture.dispose(), [texture]);
109
+ useLayoutEffect(() => {
110
+ assignRef(ref, texture);
111
+ return () => assignRef(ref, null);
112
+ }, [ref, texture]);
113
+ <>
114
+ {resolveRenderChildren(children, texture)}
115
+ </>
116
+ }
@@ -0,0 +1,92 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { extend, useInstanceHandle } from '@octanejs/three';
3
+ import { useLayoutEffect, useMemo, useRef, useState, type RefObject } from '@octanejs/three/renderer';
4
+ import { universalComponent } from 'octane/universal';
5
+ import * as THREE from 'three';
6
+ import { WireframeMaterial, type WireframeMaterialProps, WireframeMaterialShaders, setWireframeOverride, useWireframeUniforms } from '../materials/WireframeMaterial.js';
7
+
8
+ interface WireframeProps { geometry?: THREE.BufferGeometry | RefObject<THREE.BufferGeometry>; simplify?: boolean }
9
+ type WithGeometry = THREE.Mesh<THREE.BufferGeometry, THREE.Material> | THREE.Line<THREE.BufferGeometry, THREE.Material> | THREE.Points<THREE.BufferGeometry, THREE.Material>;
10
+ const MeshWireframeMaterial = extend(WireframeMaterial);
11
+
12
+ function isWithGeometry(object?: THREE.Object3D | null): object is WithGeometry { return !!(object as THREE.Mesh)?.geometry; }
13
+ function isGeometry(object?: unknown | null): object is THREE.BufferGeometry { return !!(object as THREE.BufferGeometry)?.isBufferGeometry; }
14
+ function isRefObject<T>(object?: unknown | null): object is RefObject<T> { return !!(object as RefObject<T>)?.current; }
15
+ function isRef<T>(object?: unknown | null): object is RefObject<T> { return (object as RefObject<T>)?.current !== undefined; }
16
+ function isWireframeGeometry(geometry: unknown): geometry is THREE.WireframeGeometry { return (geometry as THREE.WireframeGeometry)?.type === 'WireframeGeometry'; }
17
+
18
+ function getUniforms() {
19
+ const uniforms: Record<string, THREE.IUniform> = {};
20
+ for (const key in WireframeMaterialShaders.uniforms) uniforms[key] = { value: WireframeMaterialShaders.uniforms[key as keyof typeof WireframeMaterialShaders.uniforms] };
21
+ return uniforms;
22
+ }
23
+
24
+ function getBarycentricCoordinates(geometry: THREE.BufferGeometry, removeEdge?: boolean) {
25
+ const count = geometry.getAttribute('position').count; const barycentric: number[] = [];
26
+ for (let i = 0; i < count; i++) {
27
+ const Q = removeEdge ? 1 : 0;
28
+ if (i % 2 === 0) barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
29
+ else barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
30
+ }
31
+ return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
32
+ }
33
+
34
+ function getInputGeometry(input: THREE.BufferGeometry | RefObject<THREE.BufferGeometry> | RefObject<THREE.Object3D>) {
35
+ const geometry = isRefObject(input) ? input.current : input;
36
+ if (!isGeometry(geometry)) {
37
+ if (isWireframeGeometry(geometry)) throw new Error('Wireframe: WireframeGeometry is not supported.');
38
+ const parent = geometry?.parent;
39
+ if (isWithGeometry(parent)) {
40
+ if (isWireframeGeometry(parent.geometry)) throw new Error('Wireframe: WireframeGeometry is not supported.');
41
+ return parent.geometry;
42
+ }
43
+ } else return geometry;
44
+ }
45
+
46
+ function setBarycentricCoordinates(geometry: THREE.BufferGeometry, simplify: boolean) {
47
+ if (geometry.index) {
48
+ console.warn('Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.');
49
+ geometry.copy(geometry.toNonIndexed()); geometry.setIndex(null);
50
+ }
51
+ geometry.setAttribute('barycentric', getBarycentricCoordinates(geometry, simplify));
52
+ }
53
+
54
+ function WireframeWithCustomGeo({ geometry: customGeometry, simplify = false, ...props }: WireframeProps & WireframeMaterialProps) @{
55
+ const [geometry, setGeometry] = useState<THREE.BufferGeometry | null>(null);
56
+ useLayoutEffect(() => {
57
+ const value = getInputGeometry(customGeometry!);
58
+ if (!value) throw new Error('Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.');
59
+ setBarycentricCoordinates(value, simplify);
60
+ if (isRef(customGeometry)) setGeometry(value);
61
+ }, [simplify, customGeometry]);
62
+ const drawnGeometry = isRef(customGeometry) ? geometry : customGeometry;
63
+ @if (drawnGeometry) {
64
+ <mesh geometry={drawnGeometry as THREE.BufferGeometry}>
65
+ <MeshWireframeMaterial attach="material" transparent side={THREE.DoubleSide} polygonOffset polygonOffsetFactor={-4} {...props as any} extensions={{ derivatives: true, fragDepth: false, drawBuffers: false, shaderTextureLOD: false }} />
66
+ </mesh>
67
+ }
68
+ }
69
+
70
+ function WireframeWithoutCustomGeo({ simplify = false, ...props }: Omit<WireframeProps, 'geometry'> & WireframeMaterialProps) @{
71
+ const objectRef = useRef<THREE.Object3D | null>(null);
72
+ const instance = useInstanceHandle(objectRef);
73
+ const uniforms = useMemo(() => getUniforms(), []);
74
+ useWireframeUniforms(uniforms, props);
75
+ useLayoutEffect(() => {
76
+ const geometry = getInputGeometry(objectRef as RefObject<THREE.Object3D>);
77
+ if (!geometry) throw new Error('Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.');
78
+ const original = geometry.clone(); setBarycentricCoordinates(geometry, simplify);
79
+ return () => { geometry.copy(original); original.dispose(); };
80
+ }, [simplify]);
81
+ useLayoutEffect(() => {
82
+ const parentMesh = instance.current!.parent!.object as THREE.Mesh<THREE.BufferGeometry, THREE.Material>;
83
+ const original = parentMesh.material.clone(); setWireframeOverride(parentMesh.material, uniforms);
84
+ return () => { parentMesh.material.dispose(); parentMesh.material = original; };
85
+ }, []);
86
+ <object3D ref={objectRef as any} />
87
+ }
88
+
89
+ export function Wireframe(props: WireframeProps & WireframeMaterialProps) @{
90
+ const component = props.geometry ? WireframeWithCustomGeo : WireframeWithoutCustomGeo;
91
+ <>{universalComponent('three', component as any, props as unknown as Record<string, unknown>)}</>
92
+ }